assert oder exceptions
-
ttestr schrieb:
Ein konkreter Fall bei mir:
Foo::Foo(const std::string filename) { ... }
Der Ctor wird die Datei laden. Was mache ich, wenn der Benutzer einen Leerstring "" übergibt? assert() oder exception?Für mich ganz klar: Exception.
Sollen wir abstimmen?Stefan.
-
DStefan schrieb:
ttestr schrieb:
@DStefan: Ich finde du siehst assert() bissi zu sehr als reine "interne" Absicherungsmaßnahme. Wenn du eine öffentliche Schnittstelle hast, dann haben viele Methoden klare Richtlinien. Wenn der Anwender die nicht erfüllt, dann ist das ein Programmierfehler, der IMO auch mit assert() behandelt werden kann.
Hm, aber ich seh schon, dass es eine klare Antwort nicht gibt. Für jemanden wie mir mit sehr, sehr wenig Erfahrung ist es halt oft schwer abzuwägen was das richtige ist:/
Nö, das seh nicht nur ich so, das ist common sense.
Kuckstu hier: de.wikipedia.org/wiki/Assertion_(Informatik)Stefan.
Dein Link bestätigt aber eher meine Aussage. Z.B. dieses Code Schnippsel:
int strlenChecked(char* s) { assert(s != NULL); return strlen(s); }
Das ist ja genau mein Fall. Der Parameter s hat einen falschen Wert => assert().
Spricht eigentlich irgendwas dagegen, alle Parameterchecks mit assert() ab zu frühstücken?
-
ttestr schrieb:
DStefan schrieb:
ttestr schrieb:
@DStefan: Ich finde du siehst assert() bissi zu sehr als reine "interne" Absicherungsmaßnahme. Wenn du eine öffentliche Schnittstelle hast, dann haben viele Methoden klare Richtlinien. Wenn der Anwender die nicht erfüllt, dann ist das ein Programmierfehler, der IMO auch mit assert() behandelt werden kann.
Hm, aber ich seh schon, dass es eine klare Antwort nicht gibt. Für jemanden wie mir mit sehr, sehr wenig Erfahrung ist es halt oft schwer abzuwägen was das richtige ist:/
Nö, das seh nicht nur ich so, das ist common sense.
Kuckstu hier: de.wikipedia.org/wiki/Assertion_(Informatik)Stefan.
Dein Link bestätigt aber eher meine Aussage. Z.B. dieses Code Schnippsel:
int strlenChecked(char* s) { assert(s != NULL); return strlen(s); }
Das ist ja genau mein Fall. Der Parameter s hat einen falschen Wert => assert().
Spricht eigentlich irgendwas dagegen, alle Parameterchecks mit assert() ab zu frühstücken?Och neee... Jetzt aber! Zitat:
Wikipedia schrieb:
Durch die Formulierung einer Zusicherung bringt der Entwickler eines Programms seine Überzeugung über bestimmte Bedingungen während der Laufzeit eines Programms zum Ausdruck und lässt sie Teil des Programms werden. Er trennt diese Überzeugungen von den normalen Laufzeitumständen ab und nimmt diese Bedingungen als stets wahr an. Abweichungen hiervon werden nicht regulär behandelt, damit die Vielzahl möglicher Fälle nicht die Lösung des Problems vereitelt [...]
Hervorhebung von mir.
Stefan.
-
ttestr schrieb:
Ein konkreter Fall bei mir:
Foo::Foo(const std::string filename) { ... }
Der Ctor wird die Datei laden. Was mache ich, wenn der Benutzer einen Leerstring "" übergibt? assert() oder exception?Die Funktion wirft eh eine FileNotFoundException, wenn das Laden nicht klappte. Das assert(!filename.empty()) ist nur ein zusätzlicher Schutz, um einen typischen Programmierfehler besser jagen zu können, der das Programm in keiner weise sicherer oder unsicherer macht. Klares assert.
-
DStefan schrieb:
Für mich ganz klar: Exception.
Sollen wir abstimmen?Aber nur, wenn fricky mitspielen darf.
-
ttestr schrieb:
DStefan schrieb:
ttestr schrieb:
@DStefan: Ich finde du siehst assert() bissi zu sehr als reine "interne" Absicherungsmaßnahme. Wenn du eine öffentliche Schnittstelle hast, dann haben viele Methoden klare Richtlinien. Wenn der Anwender die nicht erfüllt, dann ist das ein Programmierfehler, der IMO auch mit assert() behandelt werden kann.
Hm, aber ich seh schon, dass es eine klare Antwort nicht gibt. Für jemanden wie mir mit sehr, sehr wenig Erfahrung ist es halt oft schwer abzuwägen was das richtige ist:/
Nö, das seh nicht nur ich so, das ist common sense.
Kuckstu hier: de.wikipedia.org/wiki/Assertion_(Informatik)Stefan.
Dein Link bestätigt aber eher meine Aussage. Z.B. dieses Code Schnippsel:
int strlenChecked(char* s) { assert(s != NULL); return strlen(s); }
Das ist ja genau mein Fall. Der Parameter s hat einen falschen Wert => assert().
Spricht eigentlich irgendwas dagegen, alle Parameterchecks mit assert() ab zu frühstücken?Das der Parameter NULL ist, ist aber kein "echter" Fehler, sondern einfach Dummheit oder es war schon spät... Damit man sowas schneller findet nutzt man eben assert()... Wenn du hingegen eine Datei nicht öffnen kannst, weil sie schreibgeschützt ist oder nicht existiert dann ist das ein "richtiger" Fehler, den man zumindest im Konstruktor nur mit einer Exception vernünftig behandeln kann...
-
DStefan schrieb:
Wikipedia schrieb:
Durch die Formulierung einer Zusicherung bringt der Entwickler eines Programms seine Überzeugung über bestimmte Bedingungen während der Laufzeit eines Programms zum Ausdruck und lässt sie Teil des Programms werden. Er trennt diese Überzeugungen von den normalen Laufzeitumständen ab und nimmt diese Bedingungen als stets wahr an. Abweichungen hiervon werden nicht regulär behandelt, damit die Vielzahl möglicher Fälle nicht die Lösung des Problems vereitelt [...]
Hervorhebung von mir.
Stefan.Ahm. Ja. Ich gehe stets davon aus, daß der string nicht leer ist. Ich mache keine Anstalten, Abweichungen davon regulär zu behandeln.
-
@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().
-
Der Vorteil is natürlich ein Nachteil
-
volkard schrieb:
ttestr schrieb:
Ein konkreter Fall bei mir:
Foo::Foo(const std::string filename) { ... }
Der Ctor wird die Datei laden. Was mache ich, wenn der Benutzer einen Leerstring "" übergibt? assert() oder exception?Die Funktion wirft eh eine FileNotFoundException, wenn das Laden nicht klappte. Das assert(!filename.empty()) ist nur ein zusätzlicher Schutz, um einen typischen Programmierfehler besser jagen zu können, der das Programm in keiner weise sicherer oder unsicherer macht. Klares assert.
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.
Stefan.
-
volkard schrieb:
DStefan schrieb:
Wikipedia schrieb:
Durch die Formulierung einer Zusicherung bringt der Entwickler eines Programms seine Überzeugung über bestimmte Bedingungen während der Laufzeit eines Programms zum Ausdruck und lässt sie Teil des Programms werden. Er trennt diese Überzeugungen von den normalen Laufzeitumständen ab und nimmt diese Bedingungen als stets wahr an. Abweichungen hiervon werden nicht regulär behandelt, damit die Vielzahl möglicher Fälle nicht die Lösung des Problems vereitelt [...]
Hervorhebung von mir.
Stefan.Ahm. Ja. Ich gehe stets davon aus, daß der string nicht leer ist. Ich mache keine Anstalten, Abweichungen davon regulär zu behandeln.
Volkard, ganz im Ernst... Deine Aussagen verwirren einen Anfänger nur.
volkard schrieb:
Klares assert.
Ahja... Und dann:
volkard schrieb:
Ahm. Ja. Ich gehe stets davon aus, daß der string nicht leer ist. Ich mache keine Anstalten, Abweichungen davon regulär zu behandeln.
Achso? Sag doch einfach klipp und klar:
Ein assert ist hier überflüssig... Ich habe noch niemanden gesehen, der einen Leer-String an einen Konstruktor übergibt, der einen Dateinamen erwartet.
Und im Release greift assert sowieso nicht mehr...
-
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. Der Ctor KANN eine Exception werfen.
-
Zu bemerken ist noch, daß das Standad-assert für C++ nicht viel taugt. Da sollte man sich zum Beispiel ein neues schreiben, das den Debugger aufruft und dann eine AssertException wirft, die in der main() gefangen wird, damit alle Transaktionen unrolled werden und die Netzwerksachen fair beendet werden, statt den Gegner auf einen Timout laufen zu lassen.
-
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.