2010-04-09 13 views
24

Ich frage mich, ob Leute ihre Best Practices/Strategien zum Umgang mit Ausnahmen & Fehler teilen würden. Jetzt frage ich nicht, wann ich eine Ausnahme werfen soll (es wurde gründlich hier geantwortet: SO: When to throw an Exception). Und ich verwende das nicht für meinen Anwendungsfluss - aber es gibt legitime Ausnahmen, die die ganze Zeit passieren. Zum Beispiel wäre ActiveRecord :: RecordNotFound am beliebtesten. Was wäre der beste Weg, damit umzugehen? Der trockene Weg?Was ist die beste Strategie, um Ausnahmen und Fehler in Rails zu behandeln?

Im Moment mache ich eine Menge Kontrollen innerhalb meines Controllers, also wenn Post.find(5) Nil zurückgibt - ich überprüfe das und werfe eine Flash-Nachricht. Obwohl dies sehr granular ist - es ist ein bisschen umständlich in einem Sinne, dass ich in jedem Controller nach Ausnahmen wie diesem suchen muss, während die meisten im Wesentlichen die gleichen sind und mit nicht gefundenen oder nicht gefundenen Datensätzen zu tun haben - so entweder als Post.find(5) nicht gefunden oder wenn Sie versuchen, Kommentare vorhanden sind, die nicht zu schreiben im Zusammenhang anzuzeigen, die eine Ausnahme (so etwas wie Post.find(5).comments[0].created_at)

werfen würde weiß ich Sie so etwas wie dies in Application tun können, und überschreiben sie später in einem bestimmten Controller/Methode, um granularere Unterstützung zu bekommen, aber wäre das ein geeigneter Weg, dies zu tun?

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

Auch würde dies im Fall arbeitet Post.find(5) nicht gefunden, aber was ist Post.find(5).comments[0].created_at - ich meine ich nicht eine vollständige geblasene Ausnahme auslösen kann, wenn der Beitrag existiert, aber hat nicht Kommentare, nicht wahr?

Um zusammenzufassen, habe ich eine Menge manuelle Überprüfung mit if/else/außer oder case/wann (und ich gestehe gelegentlich beginnen/rette) und Prüfung auf Null? oder leer? usw., aber es muss einen besseren Weg geben.

ANTWORTEN:

@Milan: Hallo Milan Vielen Dank für eine Antwort - ich stimme mit dem, was Sie gesagt haben, und ich denke, ich das Wort Ausnahme mißbraucht. Was ich meinte ist, dass gerade jetzt mache ich eine Menge Dinge wie:

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

Und ich habe eine Menge dieser Art von „Exception Handling“, versuche ich mit zu vermeiden beginnen/Rettung. Aber es scheint mir, dass dies ein allgemein genug Ergebnis/Verifikation/Situation ist, dass es einen DRYer Weg geben sollte, das zu tun, nicht wahr? Wie würden Sie diese Art von Überprüfung durchführen?

Auch wie würde es in diesem Fall damit umgehen? Angenommen, Sie Kommentar erstellt Datum in der Ansicht angezeigt werden sollen:

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

Und dieser Beitrag keine Kommentare hat. Sie können

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

tun Sie könnten einen Scheck in Controller tun. Etc. Es gibt mehrere Möglichkeiten, dies zu tun. Aber was ist der "beste" Weg, damit umzugehen?

Antwort

14

Die Tatsache, dass Sie viel der manuellen Überprüfung auf Ausnahmen tun, schlägt vor, dass Sie gerade nicht sie richtig verwenden. In der Tat ist keines Ihrer Beispiele außergewöhnlich.

Wie für den nicht vorhandenen Post - Sie sollten erwarten, dass Ihre API-Benutzer (z. B. ein Benutzer, der Ihr Web über den Browser verwendet) nach nicht vorhandenen Posts fragt.

Ihr zweites Beispiel (Post.find (5) .comments [0] .created_at) ist auch nicht außergewöhnlich. Einige Beiträge haben einfach keine Kommentare und Sie wissen es im Voraus. Warum sollte das eine Ausnahme auslösen?

Das gleiche gilt für das Beispiel ActiveRecord :: RecordInvalid. Es gibt einfach keinen Grund, diesen Fall mit einer Ausnahme zu behandeln. Dass ein Benutzer einige ungültige Daten in ein Formular eingibt, ist eine ziemlich gewöhnliche Sache und es gibt nichts Außergewöhnliches daran.

Die Verwendung des Ausnahmemechanismus für diese Situationen kann in einigen Situationen sehr praktisch sein, ist jedoch aus den oben genannten Gründen nicht korrekt.

Mit diesem gesagt bedeutet es nicht, dass Sie nicht den Code, der diese Situationen kapselt, DRY. Es ist eine ziemlich große Chance, dass Sie es zumindest in gewissem Maße tun können, da dies ziemlich häufige Situationen sind.

Also, was ist mit den Ausnahmen? Nun, die erste Regel ist wirklich: Verwenden Sie sie so sparsam wie möglich.

