Frage

Frage :

Was wird als "Best Practice" sein - und warum - von Handhabungsfehlern in einem Konstruktor ?.

"Best Practice" kann ein Zitat von Schwartz, oder 50% der CPAN-Module sein, es zu verwenden, etc ...; aber ich bin glücklich mit gut begründeter Stellungnahme von jedermann, auch wenn es erklärt, warum die gemeinsame beste Praxis nicht wirklich der beste Ansatz ist.

Was meine eigene Sicht auf das Thema (durch Software-Entwicklung seit vielen Jahren in Perl informiert), ich habe drei Hauptansätze gesehen Handhabung in einem Perl-Modul für Fehler (am besten aus den aufgeführten schlimmsten meiner Meinung nach):

  1. Konstruiere ein Objekt, ein ungültiges Flag gesetzt (in der Regel "is_valid" -Methode). Oft verbunden mit der Einstellung Ihrer Klasse Fehlerbehandlung Fehlermeldung über.

    Pros :

    • Ermöglicht Standard (im Vergleich zu anderen Methodenaufrufen) Fehlerbehandlung, da es $obj->errors() Typen Anrufe nach einem schlechten Konstruktor wie nach jedem anderen Methodenaufruf verwenden kann.

    • Hier können zusätzliche Informationen (z> 1 Fehler, Warnungen, etc ...)

    • übergeben werden
    • Ermöglicht leichte „Redo“ / „fixme“ -Funktionalität, mit anderen Worten, wenn das Objekt, das sehr schwer ist aufgebaut, mit vielen komplexen Attributen, die immer 100% in Ordnung sind, und der einzige Grund, warum es nicht gültig ist, weil jemand ein falsches Datum eingegeben, können Sie einfach „$obj->setDate()“ tun können, anstatt der Aufwand für erneute Ausführung wieder gesamten Konstruktor. Dieses Muster ist nicht immer erforderlich, kann aber in der richtigen Design.

    • enorm nützlich sein

    Cons . Keine, die ich kenne

  2. Return "undef".

    Cons : Kann keine der Vorteile der ersten Lösung erreichen (pro-Objekt Fehlermeldungen außerhalb von globalen Variablen und leicht „fixme“ -Fähigkeit für schwere Gegenstände)

  3. innerhalb des Konstruktors Die. Außerhalb von einigen sehr schmalen Rand Fällen ich dies eine schreckliche Wahl persönlich halte für zu viele Gründe, die am Rande dieser Frage aufzulisten.

  4. UPDATE: Just klar sein, halte ich die (ansonsten sehr würdig und ein tolles Design) Lösung von sehr einfachen Konstruktor hat, die gar nicht und ein schweres initializer Verfahren, bei dem alle Fehlerüberprüfung fehlschlagen können auftritt sein nur eine Teilmenge von jedem Fall # 1 (wenn initializer setzt Fehler-Flags) oder Fall # 3 (wenn initializer stirbt) für die Zwecke dieser Frage. Offensichtlich, wie ein Konzept gewählt, automatisch ablehnen Option # 2.

War es hilfreich?

Lösung

Es hängt davon ab, wie Sie Ihre Konstrukteure verhalten.

Der Rest dieser Antwort geht in meine persönlichen Beobachtungen, aber wie bei den meisten Dingen Perl, wirklich Best Practices läuft darauf hinaus, „Hier ist eine Möglichkeit, es zu tun, die Sie nehmen oder lassen können je nach Ihren Bedürfnissen.“ Ihre Präferenzen, wie Sie beschrieben sie sind völlig gültig und konsistent, und niemand sollte man sonst sagen.

I eigentlich lieber sterben, wenn Bau fehlschlägt, weil wir es so einrichten, dass die einzigen Arten von Fehlern, die wirklich groß sind bei der Objektkonstruktion auftreten können, offensichtliche Fehler, die Ausführung anhalten sollte.

Auf der anderen Seite, wenn Sie das bevorzugen nicht passiert, ich glaube, ich 2 über 1 bevorzugen würde, weil es so einfach ist für einen nicht definierten Objekt zu überprüfen, wie es ist für einige Flagvariable zu überprüfen. Dies ist nicht C, so dass wir keine starke Typisierung Constraint uns mitzuteilen haben, dass unser Konstruktor ein Objekt dieser Art zurückkehren. So undef Rückkehr und Überprüfung für den Erfolg oder Misserfolg zu etablieren, ist eine gute Wahl.

