Ist Assert Sinnvoll
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Von ZeigerAufAusreichendPlatzAlsCeeIdiom.
assert() bietet in C und C++ einen Mechanismus zur Dokumentation von wahren Aussagen über den Programmzustand an einer bestimmten Stelle, den gegenüber einem reinen Kommentar den Vorteil hat, dass er - abschaltbar - zur Laufzeit überprüft werden kann. Ist NDEBUG abgeschaltet, führt eine verletzte assert()-Bedingung zum unbedingten Programmabbruch. Ist NDEBUG eingeschaltet, wird die angenommene Bedingung nicht einmal ausgewertet.
Die Verwendung von assert wird von der Mehrheit der Programmierer positiv bewertet. Ist das richtig? Was spricht für bzw. gegen seine Verwendung?
Pro:
- assert kann als rein abstrakter, nicht codegenerierender Teil der Dokumentation betrachtet werden.
- Assertions sind direkt im Quelltext definiert, d.h.:
- DokumentationImQuelltext ist dann besonders nützlich, wenn die Übereinstimmung der Dokumentation mit dem, was der Code wirklich tut, automatisiert stattfinden kann.
- Mehr Bequemlichkeit (Hat meiner Erfahrung nach große Auswirkungen auf Konsistenz und Nutzung)
- Assertions sind eine erweiterte Typdefinition -> Verbesserter Fehlerlokalisation, wenn viele unterschiedlichen (z.B. fremde) Komponenten / Module im Einsatz sind.
- Mit Assertions wird die Überprüfung von Vor-/Nachbedingungen explizit und an definierter Stelle ausgedrückt. Damit veschwindet das ein oder andere if(eingabeCondition) {throw new Exception()} aus dem Methoden Body.
- Mit assert() kann man Vermutungen über Invarianten im Code ablegen. Hat man sich getäuscht, bekommt man eine punktgenaue Warnung.
Contra:
- Wird assert ernsthaft angewendet, sollten auch alle Vorbedingungen niedergelegt werden (GanzOderGarNicht). Dies erfasst aber mit relativ großem Schreibaufwand Trivialitäten mit geringem Gegenwert.
- Es besteht die Gefahr, dass bei der Formulierung von Bedingungen für die Korrektheit eines Programms Seiteneffekte erzeugt werden, von denen die korrekte Arbeitsweise des Programms abhängig ist.
- Es gibt aufwendig zu kontrollierende Vorbedingungen, z. B. assert(sorted(array)) für eine Funktion für binäre Suche. Solche Performancefresser werden in der Praxis aber kaum durchgehalten werden (GanzOderGarNicht).
- Asserts finden zur Runtime statt:
- d.h. man muss warten bis zufällig ein Fehler passiert, man kann nicht geziehlt kritische Beispiele definieren
- das ganze ist rechenintensiv
- Asserts bringen ihren Qualitätsgewinn erst im laufenden Betrieb ein, typischerweise will man diesen Gewinn aber vor dem Rollout haben
Erfahrungsbericht | |
Soweit die Theorie ... ich wollte heute voller Begeisterung endlich mal das neue Java assert einsetzen ... aber:
- Parameter von public Methoden können nicht mit Assertions behandelt werden ... denn Assertions können ja ausgeschaltet werden, dann bekommt mein Code Fehleingaben ab.
- Selbst, wenn ich mal davon ausgehe, dass Assertions immer an sind, werden immer nur AssertionErrors? generiert.
Ich glaub, inzwischen ist meine anfängliche Begeisterung doch arg geschrumpft. -- mj
- Was haben Aspekte mit assertions zu tun ? Kannst du ein Beispiel für einen solchen Aspekt geben ? -- mj
Man kann Aspekte implementieren, die für die Überwachung der Zusicherung bzw. des Kontraktes zuständig sind. Diese Aspekte können beim Kompilieren der Software je nach Bedarf selektiv entweder aktiviert oder deaktiviert werden. Hier ein Beispiel aus dem Programmierhanbuch von AspectJ. Dieser Aspekt übeprüft die Eingangsparameter der Methoden, die die Koordinaten eines Puktes ändern:
| aspectPointBoundsChecking {
pointcut setX(int x): (call(void FigureElement.setXY(int, int)) ||
call(void Point.setX(int))) && args(x, ..);
pointcut setY(int y): (call(void FigureElement.setXY(int, int)) ||
call(void Point.setY(int))) && args(y, ..);
before(int x): setX(x) {
if ( x < MIN_X || x > MAX_X )
throw new IllegalArgumentException("x is out of bounds.");
}
before(int y): setY(y) {
if ( y < MIN_Y || y > MAX_Y )
throw new IllegalArgumentException("y is out of bounds.");
}
} |
|
|
Hier etwas komplizierter Kontrakt: Nur definierte Factorymethoden, die hier mit make anfangen, dürfen Figuren registrieren. Aufrufe der Methode register sind aus anderen Methoden nicht erlaubt:
| static aspect RegistrationProtection {
pointcut register(): call(void Registry.register(FigureElement));
pointcut canRegister(): withincode(static * FigureElement.make*(..));
before(): register() && !canRegister() {
throw new IllegalAccessException("Illegal call " + thisJoinPoint);
}
} |
|
|
--gR
- Okay, mit Aspecten kann man Assertions nachbauen. Das macht Assertions aber nicht besser (und Aspecte an dieser Stelle nicht sinnvoller).
- "Assert" ist zumindest ein Keyword, das ein Compiler ode ein Doku-Extractor in Zukunft irgendwann vielleicht mal vernünftig auswerten kann.
- Im übrigen finde ich es bei Assertions schlecht, dass sie abschaltbar sind. Aspecte, die dieses Abschalten simulieren, sind da also kein Argument. -- mj
- Mit Aspekten kann man viel mehr als Assertions nachbauen, aber das sollte hier nicht das Thema sein. Was hier allerdings Aspekte sinnvoller macht, ist die Möglichkeit, sie selektiv abzuschalten - d.h. ich kann die Überprüfungen abschalten, die nur dazu dienen, meine eigene Fehler zu entdecken. (Diese Übrprüfungen können aufwendig sein, und es ist dann sinnvoll, sie abzuschalten) Überprüfungen der Einhaltung der Kontrakte mit der "Außenwelt" können aktiv bleiben.
- Man sollte diese zwei Arten der Überprüfung strikt auseinander halten.
- Eine Assertion sollte an Stellen verwendet werden, auf denen eine Bedingung als wahr bewiesen gilt. Sie sollte nur dazu dienen, eigene Programmierfehler zu entdecken. Da die Bedingung als bewiesen Gilt, kann man die Assertion in der Releaseversion getrost abschalten, ähnlich wie ein Gerüst nach der Fertigstellung eines Gebäude abgebaut werden kann. (Beispiel: Überpüfung, das nach dem Aufruf der Methode sort() eine Liste sortiert ist)
- Ein Kontrakt beschreibt eine Bedingung die von einem "Partner" verlangt wird, und, da wir nicht die Kontrolle über diesen Partner haben, nicht bewiesen werden kann. Es ist oft sinnvoll die Überprüfung eines Kontraktes auch in der Releaseversion aktiv zu lassen. Deren Abschaltung kann man als Optimierung in Betracht ziehen. Aus diesem Grund sollte man für Kontrakte mit der Aussenwelt nicht Assertions verwenden.
- Aspekte sind eine Art die Assertions und Kontraktüberpüfungen zu implementieren. Nicht nur die Auswahlmöglichkeit, welche an und abgeschaltet werden, bringt Vorteile. Sie haben in manchen Situationen den Vorteil, dann man den Kontrakt sehr kompakt beschreiben kann, im Gegensatz zu Assertions/Verifies usw., die in diesen Situationen an vielen Stellen verstreut werden müssten. Als Beispiel soll die obengenannte RegistrationProtection? dienen.
- Übringens, sollte man statt Assertion nicht lieber Zusicherung sagen? Anglizismen sind doch so lame :-) --gR
Wenn Du es nicht abschalten können willst, dann nimm nicht assert() und nichts, was den assert()-Mechanismus von C oder C++ nachzubilden versucht. assert() ist ein Hilfsmittel, um Dokumentation abschaltbar zu Code werden zu lassen, der die Richtigkeit der dokumentierten Behauptung überprüft. Das ist die Idee hinter assert() in C, C++ und hinter dem assert-Mechanismus in Java. Wenn Du Eingabeparameter von Funktionen mit assert() überprüfen willst, dann musst Du alle moeglichen Aufrufer dieser Funktion
unter Kontrolle haben. Wenn Die Prüfung Teil deiner Programmlogik ist, dann bilde einen eigene Mechanismus zur Verifikation von Bedingungen, aber setze in C oder C++ nicht assert() ein und wirf nicht mit Error-Exemplaren um dich. -- kw
- Falls dich meine Error-Exemplare nicht interessieren, ignoriere sie bitte einfach ;-). Ich habe ganz einfach Bedarf nach genauerer Eingabeparameter Spezifikation und deren (automatischen) Dokumentation. Es ist schon richtig, dass ich da wohl auf die Fälle 1. & 2. aus AssertionVermeidung gestossen bin. Als alter Optimierer wünsche ich mir einfach nicht für alles Objekte erzeugen zu müssen, sondern möchte manchmal auch native Datentypen genauer spez. können.
- Dazu braucht's dann nicht abschaltbare Assertions, die evtl. unterschiedliche Exceptions werfen können (Dokumentation und Eingebeeinschränkung), oder ich lasse mit einem Tool statisch die Fehlerfälle berechnen und mache dann Testcases daraus (erweiterte statische Typruefung). -- mj
- Du brauchst einen Mechanismus, mit dem du Vorbedingungen so spezifizieren kannst, dass sie immer überprüft werden. Dieser Mechanismus soll darüber hinaus eine automatische, über den Quelltext hinausgehende Dokumentation auslösen. Zu diesem Zweck willst Du einen anderen Mechanismus missbrauchen, der dazu gedacht ist, auf überprüfbare Weise eine Bedingung zuzusichern. Eine Vorbedingung kannst Du nur garantieren, wenn Du selbst für ihre Einhaltung sorgen kannst. Denn zweiten Teil eines Kontraktes, nämlich die Nachbedingung unter der Vorbedingung, kannst Du wirklich garantieren.
- Stell Dir vor, ich biete folgenden Kontrakt an: Wenn Du mir eine vollstaendige Spezifikation eines Softwaresystems lieferst, die in Form von UnitTests und AkzeptanzTests vorliegt, dann garantiere ich, dass das gelieferte Softwaresystem diese Spezifikation einhält. Darüber hinaus garantiere ich, dass die von Dir in dieser Form gelieferte Spezifikation vollständig sein wird. Würde Dir das nicht eigenartig vorkommen? Würdest Du jemanden, der Dir die zweite Garantie zu machen bereit ist, nicht für völlig naiv halten? -- kw
- Worin besteht der Sinn, die Einhaltung des Kontraktes zu überprüfen und was passiert, wenn die Vorbedingung nicht eingehalten wird? Die Überprüfung dient dazu, mögliche Fehler einzugrenzen. Ist die Vorbedingung nicht erfüllt, liegt der Fehler beim Aufrufer. Wenn die Bedingung nicht erfüllt ist und nicht überprüft wird, kann es passieren, dass wir den Fehler erst später feststellen und dass die Fehlersuche nicht so einfach wird. Ob die Überprüfung der Vorbedingung immer aktiviert sein sollte, hängt von deren Kosten ab. Wenn die Vorbedingung einer Binärsuche eine sortierte Liste ist, wäre es ziemlich sinnlos sie immer zu überprüfen und so die Performance von logaritmisch auf linear zu degradieren. Wenn der Aufrufer den Kontrakt bricht, bekommt er halt nicht das richtige Ergebnis. Während der Entwickler des Aufrufers den Fehler sucht, kann es sinnvoll sein, die Überprüfung zu aktivieren. --gR
Mißverständnisse | |
- De facto bewirkt die Verwendung von assert die Erzeugung von zwei Programmversionen (Debug-Version und Release-Version) mit unterschiedlichen Eigenschaften: Tatsächlich sind Debug- und Releaseversionen auch ohne die Verwendung von assert()s unterschiedlich.
- assert() ist für die Anwendung in Release-Versionen nicht geeignet. D. h. man investiert Aufwand in ein Fehlererkennungssystem, das am wichtigsten Ort (beim Kunden) deaktiviert wird: assert()s sind by design ein Fehler-offensichtlich-mach-system und daher für den Einsatz beim Kunden weder geplant noch geeignet. Diese Einstellung erleichtert auch den Einsatz "teurer" Überprüfungen, die dann im Echtsystem ja abgeschaltet werden.
- Das Vorhandensein einer Debug- und einer Release-Version hat in der (praktischen) Informatik eine lange Tradition, ich denke das ist kein Argument.
- Ich finde auch, daß die Assertions beim Kunden nichts mehr bringen. Aber wie wär's denn mit der Kombination aus TestgetriebeneEntwicklung und Assertions ? Die TestgetriebeneEntwicklung liefert die kritischen Konfigurationen und die Assertions helfen bei der Fehlerlokalisation ? -- mj
- Im übrigen kann ich das GanzOderGarNicht Prinzip nicht als Contragrund nachvollziehen. GanzOderGarNicht ist niemals anwendbar, nichts ist absolut perfekt ... oder willst du z.B. auch nur für das einfachste HelloWorld einen erschöpfenden Test schreiben :-) ?
- Viele Assertions sind vermeidbar. Das ist ein besonders deutliches Mißverständnis, sofern es sich um etwas dem C-Mechanismus aus <assert.h> Vergleichbares handelt. Jedes assert() muss überflüssig sein. Ein assert(), das eine Funktion hat und deswegen da stehen muss, ist immer ein Fehler. Ein assert() dokumentiert eine nach der Ansicht des Programmierers wahre Aussage. Es ist nicht dazu gedacht oder geeignet, die Wahrheit dieser Aussage zu prüfen.
Diskussionen | |
Verweise | |
In Bezug auf NULL als Eingabewert siehe NullAlsInputParameter.
Eine eher skeptische Einstellung zu Assertions vertrete ich in AssertionVermeidung.
Wegen der Ausführlichkeit meiner Stellungnahme hielt ich eine eigene Seite für sinnvoll. -- kg
KategorieC KategorieCee KategorieCpp AbgrenzungTestenZuAssertZuDesignByContract
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 29. November 2007 8:38 (diff))