Wo sollte man assert benutzen?
-
333 schrieb:
wo issn da ein fehler?!?!?!
Beim Zugriff auf arr[size] gibts einen Segfault; das "<=" sollte "<" heißen.
-
Praktisch sind asserts auch, wenn man keine Fehlerüberprüfung machen will, weil die eigentlich schon passiert sein sollte.
Zum Beispiel
// p darf nicht 0 sein void myfunc(const char * p) { assert(p); //... }
Es ist die Verantwortung des Benutzers dieser Funktion, daß p nicht 0 ist. Dennoch kann ich ihn mit diesem assert darauf hinweisen, daß er da nen Fehler gemacht hat. Gleichzeitig habe ich aber im Release keinen Laufzeitverlust.
Da die Funktion nicht mit 0 aufgerufen werden darf (steht im Kommentar) spare ich mir auch das abprüfen von selbigem. Sonst passiert es nur, daß auf jeder Abstraktionsebene alle Sachen nochmal überprüft werden und das kann sich mit der Zeit schonmal aufsummieren.MfG Jester
-
@Jester
aaaaah also jetzt versteh ich wo man assert anwenden kannbig thx!
-
@Jester: Genau diese Vorgehensweise finde ich jetzt aber nicht gut. Wenn ich erst eine Benutzereingabe mach und dann den String deiner Funktion übergebe, kommt es zu einem Fehler im Debug Build und zu undefiniertem Verhalten (oder in diesem Fall wahrscheinlich Acces Violoation) im Release-Build.
Asserts sollte man für Situationen verwenden, die Kraft eigener Programmlogik gar nicht möglich sind, deshalb verschwinden sie ja auch im Release-Build.
Benutzereingaben, Lesefehler oder sonstiges kommen im Release-Build auch vor, sowas sollte man mit Exceptions behandeln.
-
Hi,
ja, der Meinung bin ich auch. Eine Lib sollte z.B. auch im Releasebuild (also Lib im Releasebuild, so wie sie ja normalerweise ausgeliefert wird) alle Eingaben die von außen kommen als ungültig behandeln, bis sie ausdrücklich validiert wurden. Dazu gehört strenggenommen sogar, dass man nicht nur p != 0 prüft, sondern auch, dass p auf einen gültigen Speicherbereich zeigt, was IIRC aber nur mit plattformspezifischen Funktionen geht.
ChrisM
-
edit: habt ihr meinen Beitrag überhaupt gelesen? Es geht da nicht um eine Lib und die dort angebotenen High-Level-Operationen, sondern um die verschiedenen Abstraktionsebenen. Nur die oberste muß wirklich die Konsistenz prüfen.
@Optimizer: Du bist also für folgende Variante:
// unterste Abstraktionsebene void myfunc(const char * p) { if(!p) { //Fehlerbehandlung } } // mittlere Abstraktionsebene void performOperation(const Part & part) { if(! part konsistent) { Fehlerbehandlung } myfunc(part.pointer); // ... } // höchste Abstraktionsebene void complexOperation(const Object & o) { if(! o konsistent) { Fehlerbehandlung } performOperation(o.part); //... }
Die schönen redundanten Tests machen den Code sicher sehr performant.
Ansonsten verweise ich an dieser Stelle einfach mal auf Large Scale C++ Software Design von John Lakos, an dessen Beispiel obiges (das aus dem vorigen Beitrag) angelehnt ist.MfG Jester
-
Optimizer: Du hast Jesters Posting missverstanden. Wenn du in einem Programm eine Benutzereingabe machst, dann sollte idealerweise nur an einer einzigen Stelle geprüft werden, ob die Eingabe gültig ist. Nicht in jeder Funktion, die irgendwann mal die Eingabe bearbeitet. Nimm z.b. das:
double wurzel(double x) { if (x < 0) throw "Wurzel aus negativer Zahl??!"; else return sqrt(x); } int main() { double x; do { cout << "x >= 0 eingeben: "; cin >> x; } while (x < 0); cout << "Wurzel: " << wurzel(x); }
Wozu die zusätzliche Prüfung in wurzel()? Ein assert würde es auch tun, denn wurzel hat eben die Vorbedingung, dass das übergebene Argument nicht-negativ ist, und die Eingaberoutine sorgt auch dafür, dass nur gültige Eingaben angenommen werden. Ein assert würde in aggressiver Weise dafür sorgen, dass die Prüfung der Eingabe an einer Stelle konzentriert ist.
-
Moment mal, ich kann nicht hellsehen, ich weiss nicht, wo das her ist.
Wenn das jetzt so gemeint war, dass das nur ne kleine, private Hilfsmethode ist, die garantiert kein anderer füttert als ich, dann werd ich da sicher auch keine Exceptions benutzen.
Aber dann ist ja auch Kraft eigener Programmlogik sichergestellt, dass nichts passiert. Exceptions sollte man benutzen, sobald man keinen Einfluss darauf hat, was man bekommt und für mich hat das Stück Code so ausgeschaut, als wird da irgendwo mal ein String eingelesen.
-
@Optimizer
Benutzereingaben, Lesefehler oder sonstiges kommen im Release-Build auch vor, sowas sollte man mit Exceptions behandeln.
dagegen hat auch niemand etwas gesagt. Nur wenn ich eine Library oder allgemeine Funktion schreibe, weiss ich ja nicht, ob es sich um die Benutzereingabe etc. handeln könnte, deswegen hat hier dann der Programmierer selber vorsorge zu treffen.
assert dient eben nur dazu, dass dem Programmierer die Fehlersuche erleichtert wird und nicht irgend wo das Programm zusammen bricht plötzlich.
-
@Bashar: Ok, darauf können wir uns einigen.
-
Hi,
mag sein, ich bleibe trotzdem bei meiner Meinung, dass eine Libs alle Eingaben validieren sollte, auch wenn sie natürlich nicht wissen kann, ob das nicht schon von der ansteuernden Anwendung geschehen ist. Für mich ist die Lib ein eigenes Modul, das in sich geschlossen stabil laufen muss, auch wenn es ungültig angesteuert wird.
ChrisM
-
@ChrisM: Ich glaube, wir meinen alle das selbe. (Und das beinhaltet auch, dass alle Eingaben geprüft werden).
Mich hat nur Jesters Beispiel etwas verwirrt.
-
Jo, so war's auch gemeint. Wichtige Voraussetzung ist natürlich, daß die Vorbedingungen spezifiziert und dokumentiert sind. Und dann stelle ich mich irgendwie schon auf den Standpunkt, daß der Benutzer (damit meine ich den, der meine Funktion benutzt) dafür verantwortlich ist, daß er meine Funktion korrekt benutzt. Und um ihm das Leben etwas leichter zu machen helfe ich mit dem assert nach, damit er auch gleich merkt, wenn er sich nicht dran hält.
MfG Jester
-
Hi,
nein, ich habe Jester schon verstanden.
Trotzdem bin ich der Meinung, dass die Funktion ein definierbares Verhalten (z.B. Exception) zeigen sollte, wenn z.B. 0 übergeben wird, obwohl in der Dokumentation bzw. einem Kommentar steht, dass p nicht 0 sein darf.
EDIT: Eine andere Möglichkeit wäre es natürlich, es so zu machen, wie Microsoft bei DirectX und gleich noch Debugversionen der DLLs im SDK mitauszuliefern, die dann natürlich viele zusätzliche Sicherheitschecks beinhalten.
ChrisM
-
Wenn ich bestimmte Bedingungen fordere, dann muß ich auch davon ausgehen können, daß sie eingehalten werden, sonst habe ich in jedem Fall das Problem, daß ich redundante Prüfungen habe. Und mit den asserts wird die Eingabe ja wunderbar validiert, wer's falsch benutzt kriegt richtig eins auf den Deckel und muß sich drum kümmern es richtig zu machen.
MfG Jester
-
Hi,
ja, aber das assert() ist im Auslieferungsbuild ("release") ja nicht drin.
Lies mal oben mein Edit, das kam nach deinem Post eben. Können wir uns darauf einigen?
ChrisM
-
Jester schrieb:
Und dann stelle ich mich irgendwie schon auf den Standpunkt, daß der Benutzer (damit meine ich den, der meine Funktion benutzt) dafür verantwortlich ist, daß er meine Funktion korrekt benutzt.
Dann haben wir uns doch nicht richtig verstanden
Ich sag nochmal, wie ich es meine:
public: int bla(int x) { if (Argument ist ungültig) throw IllegalArgumentException(); assert(Ergebnis ist korrekt); // optional } private: int blubb() { assert(Argument ist gültig); }
-
Vielleicht sollten wir mal unterscheiden, welche Fehler es da geben kann.
Einmal die, das ein Pointer zum Beispiel nicht 0 sein darf. Das würde ich mit nem assert machen. Das ist sozusagen der Teil über den der Programmierer ne gute Kontrolle hat. Es handelt sich dabei eindeutig um einen Programmierfehler.
Es gibt aber auch semantische Fehler, das Objekt das übergeben wird erfüllt einige Eigenschaften nicht, die es aber haben muß, die sind aber semantischer Natur, nicht sowas einfaches wie Nullpointer... Dann werf ich ne Exception.
Denn der aufrufende Code weiß vielleicht garnicht, was er da weitergibt und außen werden möglicherweise verschiedene Teile produziert, von denen einige mit meiner Funktion zusammen arbeiten können, ander nicht.@ChrisM:
Wir sind hier aber bei Standard C++, da gibt's keine Dlls.
Jedenfalls ist meine Guideline: In den höheren Abstraktionsebenen Exceptions, weiter unten asserts.
-
Kann es sein, dass der springende Punkt die unterschiedliche Bedeutung von Benutzer ist? Für Jester ist der Benutzer der, der die Funktion aufruft und die Dokumentation (damit auch Vor- und Nachbedingungen) kennt. Für Optimizer scheint der Benutzer Joe, der dressierte Affe, zu sein, der immer erstmal jeglichen Nonsens eingibt ...
Es muss eine Instanz geben, die Joe's Dumm-Eingaben abfängt. Aber der Rest des Programms darf sich (per assert) drauf verlassen dürfen, dass die Eingabevalidierung richtig arbeitet.
-
Nein, der Benutzer ist ein anderer Programmierer.
Vielleicht ist ein Konstruktor ein gutes Beispiel, denn den benutzen andere und ich hab das nicht in der Hand. Ein Konstruktor für die Rationalklasse darf keinen Nenner mit dem Wert 0 akzeptieren. Jeder, der es versucht, kriegt als Strafe ein Exception zurückgeschmissen, aber (das ist IMO das wichtichtigste) er bekommt kein Rational-Objekt. Auch nicht im Release-Build meiner Rational-Lib.
Ich kann also davon ausgehen, dass kein Rational den Nenner 0 (außer Brüche können ihren Wert ändern) hat. Das ist Kraft eigener Programmlogik sichergestellt und in allen weiteren Methoden, die mit dem Bruch irgendwas sinnvolles machen könnten nochmal mit assert das prüfen.Meine Guideline: Etwas was ich nicht selber in der Hand habe mit Exceptions prüfen.