Wo sollte man assert benutzen?
-
Optimizer schrieb:
(Außerdem nimmst du dem Aufrufer die Möglichkeit, den Fehler zu behandeln.)
Der Aufrufer muss den Fehler ja gar nicht erst machen. Wie ich schon sagte, wenn er nicht in der Lage ist, die Vorbedingungen zu überprüfen, warum dann eine Exception fangen, die nur seinen eigenen Programmierfehler anzeigt?
double y = arbitrary_value(), z; try { // also irgendwas ist mit der Funktion, aber ich les die Doku vielleicht morgen, oder nächste Woche z = wurzel(y); } catch(RadicandNegativeException& e) { // Ich hab keinen blassen (ehrlich!) was hier schiefgegangen sein könnte! }
Hältst du das für realistisch?
-
Ja mein Gott, im äußerst einfachen Falle einer Wurzelfunktion bietet es sich halt nicht an, die Exception zu behandeln. Mir ist doch sowieso egal, was mit der Exception passiert.
Hauptsache, meine Funktion liefert niemals irgendeinen Müll zurück.
-
Die Wurzel ist nur ein Beispiel. Das gilt doch für alles. Warum sollte ich, statt es richtig zu machen, eine Exception fangen? Exceptions setze ich ein, wenn die Fehlerursachen unkontrollierbar sind.
-
Ja, das ist deine Philosophie. Für mich sind Fehlerursachen auch unkontrollierbar, wenn ich mich darauf verlassen muss, dass andere meine Funktion richtig füttern.
Das mit der Performance ist eigentlich nur bei sowas simplen ein Problem, wenn ich bei einer richtig fetten Klasse mal einen Wert von außen annehme, wird er einmal durch die Methode geprüft und alle Methoden (die in der Regel private sind), die danach damit arbeiten, haben nur noch asserts, weil das dann etwas ist, wo ich durch meine Programmlogik sicherstellen kann, dass der Wert nicht ungültig ist.
Und wenn ich lauter fürchterlich komplizierte Berechnungen und vielleicht noch Grafikausgaben mache, dann ist doch das einmalige Prüfen wirklich kein Flaschenhals.Aber diese Diskussion führt wirklich zu nichts mehr jetzt, fürchte ich.
Das sind einfach unterschiedliche Design-Philosophien, meine ich.
-
Optimizer schrieb:
Ja, das ist deine Philosophie. Für mich sind Fehlerursachen auch unkontrollierbar, wenn ich mich darauf verlassen muss, dass andere meine Funktion richtig füttern.
Diese anderen (was eigentlich, Teammitglieder, oder Benutzer deiner Library?) arbeiten doch nicht im Release-Modus bzw. mit der Release-Version deiner Library. Die kriegen genauso die Assertions um die Ohren wie du.
-
Das machen aber scheinbar alle größeren Bibliotheken (DirectX zum Beispiel) so. DX wirft halt keine Exception, sondern gibt einen FAILED-Wert zurück (Managed DirectX throwt aber auch, wahrscheinlich ist der Rückgabewert nur wegen C noch so), aber der Punkt ist, dass in jedem Build die Argumente geprüft werden.
Ich verstehe auch nicht, was dagegen spricht. Argumente sind ja nicht immer statisch, die werden auch mal berechnet (vielleicht sogar aufgrund von Benutzereingaben). Sonst wär die Sache ja einfach.
-
Du setzt also deine Klassen mit System-APIs wie DX gleich.
-
Ich wende dasselbe Prinzip an, so wie es große Libs machen, wie ich es gelernt habe und wie ich es selber richtig finde, weil ich der Meinung bin, dass auch im Release-Build solche Prüfung stattfinden sollten.
Weil ich nicht will, dass meine Lib Unsinn macht, Unsinn zurückliefert oder undefiniertes Verhalten erzeugt. Das ist wie wenn ich ne Fernbedienung verkaufe und die geht nach einem Tastendruck kaputt. Und ich sag dann "haben sie die Bedienungsanleitung nicht gelesen? Sie müssen immer von unten dagegen drücken." Da bau ich das Ding lieber stabiler.Das sind zwei verschiedene Ansichten, ihr bevorzugt Performance, ich bevorzuge Sicherheit und locker die Sicherheit vielleicht dann nachträglich nochmal ein bisschen, wenn eine bestimmte Prüfung zu sehr auf die Performance drückt.
-
Dann prüfst du sicherlich auch alle Referenzen, die du bekommst, ob sie nicht verschleierte null Zeiger sind?
Verwendest niemals nicht konstante Referenzen, denn der Benutzer könnte ja mit einem const_cast undefiniertes Verhalten heraufbeschwören, oder wie?
Wenn jemand undefiniertes Verhalten will, dann kriegt er es auch, egal wie gut du deine lib absicherst.
-
Dann bau dir doch ein eigenes abschaltbares assert, was im Fehlerfall Exceptions wirft, anstatt das Programm abzubrechen. Und schalte ASSERTIONS_ON auch im Release nicht ab.
# ifdef ASSERTIONS_ON # define my_assert(COND) ((COND)?(void)0:throw AssertionViolation(__FILE__, __LINE__, #COND)) # else # define my_assert(COND) # endif class AssertionViolation : public std::logic_error { public: AssertionViolation(const char * file, unsigned int line, const char * condition); };
-
davie schrieb:
Dann prüfst du sicherlich auch alle Referenzen, die du bekommst, ob sie nicht verschleierte null Zeiger sind?
Nein, das prüfen die Betriebssysteme für die ich programmiere.
Ihr glaubt immer, ich geb mich mit der halben Performance zufrieden, nur damit ich die Prüfungen habe, aber ihr überschätzt die paar Tests ganz extrem. Das sind ein paar Methoden, die man von außen benutzen kann und die rechnen jetzt irgendein kompliziertes Zeug aus und rufen weitere interne Methoden auf - da macht doch eine simple if-Abfrage nichts mehr aus (sagt zumindest auch mein Profiler und QueryPerformanceCounter()).
Also, was zum Geier spricht jetzt gegen ein paar Abfragen?@Bashar, das hat nichts mit Exceptions an sich zu tun, sondern ich verwende sie deshalb, weil die Bedingung immer geprüft werden soll. Ich könnte auch mit Rückgabewerten arbeiten, aber das mach ich aus verständlichen Gründen nicht. Es geht darum, dass die Prüfung gemacht wird, nicht um die Art der Fehlerbehandlung.
davie schrieb:
Wenn jemand undefiniertes Verhalten will, dann kriegt er es auch, egal wie gut du deine lib absicherst.
Nein, kriegt er nicht. Außer wir bleiben jetzt wirklich bei so einfachen Sachen wie eine Wurzelfunktion, wo ich dann auch keine Prüfung machen würde.
-
Hmm. Hier wird immer gesagt, assert ist im Release-Modus deaktiviert. Das ist natürlich nicht ganz richtig. Ob Assertions geprüft werden, geht danach ob NDEBUG definiert ist. Lass doch NDEBUG einfach aus, auch im Release.
-
Optimizer schrieb:
Also, was zum Geier spricht jetzt gegen ein paar Abfragen?
Nichts, aber es spricht etwas gegen Redundanz. Wenn eine Funktion einen falschen Wert bekommt, weil der User diesen nicht überprüft dann reicht ein assert. Wenn ein Programmierer so blöd ist und logische Fehler macht (wie z.B. divide mit 0 oder sqrt mit einem negativen double-Wert) dann ist er schuld. Netterweise fliegt ihm ein assert um die Ohren das auch noch nichts kostet, weil der Compiler es im Release-Build ignoriert (wenn NDEBUG deaktiviert ist). Exceptions sind für andere Sachen gedacht.
-
Um jetzt nochmal bei der Funktion, die den const char * erwartet zu bleiben.
Wenn ich spezifiziere, daß der nicht 0 sein darf, dann ist es ein Programmierfehler, wenn er 0 ist, oder?
Und daran ändert es auch nichts, wenn der string eine Eingabe des Benutzers ist. Wenn er da was blödes eingibt (oder nix eben), dann kommt ein Pointer auf "" rein und nicht 0, das ist ein kleiner aber feiner Unterschied.Btw.: Wir überprüft Dein OS denn, das eine Referenz gültig ist:
int * x=0;
int & y = *x;
// und los gehtsMfG Jester
-
Jester schrieb:
Btw.: Wir überprüft Dein OS denn, das eine Referenz gültig ist:
int * x=0;
int & y = *x;
// und los gehtsMfG Jester
Da geht gar nichts los. Das führt zu einer Acces Violation unter Windows und Linux. Ich habe es jetzt sogar extra ausprobiert.
@Bashar: Natürlich kann ich auch asserts im Release haben. Finde ich auch voll ok. Mir geht es ja nur darum, dass die Prüfung stattfindet.
Und ich nehme halt jetzt Exceptions, weil ich ein anderes Verständnis von asserts habe (siehe oben) und mische mich nicht in die #defines ein, die sich schlaue Leute so ausgedacht haben.
-
Optimizer schrieb:
Jester schrieb:
Btw.: Wir überprüft Dein OS denn, das eine Referenz gültig ist:
int * x=0;
int & y = *x;
// und los gehtsMfG Jester
Da geht gar nichts los. Das führt zu einer Acces Violation unter Windows und Linux. Ich habe es jetzt sogar extra ausprobiert.
Bei mir weder unter Windows noch unter Linux
.
int main() { int* x = 0; int& y = *x; }
-
int * x=0; int & y = *x;
dass das alleine schon eine access violation auslöst?
vielmehr ein späteres y = ...
Allerdings ist das *x schon undefiniert.was ist mit deinen klassen?
überprüfst du jedesmal ob nicht vielleicht this == 0?
das wäre nämlich auch denkbar. (wenn auch vom standard undefiniert)warum sagst du dann nicht selbst einfach:
das verhalten der funktion ist undefiniert, falls argument1 == 0.
ebenso wie du vermutest, dass das verhalten einer memberfunktion undefiniert ist,
falls this==0 (bzw. ein nullzeiger sich eben nicht dereferenzieren lässt) - ich nehme nämlich nicht an, dass du in jeder memberfunktion this==0 prüfst.
-
@Mastah: Klar, ich hab auch immer unreferenzierte Variablen in meinem Code.
davie schrieb:
warum sagst du dann nicht selbst einfach:
das verhalten der funktion ist undefiniert, falls argument1 == 0.Gegenfrage: Und warum nicht "wirft eine Exception, falls ... " ?!
Schau mal diese Methode hier an.If this parameter is NULL ...
If this argument is NULL ...
If this argument is NULL ...Das ist übrigens eine Methode, die man _sehr_ häufig aufruft, wenn man diese Klasse nutzt. Diese Methode erstellt Polygone, legt Texturen darüber, verrechnet ein paar Sachen mit der Transformationsmatrix, rechnet Bildschirmkoordinaten in Weltkoordinaten um - und macht ein paar if-Abfragen. Das wird bestimmt die ganze Performance killen.
Es gibt einfach keinen Grund, da rum zu geizen, wenn es nicht grad was simples wie sqrt ist.
EDIT: Link war falsch.
-
da ist es etwas anderes, da hier NULL als Option benutzt wird und nicht als Fehler.
-
Komm mal weg von Deinem DX-Beispiel, das ist aus zwei Gründen nämlich nicht repräsentativ:
-
Man mag von MS halten was man will, aber im gute Schnittstellen definieren sind sie wahrlich keine Meister. Wenn ich sehen will, wie man ne gute Lib schreibt, dann werf ich nen Blick auf die STL.
Nur weil die MFC von CObject erbt ist das nicht gleich gutes Design. -
DX ist eine Bibliothek, die direkt auf die Hardware zugreift. Sozusagen am OS vorbei. Daher ist es wichtig, daß sie falsche Aufrufe erkennt, denn damit kann man nicht nur die eigene Applikation zum Absturz bringen, sondern die Integrität des gesamten Systems beeinflussen. Und das darf ein Betriebssystem niemals zulassen.
Wobei ich es dennoch mal auf einen Versuch ankommen lassen würde, die von Dir genannte Methode mit 0 als erstem Parameter aufzurufen... und bitte mal im Release, bin gespannt, was da passiert.
MfG Jester
-