2010-04-09 5 views
24

Je me demandais si les gens partageraient leurs meilleures pratiques/stratégies sur la gestion des exceptions & erreurs. Maintenant, je ne demande pas quand lancer une exception (il a été répondu tridement ici: SO: When to throw an Exception). Et je n'utilise pas cela pour mon flux d'applications - mais il y a des exceptions légitimes qui se produisent tout le temps. Par exemple, le plus populaire serait ActiveRecord :: RecordNotFound. Quelle serait la meilleure façon de le gérer? La façon DRY?Quelle est la meilleure stratégie pour gérer les exceptions et les erreurs dans Rails?

En ce moment je fais beaucoup de contrôle dans mon contrôleur si Post.find(5) renvoie Nil - Je vérifie pour cela et envoie un message flash. Cependant, bien que ce soit très granulaire - c'est un peu lourd dans un sens que j'ai besoin de vérifier des exceptions comme ça dans chaque contrôleur, alors que la plupart d'entre eux sont essentiellement les mêmes et concernent des enregistrements non trouvés ou connexes non trouvés - tels comme Post.find(5) introuvable ou si vous essayez d'afficher des commentaires liés à la publication qui n'existe pas, cela créerait une exception (quelque chose comme Post.find(5).comments[0].created_at)

Je sais que vous pouvez faire quelque chose comme ça dans ApplicationController et l'écraser plus tard dans un contrôleur particulier/méthode pour obtenir un soutien plus granulaire, mais ce serait une bonne façon de le faire?

class ApplicationController < ActionController::Base 
    rescue_from ActiveRecord::RecordInvalid do |exception| 
     render :action => (exception.record.new_record? ? :new : :edit) 
    end 
end 

Aussi cela fonctionnerait en cas Post.find(5) pas trouvé, mais qu'en est-Post.find(5).comments[0].created_at - Je voulais dire que je ne peux pas lancer une exception épanouie si le poste existe, mais n'a pas de commentaires, non?

Pour résumer jusqu'à présent, je faisais beaucoup de vérifications manuelles en utilisant if/else/unless ou case/when (et j'avoue occasionnellement commencer/rescue) et en vérifiant zéro? ou vide ?, etc, mais il doit y avoir une meilleure façon, il semble.

: REPONSES

@Milan: Salut Milan Merci pour avoir une réponse - Je suis d'accord avec ce que vous avez dit, et je pense que j'abusé l'exception des mots. Ce que je voulais dire est que en ce moment je fais beaucoup de choses comme:

if Post.exists?(params[:post_id]) 
    @p = Post.find(params[:post_id]) 
else 
    flash[:error] = " Can't find Blog Post" 
end 

Et je fais beaucoup de ce genre de « gestion des exceptions », je tente d'éviter d'utiliser begin/sauvetage. Mais il me semble que c'est un résultat/une vérification/une situation assez commun qu'il devrait y avoir un moyen plus sûr de le faire, n'est-ce pas? Comment feriez-vous ce genre de vérification?

Aussi comment le gérer dans ce cas? Disons que vous voulez afficher la date de création commentaire à votre avis:

Last comment for this post at : <%= @post.comments[0].created_at %> 

Et ce poste n'a pas de commentaires. Vous pouvez faire

Last comment for this post at : <%= @post.comments.last.created_at unless @post.comments.empty? %> 

Vous pouvez faire une vérification dans le contrôleur. Etc. Il y a plusieurs façons de le faire. Mais quel est le "meilleur" moyen de gérer cela?

Répondre

14

Le fait que vous faites un lot de vérification manuelle des exceptions suggère que vous ne les utilisez pas correctement. En fait, aucun de vos exemples n'est exceptionnel. En ce qui concerne la publication inexistante, vous devriez vous attendre à ce que vos utilisateurs de l'API (par exemple, un utilisateur utilisant votre site Web via le navigateur) demandent des publications inexistantes.

Votre second exemple (Post.find (5) .comments [0] .created_at) n'est pas non plus exceptionnel. Certains postes n'ont tout simplement pas de commentaires et vous le savez à l'avance. Alors pourquoi devrait-il y avoir une exception?

La même chose est le cas avec l'exemple ActiveRecord :: RecordInvalid. Il n'y a aucune raison de gérer ce cas au moyen d'une exception. Le fait qu'un utilisateur entre des données invalides dans un formulaire est une chose assez courante et il n'y a rien d'exceptionnel à ce sujet. L'utilisation du mécanisme d'exception pour ce genre de situations peut être très pratique dans certaines situations, mais elle est incorrecte pour les raisons mentionnées ci-dessus. Cela dit, cela ne signifie pas que vous ne pouvez pas sécher le code qui encapsule ces situations. Il y a une grande chance que vous puissiez le faire au moins dans une certaine mesure, car ce sont des situations assez courantes.

Alors, qu'en est-il des exceptions? Eh bien, la première règle est vraiment: les utiliser aussi peu que possible.

