[Modultest] Mit Hilfe von assert() Programmfehler finden. Wie geht man richtig vor?



  • assert setzt man normalerweise für Logikfehler (falsche Programmierung bzw. Anwendung von Klassen/Funktionen ein). Also wie DStefan sagte, kannst du damit Pre- und Postconditions sicherstellen (Bedingungen, die vor und nach einem Algorithmus erfüllt sein müssen). Das dient primär Debugging-Zwecken, damit sich Fehler lokal zeigen und nicht erst viel später, wo kein Zusammenhang mehr ersichtlich ist. Im fertigen Release-Build expandiert das Makro assert nicht und du hast keinen Overhead mehr.

    Exceptions dagegen stellen Ausnahmen dar. Man versucht etwas und geht davon aus, dass das entweder klappt oder eine Exception geworfen wird. Zum Beispiel gibt es beim STL-Container std::vector (ähnlich wie ein Array) eine Methode at() , die eine Exception wirft, wenn ein ungültiger Index angegeben wurde. Im Gegensatz zu Assertions sind Exceptions nicht primär da, Programmierfehler aufzudecken, sondern um Laufzeitfehler zu behandeln, die trotz richtiger Programmierung auftreten können. Ein Beispiel wäre auch der Versuch, eine nicht vorhandene Datei zu öffnen (obwohl das in C++ erst mal nichts wirft).



  • Kleiner Nachtrag zu Nexus:

    Assertions dienen auch der Dokumentation. Wenn ich im Code ein assert(irgendwas) lese, kann ich sofort sehen, von welchen Annahmen/Bedingungen der Autor ausgegangen ist. Diese Funktion von assert() finde ich ziemlich wichtig - oder doch zumindest erwähnenswert.

    Stefan.



  • DStefan schrieb:

    Kleiner Nachtrag zu Nexus:

    Assertions dienen auch der Dokumentation. Wenn ich im Code ein assert(irgendwas) lese, kann ich sofort sehen, von welchen Annahmen/Bedingungen der Autor ausgegangen ist. Diese Funktion von assert() finde ich ziemlich wichtig - oder doch zumindest erwähnenswert.

    Stefan.

    Diese "Annahmen" heissen z.B. Vorbedingungen. Diese sollten als Kommentar ueber der Funktion stehen (gilt auch fuer Nachbedingungen und returns). assert ist nicht dazu gedacht, unit tests oder Quelltextdokumentation zu ersetzen.

    Ein Beispiel wäre auch der Versuch, eine nicht vorhandene Datei zu öffnen (obwohl das in C++ erst mal nichts wirft).

    Das ist ein sehr schlechtes Beispiel, der Weg in C++ geht in diesem Fall nicht ueber Exceptions. Das ist meine Meinung.



  • knivil schrieb:

    Ein Beispiel wäre auch der Versuch, eine nicht vorhandene Datei zu öffnen (obwohl das in C++ erst mal nichts wirft).

    Das ist ein sehr schlechtes Beispiel, der Weg in C++ geht in diesem Fall nicht ueber Exceptions. Das ist meine Meinung.

    Das würde aber ganz gut passen, weil man Dateien im Konstruktor des Dateiobjektes öffnet.



  • DStefan schrieb:

    Assertions dienen auch der Dokumentation. Wenn ich im Code ein assert(irgendwas) lese, kann ich sofort sehen, von welchen Annahmen/Bedingungen der Autor ausgegangen ist. Diese Funktion von assert() finde ich ziemlich wichtig - oder doch zumindest erwähnenswert.

    Naja - hier tendiere ich zu knivils Aussage. Du kennst doch lange nicht immer den Code der Funktionen, die du aufrufst. Selbst wenn er dir zugänglich ist, gehst du ihn nicht immer nachlesen. Oder?

    Dass assert als Dokumentation verstanden werden kann, bedingt aber genau das.

    knivil schrieb:

    Das ist ein sehr schlechtes Beispiel, der Weg in C++ geht in diesem Fall nicht ueber Exceptions. Das ist meine Meinung.

    Ja, deshalb habe ich das auch noch in den Klammern angemerkt. Aber von mir aus gesehen ist ein Fehler beim Laden von Ressourcen ein guter Einsatzbereich für Exceptions. std::fstream ist auch nicht gerade das Mass aller Dinge...



  • @knivil und Nexus:

    Da habe ich mich ungenau ausgedrückt (schon wieder 😉 ) Ich wollte keineswegs assert() anstelle von Dokumentation verwenden, sondern Annahmen im Code dokumentieren. Eine gute Anwendung sind Bestandteile der Klasseninvariante. Und natürlich liest man diese nicht in der Dokumentation zu einer Methode, sondern wenn man diese Methode überarbeitet.

    Nehmen wir als Beispiel eine Klasse File in RAII-Manier, also Datei im Ctor öffnen (und Exception, falls das nicht gelingt, wie volkard vorgeschlagen hat) und erst im Dtor wieder schließen. Eine Klasseninvariante wäre, dass die Datei in jeder Methode geöffnet ist. Also:

    void File::writeData(const void *data, size_t count) {
    
       assert(is_open());    // Sichert die Klasseninvariante zu und dokumentiert sie.
    
       // usw.
    
    }
    

    Die Dokumentation zur Methode ist klar. Sie enthielte aber nicht, wie ich finde, die Klasseninvariante, nämlich, dass die Datei geöffnet ist.

    Stefan.



  • Die Dokumentation zur Methode ist klar. Sie enthielte aber nicht, wie ich finde, die Klasseninvariante, nämlich, dass die Datei geöffnet ist.

    Naja, wenn es eine Invariante ist, dass die Datei geoeffnet ist, so kann es kein Dateiobjekt geben, was eine ungeoeffnete Datei repraesentiert. Dort waere das assert nur zum Debuggen gut. Wenn es keine Invariante der Klasse ist, schreibe ich es dazu, unter welchen Umstaenden die Methode aufgerufen werden darf und welche Exceptions geworfen werden koennen



  • knivil schrieb:

    Die Dokumentation zur Methode ist klar. Sie enthielte aber nicht, wie ich finde, die Klasseninvariante, nämlich, dass die Datei geöffnet ist.

    Naja, wenn es eine Invariante ist, dass die Datei geoeffnet ist, so kann es kein Dateiobjekt geben, was eine ungeoeffnete Datei repraesentiert. Dort waere das assert nur zum Debuggen gut.

    Und genau das sagt die Assertion. Sie verhindert, dass (meist ja beim Überarbeiten der Klasse) die Klasseninvariante versehentlich verletzt wird. Sie zeigt aber auch unmittelbar im Code an, dass ich dieser Invariante vertraue. Das wäre dann der Dokumentationsaspekt von assert() . Und natürlich ist das "nur" für's Debuggen gut. In der Release-Version ist assert() doch gar nicht eingeschaltet.

    Ich nehme also "assert" wörtlich. Für irgendwelche Fehler (etwa falsche Parameter) setze ich es nie ein. Dazu sollte man Exceptions verwenden.

    Stefan.



  • DStefan schrieb:

    Ich nehme also "assert" wörtlich. Für irgendwelche Fehler (etwa falsche Parameter) setze ich es nie ein. Dazu sollte man Exceptions verwenden.

    Ja, und jedesmal wieder erkläre ich Dir, das das totaler Unfug ist.



  • volkard schrieb:

    Ja, und jedesmal wieder erkläre ich Dir, das das totaler Unfug ist.

    👍

    Also ich setze assert intern fuer debugging falscher Parameter ein. Erwarte ich z.B. ein konvexes Polygon, so steht in meiner Funktion assert ( konvex_poly( poly ) ). Wenn aber jemand anderes sich nicht an diese Vorbedingung haelt und mit der realease-Version der Bibliothek arbeitet ... selbst Schuld. Warum soll ich da 'ne Exception werfen?


Anmelden zum Antworten