assert oder exceptions
-
ttestr schrieb:
DStefan schrieb:
Der gezeigte Ctor muss eine Exception werfen, falls filename kaputt ist. Danach können ggf. intern verwendete Funktionen assert() verwenden, um die Korrektheit des Dateinamens zuzusichern. Ob das nötig ist, ist eine andere Sache.
Stefan.
Könntest du mal bitte aufhören deine MEINUNG immer als ultimative Wahrheit hinzustellen? Das machst du dauernd und es nervt irgendwie bissel.
Tu ich nicht. Ich beziehe nur einen Standpunkt. Der ist für mich wahr, also warum sollte ich nicht so schreiben?
ttestr schrieb:
Der Ctor KANN eine Exception werfen.
Dann lies die von dir zitierten Sätze bitte noch einmal und setze statt "muss" "kann" ein. Macht das dann noch irgend wie Sinn? Ich finde nicht. Deswegen "muss".
Stefan.
-
ttestr schrieb:
@DStefan: Und was soll die Markierung jetzt groß zeigen? Der Code benutzt ein assert() um sicherzustellen, dass die Parameter valide sind. Genau wie in meinem Beispiel.
Es gibt tausende Beispiel.
Z.B. folgende Funktion:
void foo(const vector3f& v) { }
Der übergebene Vektor v MUSS normalisiert sein.Ich habe 2 Möglichkeiten:
assert( v.getLength() == 1);Vorteil: getLength() ist richtig teuer (Wurzel)
oder:
if(v.GetLength() == 1)
throw IllegalParameterException();Auch hier tendiere ich wieder stark zu assert().
Okay, wir ziehen das jetzt mal anders auf.
Zunächst solltest du unbedingt wissen, das assert() nur im Debug-Modus greift. Im Release-Modus existiert es praktisch gar nicht mehr und kann daher auch keinen Geschwindigkeitsverlust mit sich bringen.
Allerdings bedeutet das auch, dass assert() für produktiven Code nicht geeignet ist.
Stell dir also vor du schreibst ein Programm, um dem Benutzer die Möglichkeit zu geben ein Verzeichnis seiner Wahl zu löschen. Du könntest nun mit assert prüfen, ob das Verzeichnis existiert...
Pseudo-Code:
void deleteDir(const std::string& dir) { assert(existDir(dir)); // Verzeichnis existiert removeAllFilesInDir(dir); // Alles löschen... }
Nun kompilierst du diesen Code im Release-Modus. Was passiert wenn der Benutzer ein Verzeichnis eingibt, das nicht existiert. Nun, das Programm läuft Amok, da es sich darauf verlassen hat, das das Verzeichnis existiert.
Was ist also mit unserem assert() los? Es gibt es nicht mehr, da wir im Release-Modus kompilieren. Punkt.
Wie lösen wir das Problem? Wir werfen eine Exception, da diese für solche Zwecke da ist => Sie funktioniert auch im Release-Modus.
Dann kannst du bequem mit try die Exception abfangen, und dem Benutzer informieren, dass das Verzeichnis nicht existiert. Mit assert() wäre das auf diese Weise nicht möglich gewesen.
Ein anderes Beispiel. Du schreibst dir ein paar Funktionen, die von einer Verbindung lesen sollen.
std::string readString(Connection& connection) { assert(connection.isConnected()); // Die Verbindung existiert std::string buffer = connection.read(); return buffer; }
Du kompilierst dein Programm also im Debug-Modus und testest deine neue Funktion. Und auf einmal erscheint eine fette Fehlermeldung und das Programm stürzt ab. In der Fehlermeldung findest du die Zeilennummer und den Dateinamen, wo ein assert aufgerufen wurde.
Du schaust nach und wunderst dich. Und dann fällt es dir wieder ein. Du hast vergessen eine Verbindung aufzubauen... Wie blöd...
Den Fehler aber durch assert() innerhalb von einigen Sekunden gefunden.
Später kompilierst du im Release-Modus und bist dir sicher: Alles ist in Ordnung, die Verbindung steht, und auch einen Geschwindigkeitsverlust hast du nicht, da assert() nun nicht mehr ausgeführt wird
Das ist das ganze Geheimnis...
-
DStefan schrieb:
ttestr schrieb:
DStefan schrieb:
Der gezeigte Ctor muss eine Exception werfen, falls filename kaputt ist. Danach können ggf. intern verwendete Funktionen assert() verwenden, um die Korrektheit des Dateinamens zuzusichern. Ob das nötig ist, ist eine andere Sache.
Stefan.
Könntest du mal bitte aufhören deine MEINUNG immer als ultimative Wahrheit hinzustellen? Das machst du dauernd und es nervt irgendwie bissel.
Tu ich nicht. Ich beziehe nur einen Standpunkt. Der ist für mich wahr, also warum sollte ich nicht so schreiben?
ttestr schrieb:
Der Ctor KANN eine Exception werfen.
Dann lies die von dir zitierten Sätze bitte noch einmal und setze statt "muss" "kann" ein. Macht das dann noch irgend wie Sinn? Ich finde nicht. Deswegen "muss".
Stefan.Hängt vom Programm ab. Wenn ich nur die Liste der bisher stattgefundenen Schachpartien laden will, brauche ich auf nichts zu achten, wenn da keine Datei ist, bleibt die Liste eben leer. Trotzdem ist es ein Programmierfehler, dort einen leeren String zu übergeben. Also wohl doch eher ein Kann. Also ich kann's nicht wissen, es ist ttestrs Programm und er hat keine weiteren Details verraten. Außer dem "Kann".
-
BBBB schrieb:
Dann kannst du bequem mit try die Exception abfangen, und dem Benutzer informieren, dass das Verzeichnis nicht existiert.
Natürlich mit try und catch. Mein Fehler, sorry.
-
BBBB schrieb:
Stell dir also vor du schreibst ein Programm, um dem Benutzer die Möglichkeit zu geben ein Verzeichnis seiner Wahl zu löschen. Du könntest nun mit assert prüfen, ob das Verzeichnis existiert...
Nun kompilierst du diesen Code im Release-Modus. Was passiert wenn der Benutzer ein Verzeichnis eingibt, das nicht existiert. Nun, das Programm läuft Amok, da es sich darauf verlassen hat, das das Verzeichnis existiert.
Was ist also mit unserem assert() los? Es gibt es nicht mehr, da wir im Release-Modus kompilieren. Punkt.
Wie lösen wir das Problem? Wir werfen eine Exception, da diese für solche Zwecke da ist => Sie funktioniert auch im Release-Modus.Oder wir nehmen in der Benutzerschnittstelle keine leeren Strings an. Wenn wir die Fehler immer im Programm lassen, bis sie gezwungen werden, sich zu manifestieren, stört das erstens den Benutzer, dessen Dialog längst beendet war und der jetzt eine Messagebox um die Ohren geworfen kriegt, und zweitens müssen wir und zu viel Kram für eventuelle Nochmal-Versuche merken, und müssen an zu vielen anderen Stellen mit Folgeproblemen rechnen, die gar nicht auftauchen könnten, wenn keine schmutzigen Daten im Programm kursieren würden.
Ist eher meine Filosofie. Aber Java geht auch, man muß es nur konsequent durchziehen.
-
BBBB schrieb:
BBBB schrieb:
Dann kannst du bequem mit try die Exception abfangen, und dem Benutzer informieren, dass das Verzeichnis nicht existiert.
Natürlich mit try und catch. Mein Fehler, sorry.
Springt man da eigentlich aus dem catch-block mit goto in den try-block? Oder baut man eine Schleife um beide Blöcke und baut eine künstliche Steuervariable? Oder haut man die Blöcke in eine Funktion und überlädt den Returnwert mit einem Zusatzwert für NochmalMachen?
-
volkard schrieb:
BBBB schrieb:
Stell dir also vor du schreibst ein Programm, um dem Benutzer die Möglichkeit zu geben ein Verzeichnis seiner Wahl zu löschen. Du könntest nun mit assert prüfen, ob das Verzeichnis existiert...
Nun kompilierst du diesen Code im Release-Modus. Was passiert wenn der Benutzer ein Verzeichnis eingibt, das nicht existiert. Nun, das Programm läuft Amok, da es sich darauf verlassen hat, das das Verzeichnis existiert.
Was ist also mit unserem assert() los? Es gibt es nicht mehr, da wir im Release-Modus kompilieren. Punkt.
Wie lösen wir das Problem? Wir werfen eine Exception, da diese für solche Zwecke da ist => Sie funktioniert auch im Release-Modus.Oder wir nehmen in der Benutzerschnittstelle keine leeren Strings an. Wenn wir die Fehler immer im Programm lassen, bis sie gezwungen werden, sich zu manifestieren, stört das erstens den Benutzer, dessen Dialog längst beendet war und der jetzt eine Messagebox um die Ohren geworfen kriegt, und zweitens müssen wir und zu viel Kram für eventuelle Nochmal-Versuche merken, und müssen an zu vielen anderen Stellen mit Folgeproblemen rechnen, die gar nicht auftauchen könnten, wenn keine schmutzigen Daten im Programm kursieren würden.
Ist eher meine Filosofie. Aber Java geht auch, man muß es nur konsequent durchziehen.
Und wie stellst du dir das vor? Du hast meinetwegen einen sehr komplexen Dialog:
Verzeichnis: [Textbox] Parameter: [Textbox] Filter: [Textbox] (usw...) [OKAY] [ABBRECHEN]
Der Benutzer hat also ausversehen vergessen in einem benötigten Feld etwas einzugeben.
Und drückt also auf OKAY. Und was passiert? Nix. Er drückt wieder. Wieder nix... Frustration pur.
Stattdessen hätte man durch die Exception eine MessageBox erscheinen lassen, die besagt, dass das Feld nicht ausgefüllt wurde.
Und irgendwas merken muss man sich auch nicht. Schließlich leeren wir die Eingabefelder ja nicht... Sie sind noch immer gefüllt.
volkard schrieb:
BBBB schrieb:
BBBB schrieb:
Dann kannst du bequem mit try die Exception abfangen, und dem Benutzer informieren, dass das Verzeichnis nicht existiert.
Natürlich mit try und catch. Mein Fehler, sorry.
Springt man da eigentlich aus dem catch-block mit goto in den try-block? Oder baut man eine Schleife um beide Blöcke und baut eine künstliche Steuervariable? Oder haut man die Blöcke in eine Funktion und überlädt den Returnwert mit einem Zusatzwert für NochmalMachen?
Ich verstehe gar nicht, was du hier versuchst zu erzählen. Wenn man es ordentlich aufbaut, dann ist das alles gar kein Problem. Deine Variante hingegen ist zwar einfacher (
if (dir.empty()) return;
)umgesetzt aber einfach nicht schön.
-
DStefan schrieb:
Kommt drauf an:
Eine Assertion ist eine Zusicherung: Man weiß, dass etwas so oder so ist. Sollte die Assertion fehlschlagen, ist dies ein Hinweis auf einen internen Fehler, etwa darauf, dass eine Klasseninvariante nicht einghalten wurde. Konsequenterweise wird assert() in der Release-Version deines Programms (wenn also NDEBUG definiert ist) ausgeschaltet.
Falls method() in der öffentlichen Schnittstelle deiner Klasse verfügbar ist, weißt du nicht, dass p im Wertebereich liegt. Deshalb würde ich in diesem Fall nicht assert() verwenden, sondern tatsächlich eine passende Exception werfen.
Das ist eine Sichtweise, nämlich deine.
Man kann aber genauso andersrum argumentieren, nämlich dass falscher Input grundsätzlich nur mit assert() abgesichert werden sollte. Auch bzw. gerade bei öffentlichen Schnittstellen.
Das IMO wichtigste Argument ist z.B., dass Exception Werfen zum Misbrauch verleitet. "Brauch ich ja eh nicht checken, weil XYZ() wirft eh eine Exception wenns nicht passt."
In einigen Fällen ist das IMO OK, in anderen ... eher nicht.Beispiel für OK wäre z.B. eine "File not found" Exception.
Beispiel für nicht OK wäre z.B. eine Exception, wenn man bei std::for_each (oder anderen Funktionen die eine Iterator-Range nehmen) überprüft, ob der "begin" Iterator auch <= dem "end" Iterator ist (was natürlich nur mit Random Access Iteratoren geht, aber DAS wäre ja nicht das Problem).
Ein zweites (weniger wichtiges) Argument wäre, dass solche Tests manchmal unnötig Performance brauchen, und daher im Release nicht stattfinden sollten.
DStefan schrieb:
Tu ich nicht. Ich beziehe nur einen Standpunkt. Der ist für mich wahr, also warum sollte ich nicht so schreiben?
Ich fand das auch etwas anmassend/aufdringlich/absolut formuliert.
Vor allem der erste Paragraph ("Man weiß...").Mal ganz abgesehen davon, dass man eswas was man wirklich wüsste, nicht asserten müsste. Gelle
Asserten tut man Sachen die "so sein müssen", damit das Programm funktioniert. Und das trifft nunmal auch auf Parameter zu. Die müssen auch "gültig" sein, damit ein Programm funktioniert. Demnach spricht IMO nix dagegen die zu asserten.
p.S.: klingt das jetzt genauso anmassend/aufdringlich/absolut? Hoffe nicht
-
DStefan schrieb:
Auf keinen Fall! Denn du kannst nicht zusichern, dass filename.empty() false ist. Du kannst dir nicht sicher sein. Und dass an anderer Stelle sowieso ein Fehler geworfen wird, ist meines Erachtens ein ziemlich schwaches Argument.
Der gezeigte Ctor muss eine Exception werfen, falls filename kaputt ist. Danach können ggf. intern verwendete Funktionen assert() verwenden, um die Korrektheit des Dateinamens zuzusichern. Ob das nötig ist, ist eine andere Sache.
Aber es ist doch inkonsequent, leere Strings als einzelnen Fall zu behandeln. Da kann man gerade so gut Strings mit Zeichen, die als Dateinamen nicht erlaubt sind, prüfen. Wieso sollte es ein schwaches Argument sein, wenn woanders sowieso ein Fehler geworfen wird? So viel gewinnt man nicht durch die zusätzliche Fallunterscheidung, zumal ein leerer String normalerweise sehr selten übergeben wird, zumindest viel seltener als ein nicht existierender Dateiname.
DStefan schrieb:
Der Vollständigkeit halber sollte man wohl anmerken, dass es [assert bei Klassenschnittstellen] auch in den Standard-Bibliotheken selten so gehandhabt wird. Da hat man dann einfach undefined behavior, wenn man nicht brav ist.
Nein, das stimmt nicht. Schau dir doch mal die MSVC++-STL-Implementierung an, da werden fast alle möglichen Fehler mit Assertions behandelt (das geht von Indexzugriff über inkompatible Iteratoren bis hin zu
front()
bei leeren Containern).
-
BBBB schrieb:
Ich verstehe gar nicht, was du hier versuchst zu erzählen.
Ich auch nicht. So, wie Du es machst, gehts auch. Ich habe in der Oberfläche noch den konkreten Text "Das Feld für den Dateinamen darf nicht leer sein.", den ich dem armen User aber auch wohl per Messagebox an den Kopf knallen kann. Oder im Dialogfeld steht "<Hier Dateiname eingeben>" und der OK-Knopf ist deaktiviert. Oder es erscheint nach falschem OK so eine gelbe Sprechblase...
-
@volkard
Jopp.
Falscher User-Input ist sowieso was gänzlich anderes als falscher User-Programmer-Input.
-
-edit-
hat sich erledigt