Si vous avez vraiment besoin de les utiliser, il y a deux types d'exceptions en général (comme je le vois):

  1. exceptions qui ne cassent pas flux de travail général de l'utilisateur dans votre application (imaginez une exception à l'intérieur de votre routine de génération de vignettes d'image de profil) et vous pouvez soit les cacher à l'utilisateur ou vous le notifier juste au sujet du problème et de ses conséquences lorsque cela est nécessaire pour l'utilisateur. Ce sont les derniers recours et doivent être traités via l'erreur 500 interne du serveur dans les applications Web.

J'ai tendance à utiliser la méthode rescue_from dans le ApplicationController que pour ce dernier, car il y a des endroits plus appropriés pour le premier type et la ApplicationController comme la plus haute des classes de contrôleur semble être le bon endroit pour tomber Retour à dans de telles circonstances (bien que de nos jours, une sorte de middleware Rack pourrait être l'endroit encore plus approprié pour mettre une telle chose).

- EDIT -

La partie constructive:

En ce qui concerne la première chose, mon conseil serait de commencer à utiliser find_by_id au lieu de trouver, car il ne jette pas une exception mais renvoie zéro si elle échoue. Votre code ressemblerait à ceci:

unless @p = Post.find_by_id(params[:id]) 
    flash[:error] = "Can't find Blog Post" 
end 

ce qui est beaucoup moins bavard.

Un autre idiome commun pour sécher ce genre de situations est d'utiliser le contrôleur before_filters pour définir les variables souvent utilisées (comme @p dans ce cas).Après cela, votre contrôleur peut se présenter comme suit

controller PostsController 
    before_filter :set_post, :only => [:create, :show, :destroy, :update] 

    def show 
     flash[:error] = "Can't find Blog Post" unless @p 
    end 

private 

    def set_post 
    @p = Post.find_by_id(params[:id]) 
    end 

end 

En ce qui concerne la deuxième situation (commentaire dernière non-existante), une solution évidente à ce problème est de déplacer la chose en une aide:

# This is just your way of finding out the time of the last comment moved into a 
# helper. I'm not saying it's the best one ;) 
def last_comment_datetime(post) 
    comments = post.comments 
    if comments.empty? 
    "No comments, yet." 
    else 
    "Last comment for this post at: #{comments.last.created_at}" 
    end 
end 

Ensuite, votre point de vue, vous voulez simplement appeler

<%= last_comment_datetime(post) %> 

de cette façon, le cas de bord (post sans commentaires) seront traitées dans sa propre place et il ne sera pas encombrer la vue. Je sais, aucun de ceux-ci ne suggère un modèle pour traiter les erreurs dans Rails, mais peut-être qu'avec certaines refactorings comme celles-ci vous trouverez qu'une grande partie de la nécessité d'une stratégie de gestion des exceptions/erreurs disparaît. .

+0

S'il vous plaît - voir ma réponse ci-dessus dans la réponse - je ne peux pas poster le code correctement échancré dans les commentaires :-) – konung

+0

Hey Nick, désolé pour le sondage si Ranty. Je n'ai tout simplement pas eu le temps d'ajouter une partie constructive à ma réponse. Maintenant, c'est là. J'espère que tout le monde répondra que c'est plus utile maintenant. –

+0

Pas de problème - aucune offense prise, ma question n'était pas très claire non plus - jusqu'à ce que je l'ai nettoyé. Vous m'avez donné quelques bons conseils - je vais juste attendre un peu, peut-être que quelqu'un a quelques suggestions de plus et je marquerai cela comme une réponse. Merci pour une réponse détaillée. :-) – konung

1

Les exceptions sont pour des circonstances exceptionnelles. Une mauvaise entrée de l'utilisateur n'est généralement pas exceptionnelle; si quelque chose, c'est assez commun. Lorsque vous avez une situation exceptionnelle, vous voulez vous donner autant d'informations que possible. D'après mon expérience, le meilleur moyen d'y parvenir est d'améliorer religieusement la gestion des exceptions en fonction de l'expérience de débogage. Lorsque vous rencontrez une exception, la première chose à faire est d'écrire un test unitaire. La deuxième chose que vous devriez faire est de déterminer s'il y a plus d'informations qui peuvent être ajoutées à l'exception. Plus d'informations dans ce cas prennent généralement la forme d'attraper l'exception plus haut dans la pile et de la gérer ou de lancer une nouvelle exception, plus informative, qui bénéficie d'un contexte supplémentaire. Ma règle personnelle est que je n'aime pas attraper des exceptions de plus de trois niveaux dans la pile. Si une exception doit aller plus loin, vous devez l'attraper plus tôt. En ce qui concerne l'exposition des erreurs dans l'interface utilisateur, les instructions if/case sont totalement correctes tant que vous ne les imbriquez pas trop profondément. C'est alors que ce genre de code devient difficile à maintenir. Vous pouvez résumer ceci si cela devient un problème.

Par exemple:

def flash_assert(conditional, message) 
    return true if conditional 
    flash[:error] = message 
    return false 
end 

flash_assert(Post.exists?(params[:post_id]), "Can't find Blog Post") or return 
Questions connexes