Wenn Sie wirklich sie dort in der Regel zwei Arten von Ausnahmen verwenden müssen (wie ich es sehe):

  1. Ausnahmen, die den Benutzer allgemeinen Workflow innerhalb Ihrer App nicht brechen (man stelle sich eine Ausnahme in Ihrem Profilbild thumbnail generation routine) und Sie können sie entweder vor dem Benutzer verbergen oder Sie benachrichtigen ihn einfach über das Problem und seine Konsequenzen, wenn nötig

  2. Ausnahmen, die den Benutzer davon abhalten, die App überhaupt zu benutzen. Dies ist der letzte Ausweg und sollte über den Fehler 500 interne Server in Web-Anwendungen behandelt werden.

Ich neige dazu, die rescue_from Methode in der Application für letztere nur zu verwenden, da es mehr geeignete Plätze für die erste Art und die Application als obersten der Controller-Klassen sind, scheint der richtige Ort zu sein, fallen zurück zu unter solchen Umständen (obwohl heutzutage eine Art von Rack-Middleware noch geeigneter Ort sein könnte, um so etwas zu setzen).

- EDIT -

Der konstruktive Teil:

Was die erste Sache, mein Rat mit find_by_id statt finden zu starten wäre, da es es keine Ausnahme werfen gibt jedoch null zurück, wenn es nicht erfolgreich ist. Ihr Code würde dann ungefähr so ​​aussehen:

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

was viel weniger gesprächig ist.

Eine andere gebräuchliche Redewendung für DRYing dieser Art von Situationen ist die Verwendung des Controllers before_filters, um die oft verwendeten Variablen (wie @p in diesem Fall) zu setzen.Danach wird der Controller

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 
wie folgt

Was die zweite Lage (nicht vorhandenen letzten Kommentar), eine offensichtliche Lösung dieses Problems aussehen könnte, ist die ganze Sache in einen Helfer zu bewegen:

# 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 

Dann in Ihre Ansichten, würden Sie rufen Sie einfach

<%= last_comment_datetime(post) %> 

auf diese Weise den Rand Fall (post ohne Kommentare) werden in einem eigenen Ort behandelt werden und es wird nicht die Ansicht Krempel.

Ich weiß, keine von diesen schlägt ein Muster für die Handhabung von Fehlern in Rails, aber vielleicht mit einigen Refactorings wie diese finden Sie, dass ein großer Teil der Notwendigkeit für irgendeine Art von Strategie für Ausnahme/Fehlerbehandlung einfach verschwindet .

+0

Bitte - siehe meine Antwort oben in der Antwort - ich nicht richtig eingerückten Code in den Kommentaren :-) – konung

+0

Hey Nick veröffentlichen kann, sorry für so Ranty klingen. Ich hatte einfach nicht genug Zeit, um meiner Antwort einen konstruktiven Teil hinzuzufügen. Jetzt ist es da. Hoffe, die ganze Antwort ist jetzt hilfreicher. –

+0

Kein Problem - keine Beleidigung genommen, meine Frage war auch nicht ganz klar - bis ich es aufgeräumt habe. Du hast mir ein paar gute Tipps gegeben - ich warte nur ein bisschen, vielleicht hat jemand noch ein paar Vorschläge und dann werde ich das als beantwortet markieren. Danke für eine ausführliche Antwort. :-) – konung

1

Ausnahmen gelten für außergewöhnliche Umstände. Schlechte Benutzereingaben sind normalerweise nicht außergewöhnlich. wenn überhaupt, ist es ziemlich üblich. Wenn Sie einen außergewöhnlichen Umstand haben, möchten Sie sich so viele Informationen wie möglich geben. Meiner Erfahrung nach besteht der beste Weg darin, Ihre Ausnahmebehandlung basierend auf der Debugging-Erfahrung religiös zu verbessern. Wenn Sie auf eine Ausnahme stoßen, sollten Sie als Erstes einen Komponententest dafür schreiben. Die zweite Sache, die Sie tun sollten, ist festzustellen, ob es weitere Informationen gibt, die der Ausnahme hinzugefügt werden können. Weitere Informationen finden Sie in diesem Fall normalerweise in der Form, dass Sie die Ausnahme, die sich weiter oben im Stapel befindet, abfangen und entweder behandeln oder eine neue, informativere Ausnahme auslösen, die zusätzlichen Kontext bietet. Meine persönliche Regel ist, dass ich keine Ausnahmen von viel mehr als drei Ebenen auf dem Stapel mag. Wenn eine Ausnahme weiter reisen muss, müssen Sie sie früher abfangen.

Wie zur Fehler in der Benutzeroberfläche, if/case Aussagen sind völlig in Ordnung, solange Sie sie nicht zu tief verschachteln. Das ist, wenn diese Art von Code schwer zu pflegen ist. Sie können dies abstrahieren, wenn es ein Problem wird.

Zum Beispiel:

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 
Verwandte Themen