Für einige Sprachen (z. B. C ++) sollte ein Ressourcenleck kein Grund sein
C ++ basiert auf RAII.
Wenn Sie Code haben, der fehlschlagen, zurückgeben oder werfen kann (dh den meisten normalen Code), sollten Sie Ihren Zeiger in einen intelligenten Zeiger einschließen (vorausgesetzt, Sie haben einen sehr guten Grund, Ihr Objekt nicht auf einem Stapel zu erstellen).
Rückkehrcodes sind ausführlicher
Sie sind ausführlich und entwickeln sich zu etwas wie:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
Am Ende ist Ihr Code eine Sammlung identifizierter Anweisungen (ich habe diese Art von Code im Produktionscode gesehen).
Dieser Code könnte gut übersetzt werden in:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
Welche sauber separater Code und Fehlerverarbeitung, die kann sein , gute Sache.
Rückkehrcodes sind spröder
Wenn nicht eine obskure Warnung von einem Compiler vorliegt (siehe den Kommentar von "phjr"), können sie leicht ignoriert werden.
Nehmen wir bei den obigen Beispielen an, dass jemand vergisst, seinen möglichen Fehler zu behandeln (dies passiert ...). Der Fehler wird bei "Rückgabe" ignoriert und explodiert möglicherweise später (dh ein NULL-Zeiger). Das gleiche Problem wird mit Ausnahme nicht auftreten.
Der Fehler wird nicht ignoriert. Manchmal möchten Sie jedoch, dass es nicht explodiert ... Sie müssen also sorgfältig auswählen.
Rückkehrcodes müssen manchmal übersetzt werden
Nehmen wir an, wir haben folgende Funktionen:
- doSomething, das ein int namens NOT_FOUND_ERROR zurückgeben kann
- doSomethingElse, das einen Bool "false" zurückgeben kann (für fehlgeschlagen)
- doSomethingElseAgain, das ein Fehlerobjekt zurückgeben kann (sowohl mit __LINE__, __FILE__ als auch mit der Hälfte der Stapelvariablen.
- doTryToDoSomethingWithAllThisMess welche, na ja ... Verwenden Sie die oben genannten Funktionen und geben Sie einen Fehlercode vom Typ ... zurück.
Was ist die Art der Rückgabe von doTryToDoSomethingWithAllThisMess, wenn eine der aufgerufenen Funktionen fehlschlägt?
Rückkehrcodes sind keine universelle Lösung
Bediener können keinen Fehlercode zurückgeben. C ++ - Konstruktoren können das auch nicht.
Rückkehrcodes bedeuten, dass Sie Ausdrücke nicht verketten können
Die Folge des obigen Punktes. Was ist, wenn ich schreiben möchte:
CMyType o = add(a, multiply(b, c)) ;
Ich kann nicht, da der Rückgabewert bereits verwendet wird (und manchmal nicht geändert werden kann). Der Rückgabewert wird also zum ersten Parameter, der als Referenz gesendet wird ... oder nicht.
Ausnahmen werden eingegeben
Sie können für jede Art von Ausnahme verschiedene Klassen senden. Ressourcenausnahmen (dh nicht genügend Speicher) sollten leicht sein, aber alles andere kann so schwer wie nötig sein (ich mag die Java-Ausnahme, die mir den gesamten Stapel gibt).
Jeder Fang kann dann spezialisiert werden.
Verwenden Sie niemals catch (...), ohne erneut zu werfen
Normalerweise sollten Sie einen Fehler nicht verbergen. Wenn Sie den Fehler zumindest nicht erneut auslösen, protokollieren Sie ihn in einer Datei, öffnen Sie eine Nachrichtenbox, was auch immer ...
Ausnahme sind ... NUKE
Das Problem mit Ausnahme ist, dass eine Überbeanspruchung Code voller Versuche / Fänge erzeugt. Das Problem ist jedoch anderswo: Wer versucht, seinen Code mithilfe eines STL-Containers abzufangen? Diese Container können jedoch eine Ausnahme senden.
Lassen Sie in C ++ natürlich niemals eine Ausnahme einen Destruktor verlassen.
Ausnahme sind ... synchron
Stellen Sie sicher, dass Sie sie abfangen, bevor sie Ihren Thread auf die Knie zwingen oder sich in Ihrer Windows-Nachrichtenschleife verbreiten.
Die Lösung könnte darin bestehen, sie zu mischen?
Ich denke, die Lösung ist zu werfen, wenn etwas nicht passieren sollte. Und wenn etwas passieren kann, verwenden Sie einen Rückkehrcode oder einen Parameter, damit der Benutzer darauf reagieren kann.
Die einzige Frage ist also: "Was sollte nicht passieren?"
Dies hängt vom Vertrag Ihrer Funktion ab. Wenn die Funktion einen Zeiger akzeptiert, aber angibt, dass der Zeiger nicht NULL sein darf, ist es in Ordnung, eine Ausnahme auszulösen, wenn der Benutzer einen NULL-Zeiger sendet (die Frage ist in C ++, wann der Funktionsautor keine Referenzen verwendet hat von Zeigern, aber ...)
Eine andere Lösung wäre, den Fehler anzuzeigen
Manchmal ist Ihr Problem, dass Sie keine Fehler wollen. Die Verwendung von Ausnahmen oder Fehlerrückgabecodes ist cool, aber ... Sie möchten es wissen.
In meinem Job verwenden wir eine Art "Assert". Abhängig von den Werten einer Konfigurationsdatei wird unabhängig von den Debug- / Release-Kompilierungsoptionen Folgendes ausgeführt:
- Protokollieren Sie den Fehler
- Öffne eine Messagebox mit einem "Hey, du hast ein Problem"
- Öffnen Sie eine Nachrichtenbox mit der Meldung "Hey, Sie haben ein Problem, möchten Sie debuggen?"
Auf diese Weise kann der Benutzer sowohl beim Entwickeln als auch beim Testen das Problem genau bestimmen, wenn es erkannt wird, und nicht danach (wenn sich ein Code um den Rückgabewert kümmert oder innerhalb eines Catch).
Es ist einfach, Legacy-Code hinzuzufügen. Beispielsweise:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
führt eine Art Code ähnlich wie:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(Ich habe ähnliche Makros, die nur beim Debuggen aktiv sind).
Beachten Sie, dass die Konfigurationsdatei in der Produktion nicht vorhanden ist, sodass der Client das Ergebnis dieses Makros nie sieht. Es ist jedoch einfach, es bei Bedarf zu aktivieren.
Fazit
Wenn Sie mit Rückkehrcodes codieren, bereiten Sie sich auf einen Fehler vor und hoffen, dass Ihre Testfestung sicher genug ist.
Wenn Sie mit Ausnahme codieren, wissen Sie, dass Ihr Code fehlschlagen kann, und setzen normalerweise den Gegenfeuerfang an der ausgewählten strategischen Position in Ihrem Code. Aber normalerweise geht es in Ihrem Code mehr um "was es tun muss" als um "was ich fürchte".
Wenn Sie jedoch überhaupt codieren, müssen Sie das beste Tool verwenden, das Ihnen zur Verfügung steht. Manchmal lautet es "Verstecken Sie niemals einen Fehler und zeigen Sie ihn so schnell wie möglich an". Das Makro, das ich oben gesprochen habe, folgt dieser Philosophie.