Wo ist der Fehler, Billy? Wir brauchen einen Fehler ...


Vor einiger Zeit veröffentlichte mein Kollege einen Artikel über die Fehlerbehandlung in Java / Kotlin. Und es wurde für mich interessant, welche Methoden der Fehlerübertragung in der Programmierung im Allgemeinen existieren. Wenn Sie auch interessiert sind, dann ist unter dem Schnitt das Ergebnis der Forschung. Höchstwahrscheinlich wurden einige exotische Methoden weggelassen, aber es gibt nur eine Hoffnung auf Kommentare, die manchmal interessanter und nützlicher für den Habré-Artikel sind. :)

In der Geschichte der Programmiersprachen wurden nicht viele Wege erfunden, um einen Fehler zu vermitteln. Wenn wir uns vollständig lösen, gibt es nur drei davon: eine direkte Rückkehr von einer Funktion, eine Übertragung der Kontrolle und das Festlegen des Zustands. Alles andere ist bis zu dem einen oder anderen Grad eine Mischung dieser Ansätze. Im Folgenden habe ich versucht, die Hauptvertreter dieser drei Arten zu sammeln und zu beschreiben.

Haftungsausschluss: Zur Vereinfachung und Vereinfachung der Wahrnehmung für jeden isolierten ausführbaren Code, der einen Fehler erzeugt, verwende ich das Wort "Funktion" und für alle nicht primitiven (Ganzzahl-, Zeichenfolgen-, Booleschen usw.) Entitäten - "Struktur".

Direkte Rückgabe


Die direkte Rückgabe ist einfach. Dies ist zwar wahrscheinlich die am häufigsten verwendete Methode, aber es gibt viele Möglichkeiten. Die Verarbeitungsmethode vereint sie alle und vergleicht den Rückgabewert mit vordefinierten Werten.

  1. Laufstatus zurückgeben. Die banalste Option ist TRUE (wenn sie normal ausgeführt wurde) oder FALSE (wenn ein Fehler aufgetreten ist).
  2. Geben Sie im Erfolgsfall den richtigen und im Fehlerfall den falschen Wert zurück.
    C / c ++
    Die Funktion strchr () gibt einen Zeiger auf das erste Vorkommen des Zeichens ch in der Zeichenfolge zurück, auf die str zeigt. Wenn ch nicht gefunden wird, wird NULL zurückgegeben.

    Sehr oft werden die Ansätze 1 und 2 in Verbindung mit der Einstellung des Zustands verwendet.
  3. Fehlercode zurückgeben. Wenn wir nicht nur wissen wollen, dass die Ausführung falsch beendet wurde, sondern auch verstehen möchten, wo der Fehler in der Funktion aufgetreten ist. Wenn die Funktion fehlerfrei abgeschlossen wurde, wird normalerweise der Code 0 zurückgegeben. Im Fehlerfall wird der Code verwendet, um eine bestimmte Stelle im Hauptteil der Funktion zu bestimmen, an der ein Fehler aufgetreten ist. Dies ist jedoch keine eiserne Regel. Schauen Sie sich beispielsweise HTTP mit seinen 200 an.
  4. Rückgabe des Fehlercodes in einem ungültigen Wertebereich. Normalerweise sollte eine Funktion normalerweise eine positive Ganzzahl und im Fehlerfall ihren Code mit einem Minuszeichen zurückgeben.

    function countElements(param) { if (!isArray(param)) { return -10; } else if(!isInitialized(param)){ return -20 } else { return count(array); } } 

  5. Geben Sie verschiedene Typen für positive und negative Ergebnisse zurück. Zum Beispiel nominell - eine Zeichenfolge, aber nicht nominell - eine Zahl oder Klasse Erfolg und Klasse Fehler .

     sealed class UserProfileResult { data class Success(val userProfile: UserProfileDTO) : UserProfileResult() data class Error(val message: String, val cause: Exception? = null) : UserProfileResult() } val avatarUrl = when (val result = client.requestUserProfile(userId)) { is UserProfileResult.Success -> result.userProfile.avatarUrl is UserProfileResult.Error -> "http://domain.com/defaultAvatar.png" } 

    Sie können sich auch an Entweder aus der Welt der funktionalen Programmierung erinnern. Obwohl hier kann man streiten.
  6. Rückgabe einer Struktur, die sowohl das Ergebnis selbst als auch den Fehler enthält.

     function doSomething(): array { ... if($somethingWrong === true) { return ["result" => null, "error" => "Alarm!!!"]; } else { return ["result" => $result, "error" => null]; } ... } 

  7. Mehrere Werte zurückgeben. Anfangs war ich geneigt, diese Methode nicht von der vorherigen zu trennen, aber am Ende habe ich beschlossen, sie in einen separaten Absatz aufzunehmen. Diese Option ist ziemlich selten, da sie ausschließlich in Sprachen verwendet werden kann, in denen Sie mehrere Werte von einer Funktion zurückgeben können, und es gibt nicht viele davon. Das auffälligste, aber nicht das einzige Beispiel ist die Go- Sprache.

     f, err := Sqrt(-1) if err != nil { fmt.Println(err) } 

