Vorwort
Dieser Artikel ist eine Reaktion auf den Artikel: Was passiert mit der Fehlerbehandlung in C ++ 2a? Nach jedem Absatz bekam ich einen Juckreiz, geheilte Wunden öffneten sich und begannen zu bluten. Vielleicht nehme ich das, was geschrieben steht, zu sehr in mein Herz. Aber ich möchte nur über die Kurzsichtigkeit und den Analphabetismus heulen, die C ++ - Programmierer im 21. Jahrhundert zeigen. Und nicht einmal am Anfang.
Fangen wir an.
Klassifizierung
Herkömmlicherweise können alle fehlerhaften Situationen im Programm in zwei große Gruppen unterteilt werden:
- Schwerwiegende Fehler.
- Keine schwerwiegenden oder erwarteten Fehler.
Ich werde jetzt Fehler finden. Aber fatale Fehler - sie werden auch gewissermaßen erwartet. Wir erwarten, dass Gedächtnisreisen oft zu einem Sturz führen, aber möglicherweise nicht dazu. Und das wird erwartet, nicht wahr? Wenn eine Klassifizierung eingeführt wird, ist es immer möglich, sie auf Konsistenz zu überprüfen.
Dies ist jedoch ein häufiger subtiler Fehler.
Schauen wir uns die schwerwiegenden Fehler an.
Division durch 0 . Ich frage mich, warum dieser Fehler schwerwiegend ist. Ich würde in diesem Fall gerne eine Ausnahme auslösen und sie zur weiteren Verarbeitung abfangen. Warum ist sie tödlich? Warum wird mir ein bestimmtes Verhalten meines eigenen Programms auferlegt, und ich kann es in keiner Weise beeinflussen? Geht es in C ++ nicht um Flexibilität und darum, dass die Sprache dem Programmierer zugewandt ist? Obwohl ...
Nullzeiger-Dereferenzierung . Ich erinnere mich sofort an Java, es gibt eine NullPointerException
, die behandelt werden kann. Es gibt auch eine NullPointerException
Poco-Bibliothek! Warum wiederholen Standardentwickler mit der Hartnäckigkeit des Taubstummen das gleiche Mantra?
Warum habe ich dieses Thema im Allgemeinen begonnen? Es ist sehr wichtig und zeigt nur das Verständnis des Entwicklers für die Fehlerbehandlung. Hier geht es nicht um die Fehlerbehandlung an sich, sondern um den Auftakt zu einer wichtigen Aktion. Es geht immer um die Zuverlässigkeit der Anwendung, um Fehlertoleranz und manchmal, bei den seltensten und am stärksten gefährdeten, würde ich sogar sagen, um gefährdete Programmtypen, um transaktionales und konsistentes Verhalten.
In dieser Hinsicht sehen alle Streitigkeiten über das Teilen durch Null und das Dereferenzieren von Zeigern wie ein Kampf der Vögel um eine Brotkrume aus. Sicher ein wichtiger Prozess. Aber nur aus Sicht der Vögel.
Kehren wir zur Aufteilung in Fatalismus und dessen Abwesenheit zurück ... Ich beginne mit einer einfachen Frage: Wenn ich über das Netzwerk falsche Daten erhalten habe, ist dies ein schwerwiegender Fehler?
Einfache und richtige Antwort: hängt davon ab. Es ist klar, dass dies in den meisten Fällen kein schwerwiegender Fehler ist und alle über das Netzwerk empfangenen Daten validiert und 4xx zurückgegeben werden müssen, wenn die Daten falsch sind. Gibt es Fälle, in denen Sie abstürzen müssen? Und stürzte mit wildem Heulen ab, so dass zum Beispiel SMS kommen würde. Ja und nicht einer.
Es gibt. Ich kann ein Beispiel aus meinem Fachgebiet geben: einen verteilten Konsensalgorithmus. Ein Knoten erhält eine Antwort, die einen Hash aus einer Änderungskette von einem anderen Knoten enthält. Und dieser Hash unterscheidet sich von dem lokalen. Dies bedeutet, dass ein Fehler aufgetreten ist und die weitere Ausführung einfach gefährlich ist: Die Daten können abweichen, wenn nicht bereits. Dies geschieht, wenn die Verfügbarkeit eines Dienstes weniger wichtig ist als seine Konsistenz. In diesem Fall müssen wir fallen und brüllen, damit jeder herumhört. Das heißt, Wir haben Daten über das Netzwerk erhalten, sie haben validiert und sind gefallen. Für uns ist dieser Fehler nirgends tödlicher. Wird dieser Fehler erwartet? Ja, wir haben den Code mit Validierung geschrieben. Es ist dumm, das Gegenteil zu sagen. Nur wollen wir das Programm danach nicht mehr fortsetzen. Manueller Eingriff erforderlich, Automatisierung funktionierte nicht.
Wahl des Fatalismus
Die offensichtlichste Sache bei der Fehlerbehandlung ist die Entscheidung, was fatal ist und was nicht. Aber jeder Programmierer stellt sich diese Frage während der gesamten Entwicklungsaktivität. Daher beantwortet es sich irgendwie auf diese Frage. Die richtige Antwort kommt aus irgendeinem Grund aus der Praxis.
Dies ist jedoch nur der sichtbare Teil des Eisbergs. In den Tiefen gibt es eine viel monströsere Frage. Um die volle Tiefe zu verstehen, müssen Sie eine einfache Aufgabe festlegen und versuchen, sie zu beantworten.
Herausforderung . Machen Sie einen Rahmen für etwas.
Alles ist einfach. Wir bilden zum Beispiel einen Rahmen für die Netzwerkinteraktion. Oder JSON analysieren. Oder im schlimmsten Fall XML. Die Frage stellt sich sofort: Aber wenn ein Fehler aus dem Socket auftritt - ist es ein schwerwiegender Fehler oder nicht? Um es zu paraphrasieren: Soll ich eine Ausnahme auslösen oder einen Fehler zurückgeben? Ist das eine Ausnahmesituation oder nicht? Oder kann std::optional
? Oder eine Nonne? (^ 1)
Die paradoxe Schlussfolgerung ist, dass der Rahmen selbst diese Frage nicht beantworten kann. Nur der Code, der es verwendet, weiß es. Aus diesem Grund verwendet die meiner Meinung nach ausgezeichnete Bibliothek boost.asio beide Optionen. Abhängig von den persönlichen Vorlieben des Autors des Codes und der angewandten Logik kann die eine oder andere Methode zur Fehlerbehandlung gewählt werden. Anfangs war mir dieser Ansatz etwas peinlich, aber im Laufe der Zeit war ich von der Flexibilität dieses Ansatzes durchdrungen.
Dies ist jedoch nicht alles. Das Schlimmste kommt. Hier schreiben wir Anwendungscode, aber es scheint uns, dass er angewendet wird. Für einen anderen Code auf höherer Ebene ist unser Code eine Bibliothek. Das heißt, Die Unterteilung in Anwendungs- / Bibliothekscode (Framework usw.) ist eine reine Konvention, die vom Grad der Wiederverwendung von Komponenten abhängt. Sie können immer etwas oben anschrauben und der Anwendungscode wird nicht mehr so sein. Und dies bedeutet sofort, dass die Wahl, was gültig ist und was nicht, bereits durch den verwendeten und nicht verwendeten Code entschieden wird.
Wenn wir zur Seite springen, stellt sich heraus, dass es manchmal nicht einmal möglich ist zu verstehen, wer wen benutzt. Das heißt, Komponente A kann Komponente B und Komponente B Komponente A (^ 2) verwenden. Das heißt, Wer bestimmt, was passieren wird, ist überhaupt nicht klar.
Gewirr entwirren
Wenn man sich all diese Schande ansieht, stellt sich sofort die Frage: Wie soll man damit leben? Was zu tun ist? Welche Richtlinien sollten Sie wählen, um nicht in Abwechslung zu ertrinken?
Dazu ist es hilfreich, sich umzuschauen und zu verstehen, wie solche Probleme an anderen Stellen gelöst werden. Man muss jedoch mit Bedacht suchen: Es ist notwendig, "Briefmarkensammeln" von Komplettlösungen zu unterscheiden.
Was ist Briefmarkensammeln? Dies ist ein Sammelbegriff, was bedeutet, dass wir ein Ziel ausgetauscht haben, aber etwas anderes. Zum Beispiel: Wir hatten das Ziel, unsere Lieben anzurufen und mit ihnen zu kommunizieren. Und wir haben einmal ein teures Spielzeug gekauft, weil "modisch" und "schön" (^ 3). Ist das bekannt? Denken Sie, dass das Programmierern nicht passiert? Schmeicheln Sie sich nicht.
Fehlerbehandlung ist nicht das Ziel. Immer wenn wir über Fehlerbehandlung sprechen, kommen wir sofort zum Stillstand. Weil es ein Weg ist, ein Ziel zu erreichen. Das ursprüngliche Ziel ist es, unsere Software zuverlässig, einfach und verständlich zu machen. Es sind diese Ziele, die gesetzt und immer eingehalten werden müssen. Und Fehlerbehandlung ist Bullshit, der die Diskussion nicht wert ist. Ich möchte eine Ausnahme machen - aber für die Gesundheit! Rückgabe eines Fehlers - gut gemacht! Willst du eine Monade? Herzlichen Glückwunsch, Sie haben die Illusion des Fortschritts geschaffen, aber nur in Ihrem eigenen Kopf (^ 4).
Dann wollte ich schreiben, wie man es richtig macht, aber ich habe bereits geschrieben. Die Wunden heilten und hörten auf zu bluten. Kurz gesagt, die Tipps sind:
- In Komponenten mit klaren Grenzen trennen.
- Beschreiben Sie an den Grenzen, was und wie es fliegen kann. Es ist wünschenswert, dass es einheitlich ist. Aber viel wichtiger ist.
- Machen Sie es einfach, Fehler im Code zu behandeln, der sie verwendet.
- Wenn etwas intern verarbeitet werden kann, ohne den Benutzercode zu laden, halten Sie es nicht heraus. Je weniger Fehler ein Benutzer behandeln muss, desto besser.
- Respektiere deinen User, sei kein Arschloch! Schreiben Sie intuitive Schnittstellen mit dem erwarteten Verhalten, damit er keine Kommentare lesen und schwören muss.
Der 5. Rat ist der wichtigste, weil er kombiniert die ersten vier.
PS In meiner Kindheit war ich immer neugierig auf den Ameisenhaufen. Tausende Ameisen, jeder macht etwas, kriecht über sein Geschäft. Der Prozess läuft. Jetzt schaue ich auch mit Interesse zu. Auch hinter dem Ameisenhaufen. Wo Tausende von Menschen ihr kleines Ding machen. Ich kann ihnen viel Glück in ihrem harten Geschäft wünschen!
^ 1: Die Leute lieben modische Dinge. Als alle genug gespielt hatten, wachten C ++ - Programmierer auf und dann drehte sich alles um.
^ 2: Dies kann passieren, wenn es in Komponente B mehrere Abstraktionen gibt, die sie verbinden. Siehe Umkehrung der Kontrolle .
^ 3: Und am nächsten Tag stürzte bam und der Bildschirm ab.
^ 4: Ich bin nicht gegen Monaden, ich bin gegen das Ansaugen, wie, schau, hier ist die Monade, d.h. Monoid in der monoidalen Kategorie der Endofunktoren! Applaus und zustimmende Nicken sind zu hören. Und irgendwo weit, weit, kaum hörbar, jemand Orgasmen.