Die ‚Overhead‘ Konstruktionsversagen ist eine Berücksichtigung in bestimmten Grenzfällen (wo man nicht so schnell vor dem Aufwand für ausfallen kann), so dass für diejenigen, die Sie bevorzugen Methode könnten 1. Also noch einmal, es hängt davon ab, welche Semantik Sie haben für die Objektkonstruktion definiert. Zum Beispiel, ziehe ich es außerhalb von Bau-Schwergewichts-Initialisierung zu tun. In Bezug auf Standardisierung, denke ich, dass geprüft wird, ob ein Konstruktor ein definierte Objekt zurückgibt, wie gut ist ein Standard einen Flag-Variable als Kontrolle.

EDIT: Als Antwort auf deine Bearbeitung über initializers Ablehnung Fall # 2, sehe ich nicht, warum ein initializer kann einfach keinen Wert zurück, der eher Erfolg oder Misserfolg zeigt als eine Flag-Variable Einstellung . Eigentlich können Sie beide verwenden möchten, je nachdem, wie viele Details Sie wollen über die Fehler, die aufgetreten sind. Aber es wäre durchaus möglich, ein initializer auf Erfolg und undef bei Ausfall true zurück.

Andere Tipps

Ich ziehe es:

  1. Sie so wenig wie möglich Initialisierung im Konstruktor.
  2. croak mit einer informativen Nachricht, wenn etwas schief geht.
  3. Verwenden Sie geeignete Initialisierungsmethoden pro Nachrichten Objektfehler zur Verfügung zu stellen usw.

Darüber hinaus undef Rückkehr (statt Quaken) ist bei Fein die Benutzer der Klasse kümmern können nicht, warum genau der Fehler aufgetreten ist, nur dann, wenn sie ein gültiges Objekt bekamen oder nicht.

Ich verachte leicht is_valid Methoden oder das Hinzufügen von zusätzlichen Kontrollen vergessen Methoden, um sicherzustellen, nicht, wenn der innere Zustand des Objekts aufgerufen wird, nicht gut definiert.

sagen, dass ich diese von einer sehr subjektiven Perspektive, ohne irgendwelche Aussagen über Best Practices zu machen.

Ich würde empfehlen, gegen # 1, nur weil es zu mehr Fehlerbehandlung Code führt, die wird nicht geschrieben. Zum Beispiel, wenn Sie nur return false dann funktioniert das gut.

my $obj = Class->new or die "Construction failed...";

Aber wenn Sie ein Objekt zurückgeben, die ungültig ist ...

my $obj = Class->new;
die "Construction failed @{[ $obj->error_message ]}" if $obj->is_valid;

Und als die Menge des Fehlerbehandlungscode erhöht die Wahrscheinlichkeit, geschrieben sinkt sein. Und es ist nicht linear. Durch die Erhöhung verringern die Komplexität Ihrer Fehlerbehandlungssystem, das Sie tatsächlich die Menge der Fehler es in der praktischen Anwendung fangen.

Sie müssen auch darauf achten, dass Ihr ungültiges Objekt in Frage stirbt, wenn jede Methode (abgesehen von is_valid und error_message), was zu noch mehr Code und die Möglichkeiten für Fehler aufgerufen wird.

Aber ich stimme es Wert in der Lage, Informationen über den Fehler zu bekommen, was falsch macht Rückkehr (nur return nicht return undef) schlechter. Traditionell geschieht dies durch eine Klassenmethode oder globale Variable wie in DBI aufrufen.

my $ dbh = DBI-> connect ($ data_source, $ username, $ password)               oder sterben $ DBI :: errstr;

Aber es leidet unter A) haben Sie immer noch die Fehlerbehandlung Code und B schreiben) seine nur gültig für die letzte Operation.

Das Beste, was zu tun ist, in der Regel ist eine Ausnahme mit croak werfen. Jetzt im Normalfall der Benutzer keinen speziellen Code schreibt, tritt der Fehler an dem Punkt des Problems, und sie erhalten eine gute Fehlermeldung standardmäßig aktiviert.