Statuseinstellung


Die älteste und hardcore Version, die bis heute nicht an Relevanz verloren hat. Es besteht darin, dass die Funktion nichts zurückgibt und im Falle eines Fehlers ihren Wert (in irgendeiner Form) in eine separate Entität schreibt, unabhängig davon, ob es sich um ein Prozessorregister, eine globale Variable oder ein privates Klassenfeld handelt. Um diese Art von Fehler zu behandeln, müssen Sie den Wert unabhängig von der richtigen Stelle extrahieren und überprüfen.

  1. Festlegen des "globalen" Status. Ich habe es in Anführungszeichen gesetzt, weil wir meistens über Globalität in einem bestimmten Umfang sprechen.

     # ls /unknown/path 2>/dev/null # echo $? 1 

  2. Festlegen Ihres eigenen Status. Wenn wir eine Struktur haben, die eine Funktion bereitstellt. Die Funktion legt den Status für diese Struktur fest, und der Fehler wird bereits entweder direkt oder mithilfe einer anderen speziellen Funktion aus der Struktur extrahiert.

     $mysqli = new mysqli("localhost", "my_user", "my_password", "world"); $result = $mysqli->query("SET a=1"); if ($mysqli->errno) { printf(" : %d\n", $mysqli->errno); } 

  3. Festlegen des Status des zurückgegebenen Objekts. Stimmt stark mit Absatz 6 überein. aus dem vorherigen Abschnitt. Im Gegensatz zum vorherigen Absatz wird eine Statusprüfung für die zurückgegebene Struktur durchgeführt und nicht für die Struktur, die die Funktion bereitstellt. Als offensichtliches Beispiel arbeiten das HTTP-Protokoll und unzählige Bibliotheken in einer Vielzahl von Sprachen damit.

     Response response = client.newCall("https://www.google.com").execute(); Integer errorCode = response.getCode(); 


Kontrolle übertragen


Und jetzt kommen wir zum modischsten Paradigma. Ausnahmen, Rückrufe, globale Fehlerbehandlungsroutinen - all dies. Was sie alle verbindet, ist, dass im Fehlerfall die Steuerung an einen vorgegebenen Handler übertragen wird und nicht an den Code, der die Funktion aufgerufen hat.

  1. Ausnahmen Jeder weiß werfen / versuchen / fangen. Wenn eine Ausnahme ausgelöst wird, bildet die Funktion eine Struktur, die den Fehler beschreibt und meistens verschiedene nützliche Metadaten enthält, die die Diagnose des Problems erleichtern (z. B. den Aufrufstapel). Danach wird diese Struktur an einen speziellen Mechanismus übergeben, der entlang des Aufrufstapels zum ersten try-Block "zurückrollt", der mit catch verknüpft ist und Ausnahmen dieses Typs verarbeiten kann. Diese Methode ist insofern gut, als die gesamte Logik zum Auslösen einer Ausnahme von der Laufzeit selbst implementiert wird. Das gleiche ist schlecht, da Gemeinkosten (lasst uns nur ohne holivarov :)).
  2. Globale Fehlerbehandlungsroutinen. Nicht der üblichste Weg, aber es tut. Ich weiß nicht einmal, was ich hier sagen soll. Vielleicht ist es erwähnenswert, dass hier auch die Mechanismen von Browsern zugeschrieben werden können: Wenn der Code, der vom Hauptstrom abweicht, die darin eintreffenden Ereignisse überwacht.

     function myErrorHandler($errno, $errstr, $errfile, $errline) { echo "<b>Custom error:</b> [$errno] $errstr<br>"; echo " Error on line $errline in $errfile<br>"; } set_error_handler("myErrorHandler"); 

  3. Rückrufe. Sie werden von Entwicklern für Android, JavaScript und Entschuldigern für reaktive Programmierung sehr geliebt. Das Wesentliche ist einfach: Zusätzlich zu den verarbeiteten Daten werden Handlerfunktionen auf die Funktion übertragen. Im Fehlerfall ruft die Hauptfunktion den entsprechenden Handler auf und leitet den Fehler an ihn weiter.

     var observer = Rx.Observer.create( x => console.log(`onNext: ${x}`), e => console.log(`onError: ${e}`), () => console.log('onCompleted')); 


Es scheint nichts vergessen zu haben.

Und eine lustige Tatsache. Der wahrscheinlich originellste Weg, einen Fehler zurückzugeben, Ausnahmen gleichzeitig zu kombinieren, den Status festzulegen und mehrere Werte zurückzugeben, traf ich in Informix SPL (ich schreibe aus dem Speicher):

 CREATE PROCEDURE some_proc(...) RETURNING int, int, int, int; … ON EXCEPTION SET SQLERR, ISAMERR RETURN 0, SQLERR, ISAMERR, USRERR; END EXCEPTION; LET USRERR = 1; -- do Something That May Raise Exception LET USRERR = 2; -- do Something Other That May Raise Exception … RETURN result, 0, 0, 0; END PROCEDURE 

Source: https://habr.com/ru/post/de472506/


All Articles