Googles Go-Sprache hat keine Ausnahmen als Design-Wahl, und Linus von Linux hat Ausnahmen Mist genannt. Warum?
Googles Go-Sprache hat keine Ausnahmen als Design-Wahl, und Linus von Linux hat Ausnahmen Mist genannt. Warum?
Antworten:
Ausnahmen machen es wirklich einfach, Code zu schreiben, bei dem eine ausgelöste Ausnahme Invarianten aufbricht und Objekte in einem inkonsistenten Zustand belässt. Sie zwingen Sie im Wesentlichen dazu, sich daran zu erinnern, dass fast jede Aussage, die Sie machen, möglicherweise werfen und richtig damit umgehen kann. Dies kann schwierig und kontraintuitiv sein.
Betrachten Sie so etwas als einfaches Beispiel:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Unter der Annahme , das FrobManager
wird delete
das FrobObject
, das sieht OK, nicht wahr? Oder vielleicht auch nicht ... Stellen Sie sich dann vor, ob entweder FrobManager::HandleFrob()
oder operator new
eine Ausnahme auslöst. In diesem Beispiel wird das Inkrement von m_NumberOfFrobs
nicht zurückgesetzt. Daher wird jeder, der diese Instanz von verwendet Frobber
, ein möglicherweise beschädigtes Objekt haben.
Dieses Beispiel mag dumm erscheinen (ok, ich musste mich ein wenig strecken, um eines zu konstruieren :-)), aber das Wichtigste ist, dass ein Programmierer nicht ständig an Ausnahmen denkt und dafür sorgt, dass jede Permutation des Zustands gerollt wird Zurück, wenn es Würfe gibt, geraten Sie auf diese Weise in Schwierigkeiten.
Als Beispiel können Sie sich das so vorstellen, wie Sie an Mutexe denken. In einem kritischen Abschnitt verlassen Sie sich auf mehrere Anweisungen, um sicherzustellen, dass Datenstrukturen nicht beschädigt werden und andere Threads Ihre Zwischenwerte nicht sehen können. Wenn eine dieser Aussagen nicht zufällig ausgeführt wird, geraten Sie in eine Welt voller Schmerzen. Nehmen Sie nun Sperren und Parallelität weg und überlegen Sie sich jede Methode so. Stellen Sie sich jede Methode als eine Transaktion von Permutationen für den Objektstatus vor, wenn Sie so wollen. Zu Beginn Ihres Methodenaufrufs sollte das Objekt einen sauberen Zustand haben, und am Ende sollte es auch einen sauberen Zustand geben. Dazwischen kann die Variable foo
nicht mit übereinstimmenbar
, aber Ihr Code wird das irgendwann korrigieren. Ausnahmen bedeuten, dass jede Ihrer Aussagen Sie jederzeit unterbrechen kann. Bei jeder einzelnen Methode liegt es an Ihnen, es richtig zu machen und in diesem Fall einen Rollback durchzuführen oder Ihre Operationen so zu ordnen, dass Würfe keinen Einfluss auf den Objektstatus haben. Wenn Sie etwas falsch machen (und es ist leicht, diese Art von Fehler zu machen), sieht der Anrufer Ihre Zwischenwerte.
Methoden wie RAII, die C ++ - Programmierer gerne als ultimative Lösung für dieses Problem erwähnen, tragen wesentlich dazu bei, sich davor zu schützen. Aber sie sind keine Silberkugel. Es stellt sicher, dass Sie Ressourcen für einen Wurf freigeben, befreit Sie jedoch nicht davon, über eine Beschädigung des Objektstatus nachzudenken und Aufrufer, die Zwischenwerte sehen. Für viele Menschen ist es daher einfacher, mit Ausnahme des Codierungsstils keine Ausnahmen zu sagen . Wenn Sie die Art des Codes einschränken, den Sie schreiben, ist es schwieriger, diese Fehler einzuführen. Wenn Sie dies nicht tun, ist es ziemlich einfach, einen Fehler zu machen.
Es wurden ganze Bücher über ausnahmesichere Codierung in C ++ geschrieben. Viele Experten haben es falsch verstanden. Wenn es wirklich so komplex ist und so viele Nuancen hat, ist das vielleicht ein gutes Zeichen dafür, dass Sie diese Funktion ignorieren müssen. :-)
Der Grund dafür, dass Go keine Ausnahmen hat, wird in den häufig gestellten Fragen zum Go-Sprachdesign erläutert:
Ausnahmen sind eine ähnliche Geschichte. Es wurde eine Reihe von Entwürfen für Ausnahmen vorgeschlagen, die jedoch die Sprache und die Laufzeit erheblich komplexer machen. Ausnahmen umfassen naturgemäß Funktionen und vielleicht sogar Goroutinen; Sie haben weitreichende Auswirkungen. Es gibt auch Bedenken hinsichtlich der Auswirkungen, die sie auf die Bibliotheken haben würden. Sie sind per Definition außergewöhnlich, aber die Erfahrung mit anderen Sprachen, die sie unterstützen, zeigt, dass sie tiefgreifende Auswirkungen auf die Bibliotheks- und Schnittstellenspezifikation haben. Es wäre schön, ein Design zu finden, das es ihnen ermöglicht, wirklich außergewöhnlich zu sein, ohne häufige Fehler zu ermutigen, sich in einen speziellen Kontrollfluss zu verwandeln, den jeder Programmierer kompensieren muss.
Ausnahmen bleiben wie bei Generika ein offenes Thema.
Mit anderen Worten, sie haben noch nicht herausgefunden, wie Ausnahmen in Go auf eine Weise unterstützt werden können, die sie für zufriedenstellend halten. Sie sagen nicht, dass Ausnahmen per se schlecht sind ;
UPDATE - Mai 2012
Die Go-Designer sind jetzt vom Zaun heruntergeklettert. Ihre FAQ sagt jetzt Folgendes:
Wir glauben, dass das Koppeln von Ausnahmen an eine Kontrollstruktur, wie in der Try-Catch-Endlich-Sprache, zu verschlungenem Code führt. Außerdem werden Programmierer dazu ermutigt, zu viele normale Fehler, z. B. das Nichtöffnen einer Datei, als außergewöhnlich zu kennzeichnen.
Go verfolgt einen anderen Ansatz. Für eine einfache Fehlerbehandlung machen es die mehrwertigen Rückgaben von Go einfach, einen Fehler zu melden, ohne den Rückgabewert zu überladen. Ein kanonischer Fehlertyp in Verbindung mit den anderen Funktionen von Go macht die Fehlerbehandlung angenehm, unterscheidet sich jedoch erheblich von der in anderen Sprachen.
Go verfügt außerdem über einige integrierte Funktionen zum Signalisieren und Wiederherstellen von wirklich außergewöhnlichen Bedingungen. Der Wiederherstellungsmechanismus wird nur ausgeführt, wenn der Status einer Funktion nach einem Fehler heruntergefahren wird. Dies reicht aus, um eine Katastrophe zu bewältigen, erfordert jedoch keine zusätzlichen Kontrollstrukturen und kann bei guter Verwendung zu einem sauberen Fehlerbehandlungscode führen.
Weitere Informationen finden Sie im Artikel Aufschieben, Panik und Wiederherstellen.
Die kurze Antwort lautet also, dass sie es mit der mehrwertigen Rückgabe anders machen können. (Und sie haben sowieso eine Form der Ausnahmebehandlung.)
... und Linus von Linux Ruhm hat Ausnahmen Mist genannt.
Wenn Sie wissen möchten, warum Linus Ausnahmen für Mist hält, ist es am besten, nach seinen Schriften zu diesem Thema zu suchen. Das einzige, was ich bisher aufgespürt habe, ist dieses Zitat, das in ein paar E-Mails unter C ++ eingebettet ist :
"Die ganze Sache mit der Behandlung von C ++ - Ausnahmen ist grundlegend kaputt. Sie ist besonders für Kernel kaputt."
Sie werden feststellen, dass es sich insbesondere um C ++ - Ausnahmen handelt und nicht um Ausnahmen im Allgemeinen. (Und C ++ - Ausnahmen haben anscheinend einige Probleme, die es schwierig machen, sie richtig zu verwenden.)
Mein Fazit ist, dass Linus Ausnahmen (im Allgemeinen) überhaupt nicht "Mist" genannt hat!
Ausnahmen sind an sich nicht schlecht, aber wenn Sie wissen, dass sie häufig auftreten werden, können sie in Bezug auf die Leistung teuer sein.
Als Faustregel gilt, dass Ausnahmen außergewöhnliche Bedingungen kennzeichnen sollten und dass Sie sie nicht zur Steuerung des Programmflusses verwenden sollten.
Ich bin nicht einverstanden mit "nur in einer Ausnahmesituation Ausnahmen werfen". Während im Allgemeinen wahr, ist es irreführend. Ausnahmen sind Fehlerbedingungen (Ausführungsfehler).
Unabhängig von der verwendeten Sprache erhalten Sie eine Kopie der Framework Design Guidelines : Konventionen, Redewendungen und Muster für wiederverwendbare .NET-Bibliotheken (2. Ausgabe). Das Kapitel über das Auslösen von Ausnahmen ist ohne Peer. Einige Zitate aus der ersten Ausgabe (die zweite bei meiner Arbeit):
Es gibt Seiten mit Hinweisen zu den Vorteilen von Ausnahmen (API-Konsistenz, Auswahl des Speicherorts des Fehlerbehandlungscodes, verbesserte Robustheit usw.). Es gibt einen Abschnitt zur Leistung, der mehrere Muster enthält (Tester-Doer, Try-Parse).
Ausnahmen und Ausnahmebehandlung sind nicht schlecht. Wie jede andere Funktion können sie missbraucht werden.
Aus der Sicht von Golang hält das Fehlen einer Ausnahmebehandlung den Kompilierungsprozess einfach und sicher.
Aus der Sicht von Linus verstehe ich, dass es beim Kernel-Code ALLES um Eckfälle geht. Es ist also sinnvoll, Ausnahmen abzulehnen.
Ausnahmen sind im Code sinnvoll, wenn es in Ordnung ist, die aktuelle Aufgabe auf den Boden zu legen, und wenn allgemeiner Fallcode wichtiger ist als die Fehlerbehandlung. Sie erfordern jedoch die Codegenerierung vom Compiler.
Zum Beispiel sind sie in den meisten übergeordneten, benutzerbezogenen Codes wie Web- und Desktop-Anwendungscode in Ordnung.
Ausnahmen an und für sich sind nicht "schlecht", es ist die Art und Weise, wie Ausnahmen manchmal behandelt werden, die dazu neigt, schlecht zu sein. Bei der Behandlung von Ausnahmen können verschiedene Richtlinien angewendet werden, um einige dieser Probleme zu beheben. Einige davon sind (ohne darauf beschränkt zu sein):
Option<T>
anstelle von null
heutzutage ein zurückgeben. Ich bin gerade in Java 8 eingeführt worden und habe einen Hinweis von Guava (und anderen) erhalten.
Typische Argumente sind, dass es keine Möglichkeit gibt zu sagen, welche Ausnahmen aus einem bestimmten Code herauskommen (abhängig von der Sprache), und dass sie zu sehr wie goto
s sind, was es schwierig macht, die Ausführung mental zu verfolgen.
http://www.joelonsoftware.com/items/2003/10/13.html
In dieser Frage besteht definitiv kein Konsens. Ich würde sagen, dass aus Sicht eines Hardcore-C-Programmierers wie Linus Ausnahmen definitiv eine schlechte Idee sind. Ein typischer Java-Programmierer befindet sich jedoch in einer völlig anderen Situation.
setjmp
/ longjmp
Zeug, was ziemlich schlecht ist.
Ausnahmen sind nicht schlecht. Sie passen gut zum RAII-Modell von C ++, was das eleganteste an C ++ ist. Wenn Sie bereits eine Menge Code haben, der nicht ausnahmesicher ist, sind sie in diesem Zusammenhang schlecht. Wenn Sie wirklich Low-Level-Software wie das Linux-Betriebssystem schreiben, sind sie schlecht. Wenn Sie Ihren Code gerne mit einer Reihe von Fehlerrückgabeprüfungen verunreinigen, sind diese nicht hilfreich. Wenn Sie keinen Plan für die Ressourcensteuerung haben, wenn eine Ausnahme ausgelöst wird (die von C ++ - Destruktoren bereitgestellt wird), sind sie fehlerhaft.
Ein guter Anwendungsfall für Ausnahmen ist also ....
Angenommen, Sie befinden sich in einem Projekt und jeder Controller (etwa 20 verschiedene Hauptcontroller) erweitert einen einzelnen Superklassen-Controller um eine Aktionsmethode. Dann macht jeder Controller eine Reihe von Dingen, die sich voneinander unterscheiden, und ruft in einem Fall die Objekte B, C, D und in einem anderen Fall F, G, D auf. Ausnahmen kommen hier in vielen Fällen zur Rettung, in denen es Tonnen von Rückkehrcodes gab und JEDER Controller anders damit umging. Ich habe den ganzen Code verprügelt, die richtige Ausnahme von "D" geworfen, ihn in der Superklassen-Controller-Aktionsmethode abgefangen und jetzt sind alle unsere Controller konsistent. Zuvor gab D für MEHRERE verschiedene Fehlerfälle null zurück, über die wir dem Endbenutzer Bescheid geben möchten, dies aber nicht konnten, und ich habe es nicht getan.
Ja, wir müssen uns um jede Ebene und alle Ressourcenbereinigungen / -lecks kümmern, aber im Allgemeinen hatte keiner unserer Controller Ressourcen, nach denen er bereinigen konnte.
Gott sei Dank hatten wir Ausnahmen, sonst hätte ich einen riesigen Refactor bekommen und zu viel Zeit mit etwas verschwendet, das ein einfaches Programmierproblem sein sollte.
Theoretisch sind sie wirklich schlecht. In einer perfekten mathematischen Welt kann es keine Ausnahmesituationen geben. Schauen Sie sich die funktionalen Sprachen an, sie haben keine Nebenwirkungen, so dass sie praktisch keine Quelle für außergewöhnliche Situationen haben.
Aber die Realität ist eine andere Geschichte. Wir haben immer Situationen, die "unerwartet" sind. Deshalb brauchen wir Ausnahmen.
Ich denke, wir können uns Ausnahmen als Syntaxzucker für ExceptionSituationObserver vorstellen. Sie erhalten nur Benachrichtigungen über Ausnahmen. Nichts mehr.
Ich denke, mit Go werden sie etwas einführen, das sich mit "unerwarteten" Situationen befasst. Ich kann mir vorstellen, dass sie versuchen werden, es als Ausnahmen weniger destruktiv und mehr als Anwendungslogik klingen zu lassen. Aber das ist nur meine Vermutung.
Das Ausnahmebehandlungsparadigma von C ++, das eine Teilbasis für das von Java und wiederum .net bildet, führt einige gute Konzepte ein, weist jedoch auch einige schwerwiegende Einschränkungen auf. Eine der wichtigsten Entwurfsabsichten der Ausnahmebehandlung besteht darin, Methoden zu ermöglichen, sicherzustellen, dass sie entweder ihre Nachbedingungen erfüllen oder eine Ausnahme auslösen, und sicherzustellen, dass alle Bereinigungen durchgeführt werden, die erforderlich sind, bevor eine Methode beendet werden kann. Leider bieten die Ausnahmebehandlungsparadigmen von C ++, Java und .net keine gute Möglichkeit, die Situation zu behandeln, in der unerwartete Faktoren die erwartete Bereinigung verhindern. Dies bedeutet wiederum, dass man entweder riskieren muss, dass alles zum Stillstand kommt, wenn etwas Unerwartetes passiert (der C ++ - Ansatz zur Behandlung einer Ausnahme tritt beim Abwickeln des Stapels auf).
Selbst wenn die Ausnahmebehandlung im Allgemeinen gut wäre, ist es nicht unangemessen, ein Ausnahmebehandlungsparadigma als inakzeptabel anzusehen, das kein gutes Mittel zur Behandlung von Problemen darstellt, die beim Aufräumen nach anderen Problemen auftreten. Das heißt nicht, dass ein Framework nicht mit einem Ausnahmebehandlungsparadigma entworfen werden kann, das selbst in Szenarien mit mehreren Fehlern ein vernünftiges Verhalten gewährleisten kann, aber noch keine der Top-Sprachen oder Frameworks kann dies.
Ich habe nicht alle anderen Antworten gelesen, daher wurde dies bereits erwähnt, aber ein Kritikpunkt ist, dass Programme in langen Ketten brechen, was es schwierig macht, Fehler beim Debuggen des Codes aufzuspüren. Wenn Foo () beispielsweise Bar () aufruft, das Wah () aufruft, das ToString () aufruft, sieht das versehentliche Verschieben der falschen Daten in ToString () wie ein Fehler in Foo () aus, eine fast völlig unabhängige Funktion.
Okay, langweilige Antwort hier. Ich denke, es hängt wirklich von der Sprache ab. Wenn eine Ausnahme zugewiesene Ressourcen zurücklassen kann, sollten sie vermieden werden. In Skriptsprachen verlassen sie nur Teile des Anwendungsflusses oder überspringen sie. Das ist an sich unangenehm, aber es ist eine akzeptable Idee, mit Ausnahmen fast fatalen Fehlern zu entkommen.
Für die Fehlersignalisierung bevorzuge ich generell Fehlersignale. Alles hängt von der API, dem Anwendungsfall und dem Schweregrad ab oder davon, ob die Protokollierung ausreicht. Außerdem versuche ich das Verhalten neu zu definieren und throw Phonebooks()
stattdessen. Die Idee ist, dass "Ausnahmen" oft Sackgassen sind, aber ein "Telefonbuch" hilfreiche Informationen zur Fehlerbehebung oder zu alternativen Ausführungsrouten enthält. (Noch kein guter Anwendungsfall gefunden, aber versuchen Sie es weiter.)
Für mich ist das Problem sehr einfach. Viele Programmierer verwenden den Ausnahmebehandler unangemessen. Mehr Sprachressourcen sind besser. Mit Ausnahmen umgehen zu können ist gut. Ein Beispiel für eine schlechte Verwendung ist ein Wert, der eine Ganzzahl sein muss, die nicht überprüft werden muss, oder eine andere Eingabe, die sich teilen und nicht auf eine Teilung von Null prüfen kann. Die Ausnahmebehandlung kann ein einfacher Weg sein, um mehr Arbeit und hartes Denken zu vermeiden, so der Programmierer Möglicherweise möchten Sie eine fehlerhafte Verknüpfung erstellen und eine Ausnahmebehandlung anwenden ... Die Aussage: "Ein professioneller Code schlägt NIEMALS fehl" kann illusorisch sein, wenn einige der vom Algorithmus verarbeiteten Probleme von Natur aus ungewiss sind. Vielleicht ist in den unbekannten Situationen von Natur aus gut der Ausnahmebehandler ins Spiel gekommen. Gute Programmierpraktiken sind umstritten.