my $obj = Class->new;

Perl traditionelle Empfehlungen gegen Ausnahmen in der Bibliothek Code werfen als unhöflich zu sein, sind veraltet. Perl-Programmierer sind (schließlich) umfassen Ausnahmen. Anstatt das Schreiben der Fehlerbehandlung Code immer und immer wieder, schlecht und oft vergessen, DWIM Ausnahmen. Wenn Sie nicht überzeugt sind nur beginnen autodie ( Uhr pjf Video darüber ) und Sie werden nie mehr zurück.

Ausnahmen ausrichten Huffman-Codierung mit dem tatsächlichen Verwendung. Der gemeinsame Fall von dem Konstruktor erwartet einen Fehler nur zu arbeiten und wollte, wenn es nicht tut, ist jetzt der am wenigsten Code. Der ungewöhnliche Fall zu wollen, dass die Fehler behandeln erfordert spezielle Code zu schreiben. Und der spezielle Code ist ziemlich klein.

my $obj = eval { Class->new } or do { something else };

Wenn Sie sich jeden Anruf in einer eval Umwickeln Sie es falsch machen. Ausnahmen werden so genannt, weil sie außergewöhnlich sind. Wenn, wie in Ihrem Kommentar oben, Sie anmutige Fehlerbehandlung willen für den Benutzer mögen, dann nutzen Sie die Tatsache, dass Fehler Blase den Stapel nach oben. Zum Beispiel, wenn Sie eine nette User Fehlerseite zur Verfügung zu stellen und auch die Fehler melden Sie dies tun können:

eval {
    run_the_main_web_code();
} or do {
    log_the_error($@);
    print_the_pretty_error_page;
};

Sie müssen es nur an einer Stelle, an der Spitze des Call-Stack, anstatt überall verstreut. Sie können diesen Vorteil bei kleineren Schritten erfolgen, beispielsweise ...

my $users = eval { Users->search({ name => $name }) } or do {
    ...handle an error while finding a user...
};

Es gibt zwei Dinge geht. 1) Users->search liefert immer einen wahren Wert, in diesem Fall ein Array ref. Das macht die einfache my $obj = eval { Class->method } or do Arbeit. Das ist optional. Aber noch wichtiger ist 2) Sie brauchen nur spezielle Fehler setzen um Users->search Handhabung. Alle genannten Methoden innerhalb Users->search und alle Methoden, die sie nennen ... sie nur Ausnahmen werfen. Und sie sind alle an einem Punkt abgefangen und behandelt das gleiche. Umgang mit der Ausnahme an dem Punkt, der kümmert sich um sie für viele sauberen, kompakten und flexiblere Fehlerbehandlung Code macht.

Sie können von croaking mit einem String-Objekt überlastet und nicht nur einen String.

Weitere Informationen in die Ausnahme packen
my $obj = eval { Class->new }
  or die "Construction failed: $@ and there were @{[ $@->num_frobnitz ]} frobnitzes";

Ausnahmen:

  • Tu das Richtige ohne einen Gedanken durch den Anrufer
  • Erfordern die am wenigsten Code für die häufigste Fall
  • Geben Sie die größte Flexibilität und Informationen über den Fehler to der Anrufer

Module wie Versuchen :: Tiny meisten der hängenden Probleme zu beheben eval Umgebung mit als Ausnahme-Handler.

Wie für Ihren Anwendungsfall, wo man ein sehr teures Objekt haben könnte und will versuchen, weiter mit ihm bauen teilweise ... riecht wie YAGNI mich. Haben Sie es wirklich brauchen? Oder Sie haben einen aufgeblähten Objekt-Design, die zu früh zu viel Arbeit macht. Wenn Sie es tun, dann können Sie die Informationen setzen notwendig, den Bau in der Ausnahme-Objekt, um fortzufahren.

Zuerst wird die pompösen allgemeinen Bemerkungen:

  1. Ein Job Konstruktor sollte sein: Bei gültigen Konstruktionsparameter, geben Sie ein gültiges Objekt
  2. .
  3. Ein Konstruktor, der kein gültiges Objekt nicht konstruieren kann nicht seine Aufgabe erfüllen und ist damit ein perfekter Kandidat für die Ausnahme Generation.
  4. Sicherstellen, dass das konstruierte Objekt gültig ist Teil des Jobs Konstruktor. Austeilen einer bekannten sein-bad-Objekt und unter Berufung auf den Client zu prüfen, ob das Objekt gültig ist eine todsichere Methode, mit ungültigen Objekte aufzuwickeln, die für nicht-offensichtlichen Gründen in abgelegenen Orten explodieren.
  5. Überprüfen, dass alle richtigen Argumente an Ort und Stelle vor dem Konstruktoraufruf sind ist der Kunden Job.
  6. Ausnahmen bieten eine feinkörnige Art und Weise die besondere Fehler zu propagieren, die ohne die Notwendigkeit aufgetreten ein gebrochenes Objekt in der Hand zu haben.
  7. return undef; ist immer schlecht [1]
  8. bIlujDI 'yIchegh () Qo'; yIHegh ()!

Nun zur eigentlichen Frage, die ich meine, wird auslegen zu „was tun Sie, DARCH, betrachten die beste Praxis und warum“. Ich werde zuerst beachten, dass ein falscher Wert bei einem Fehler der Rückkehr eine lange Perl Geschichte hat (die meisten der Kern funktioniert so, zum Beispiel), und viele Module folgen dieser Konvention. Aber es stellt sich heraus, diese Konvention produziert minderwertig Client-Code und neuere Module bewegen sich weg von ihm. [2]

[Die Stütz Argument und Codebeispiele für diese entpuppen der allgemeinere Fall für Ausnahmen sein, die die Erstellung von autodie , und so werde ich die Versuchung widerstehen, diesen Fall hier zu machen. Statt:]

für die erfolgreiche Erstellung zu überprüfen ist eigentlich mehr belastender ist als bei einer geeigneten Ausnahmebehandlungsstufe für eine Ausnahme zu überprüfen. Die anderen Lösungen erfordern die sofortige Client mehr Arbeit zu tun, als es nur ein Objekt zu erhalten, müssen sollte, Arbeit, die nicht erforderlich ist, wenn der Konstruktor nicht durch eine Ausnahme auszulösen. [3] Eine Ausnahme ist wesentlich ausdrucksvoller als undef und ebenso ausdrucksstark wie Führen eines gebrochenen Objekt zum Zwecke der Dokumentation von Fehlern und zu deren auf verschiedenen Ebenen in den Call-Stack mit Anmerkungen versehen.

können Sie erhalten auch die teilweise konstruiertes Objekt, wenn Sie es zurück in die Ausnahme passieren. Ich denke, das ist eine schlechte Praxis pro meinem Glauben zu wissen, was ein Konstruktor Vertrag mit seinen Kunden sein soll, aber das Verhalten unterstützt wird. Ungeschickt.

So: Ein Konstruktor, der kein gültiges Objekt erstellen kann, sollte eine Ausnahme so früh wie möglich werfen. Die Ausnahmen ein Konstruktor werfen können, sollten Teile der Schnittstelle zu dokumentieren. Nur die Berufung Ebenen, die auf die Ausnahme nach Bedeutung handeln kann sogar für sie aussehen sollte; sehr oft, das Verhalten „wenn diese Konstruktion nicht, nichts tun“, ist genau richtig.

[1]: Damit meine ich, bin ich keine Kenntnis von Anwendungsfällen, wo return; nicht streng überlegen ist. Wenn mich jemand auf diese Anrufe könnte ich eigentlich eine Frage offen. Also bitte nicht. ;)
[2]:. Per meine extrem unwissenschaftlich Erinnerung an die Modulschnittstellen ich in den letzten zwei Jahren gelesen haben, unterliegen sowohl Auswahl und Bestätigung Vorurteile
[3]: Beachten Sie, dass eine Ausnahme zu werfen erfordert immer noch Fehlerbehandlung, wie es die anderen vorgeschlagenen Lösungen. Dies bedeutet nicht, jede Instanziierung in einer eval Verpackung, wenn Sie tatsächlich tun wollen komplexe Fehlerbehandlung um jede Konstruktion (und wenn Sie denken, Sie tun, sind Sie wahrscheinlich falsch). Es bedeutet, den Anruf Einwickeln, die Bedeutung der Lage ist, in einem eval auf der Ausnahme zu handeln.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top