Exception oder nullptr?
-
Zunächst vielen Dank für die Antworten. Da habe ich ja fast vermutet, dass ein kleiner Glaubenskrieg ausbricht. :xmas1:
Smilies in diesem Beitrag schrieb:
Warum brauchst man überhaupt eine create und eine set Methode?
Das ist nur wg. des Beispiels so. Diese drei Operationen sind mir prompt eingefallen, bei denen ich mir unsicher bin, wie ich das handeln soll. Tatsächlich würden z.B. nur "set" und "get" gemeinsam in einer Klasse auftauchen, "create" dann separat in einer anderen.
dot schrieb:
Es hängt natürlich vom Anwendungsfall ab, aber ich würd im Allgemeinen da eher in allen 3 Fällen wenn überhaupt dann ein assert() verwenden.
Das würde mir (technisch) auch völlig ausreichen. Blöderweise verträgt sich das assert() mit Boost.Test, das ich einsetze, nicht so gut. Auf Exceptions testen ist einfacher.
Im Falle der "get"-Methode tendiere ich mittlerweile zu einer Exception/nullptr-"Kombination". Exception werfen, wenn der Parameter unsinnig ist, d.h. wirklich ein Programmierfehler vorliegt und nullptr (bzw. Fehlerwert) liefern, wenn zwar der Parameter gültig ist, an geeigneter Stelle aber nichts gefunden wird.
Ich möchte euch aber gerne noch mit einem konkreteren Beispiel verwöhnen, der vorige Code scheint teilweise mehr verwirrt als geholfen zu haben:
class Map { public: static const unsigned int INVALID_ID = std::numeric_limits<unsigned int>::max(); Map( unsigned int size ) : m_tiles( size * size, INVALID_ID ), m_size( size ) { } void set( unsigned int x, unsigned int y, unsigned int id ) { if( x >= m_size || y >= m_size ) { throw std::invalid_argument( "x/y doof." ); } else if( id == INVALID_ID ) { throw std::invalid_argument( "id doof." ); } m_tiles[y * m_size + x] = id; } void reset( unsigned int x, unsigned int y ) { if( x >= m_size || y >= m_size ) { throw std::invalid_argument( "x/y doof." ); } m_tiles[y * m_size + x] = INVALID_ID; } unsigned int get( unsigned int x, unsigned int y ) const { if( x >= m_size || y >= m_size ) { throw std::invalid_argument( "x/y doof." ); } return m_tiles[y * m_size + x]; } private: std::vector<unsigned int> m_tiles; unsigned int m_size; };std::vector<unsigned int> ist der Einfachheit halber gewählt, es gibt den Fall auch mit größeren Ressourcen, die dann über Pointer rausgehen.
Prinzipiell habe ich im Beispiel nun statt assert()s Exceptions genutzt und mich im get-Fall entschieden, einen Fehlerwert zurück zu geben, falls zwar die Koordinate korrekt ist, dort aber kein wirklicher Inhalt zu finden ist (INVALID_ID). Und falls wer meckert, dass das ja fast ein purer vector-Wrapper ist: Es gibt selbstverfreilich weitere Operationen.
Was haltet ihr davon?
-
Ich würde bei diesen Settern nur in der Debugversion mit asserts den Bereich und die IDs prüfen.
In der Releaseversion haben derlei Überprüfungen imho nichts zu suchen.
-
Dann bau dir ein assert() dass eine Exception wirft?
Wenn dein Programmdesign vom Testsystem bestimmt wird, dann ist das Testsystem Mist oder du verwendest es falsch, wenn du mich fragst.
-
sboc schrieb:
Blöderweise verträgt sich das assert() mit Boost.Test, das ich einsetze, nicht so gut. Auf Exceptions testen ist einfacher.
Du brauchst auch nicht testen, was passiert, wenn man falsch programmiert.
-
dot schrieb:
Dann bau dir ein assert() dass eine Exception wirft?
Wenn dein Programmdesign vom Testsystem bestimmt wird, dann ist das Testsystem Mist oder du verwendest es falsch, wenn du mich fragst.Könnte es nicht auch ein Hinweis darauf sein, dass das Design kein anständiges Testen zulässt? Ich schreibe zuerst Unittests, bevor ich den Produktiv-Code schreibe, und dort möchte ich halt auch Fehler provozieren, wo sie auftauchen sollen.
Du brauchst auch nicht testen, was passiert, wenn man falsch programmiert.
Ich denke schon, dass das sinnvoll ist. Ich möchte ja z.B. durch einen Testfall sicherstellen, dass eine Funktion bei falschen Werten fehlerhaft reagiert. Das wäre vergleichbar damit, als würde man nur testen, ob 15 / 5 = 3, aber nicht ob 15 / 0 = Fehler.
Dass etwas bei anständigen Werten funktioniert, ist ja nur die halbe Miete. Oder aber ich mache es tatsächlich falsch, bin noch recht frisch beim test-driven development.
-
sboc schrieb:
dot schrieb:
Dann bau dir ein assert() dass eine Exception wirft?
Wenn dein Programmdesign vom Testsystem bestimmt wird, dann ist das Testsystem Mist oder du verwendest es falsch, wenn du mich fragst.Könnte es nicht auch ein Hinweis darauf sein, dass das Design kein anständiges Testen zulässt? Ich schreibe zuerst Unittests, bevor ich den Produktiv-Code schreibe, und dort möchte ich halt auch Fehler provozieren, wo sie auftauchen sollen.
Wenn du mich fragst soll das Testsystem sich dem Programm anpassen und nicht umgekehrt. Denn ich schreib das Programm ja nicht damit ich was zu testen hab, sondern aus anderen Gründen...
Im konkreten Fall würd ich mal ganz definitiv sagen, dass mit Testsystem grob was im argen liegt, wenn es so grundlegende Entscheidungen wie das Verhalten einer Containerklasse beeinflusst oO
-
dot schrieb:
Wenn du mich fragst soll das Testsystem sich dem Programm anpassen und nicht umgekehrt. Denn ich schreib das Programm ja nicht damit ich was zu testen hab, sondern aus anderen Gründen...
Mit den Tests möchte ich auch nur sicherstellen, dass die Klasse sich genau so verhält wie gedacht. Das heißt, wenn ich die Tests schreibe, überlege ich, welche Fehler ich bei falschen Eingaben erwarte (neben den viel wichtigeren Erwartungen, was korrekterweise herauskommen soll, natürlich). Erst danach implementiere ich die Klasse nach diesem Schema.
Im konkreten Fall würd ich mal ganz definitiv sagen, dass mit Testsystem grob was im argen liegt, wenn es so grundlegende Entscheidungen wie das Verhalten einer Containerklasse beeinflusst oO
Ich weiß nicht genau, was du mit Beeinflussen meinst. Die Tests geben in meinem Fall das Design vor, da die Klasse so funktionieren soll wie die Tests sie nutzen (TDD, falls noch nicht bekannt).
-
sboc schrieb:
dot schrieb:
Wenn du mich fragst soll das Testsystem sich dem Programm anpassen und nicht umgekehrt. Denn ich schreib das Programm ja nicht damit ich was zu testen hab, sondern aus anderen Gründen...
Mit den Tests möchte ich auch nur sicherstellen, dass die Klasse sich genau so verhält wie gedacht. Das heißt, wenn ich die Tests schreibe, überlege ich, welche Fehler ich bei falschen Eingaben erwarte (neben den viel wichtigeren Erwartungen, was korrekterweise herauskommen soll, natürlich). Erst danach implementiere ich die Klasse nach diesem Schema.
Kann die Testumgebung denn nicht erwarten und testen, daß bei einem falschen Index ein assert zündet?
Das wäre doch ok, dann könntest Du, wie es sich gehört, Programmierfehler mit assert begrenzen.
-
cooky451 schrieb:
Shade Of Mine schrieb:
Das Flag ist halt furchtbar, weil man nie weiss was jetzt passiert.
Kannst du mir dafür mal ein Beispiel nennen?
Klar:
void foo(Klasse& obj) { obj.bar(); } int main() { Klasse a; a.enable_exceptions(true); foo(a); Klasse b; b.enable_exceptions(false); foo(b); }Wie soll foo jetzt wissen wie es auf einen Fehler bei bar() reagieren soll? Jedesmal checken ob Exceptions aktiviert sind? Und wenn Exceptions aktiviert sind, wie ist dann isGood() oder failed() etc. definiert wenn eine Exception fliegt? Wird immer das fail bit immer gesetzt? Was wenn ich zwischen einem Fehler umschalte?
Enorme Komplexität für keinen Mehrwert. Wenn du einfach sagst bar() wirft eine Exception, dann ist alles klar. Keine Probleme, keine Unsicherheit - auch robuster Code.
Denn Stell dir vor du hast Unit Tests mit eingeschaltenen Exceptions und dann im Produktionscode werden sie irgendwo ausgeschalten. uU nur bei einem Objekt. Das führt zu unfindbaren Fehlern.
Deshalb ist es ganz wichtig: ein Weg! Eine Art Fehler zu reporten. Nicht 2. Nicht 3. Nur eine Art. Weil sonst werden die anderen irgendwann/irgendwo einmal übersehen.
-
volkard schrieb:
Kann die Testumgebung denn nicht erwarten und testen, daß bei einem falschen Index ein assert zündet?
Das wäre doch ok, dann könntest Du, wie es sich gehört, Programmierfehler mit assert begrenzen.Hast Du völlig Recht, aber soweit ich weiß, bietet zumindest Boost.Test das nicht an (es gibt einen Weg über einen Hack, aber das ist immer so eine Sache..). Vielleicht schaue ich mich mal bei anderen Frameworks um, wie die das unterstützen.
-
cooky451 schrieb:
bool set( std::size_t index, const Foo& value, bool throw_exception = false ); const Foo* get( std::size_t index, bool throw_exception = false ) const; bool create( std::size_t index, bool throw_exception = false );Wenn die Klasse eh nur in einem größeren Kontext Sinn macht, in dem Exceptions auf höherer Ebene gefangen werden (z.B. in einem Spiel), dann kannst du auch gleich Variante 1 nehmen. Ansonsten weißt du nicht wie der "Nutzer" der Klasse diese Funktionen nutzen möchte, warum also nicht ihm die Wahl überlassen?
Also abgesehen davon dass ich da beim hinschauen Kopfweh bekomm, widerspricht das doch rein prinzipiell komplett dem Sinn von Exceptions!?
Der war doch eben genau dass man die Exception auf irgendeiner Ebene fangen kann und nicht immer direkt eine Ebene drüber behandeln muss. Mit deinem Parameter zwingst du aber erst wieder sämtliche Ebenen dazu, sich mit Exceptions zu befassen, nur halt von der anderen Richtung aus...
-
Du testest falsch. Wenn du z.B. den Zugriff aus Performancegründen so implementieren willst, dass die Gültigkeit von Parametern nicht überprüft wird, dann schreibts du in die Doku, dass falscher Zugriff zu undefiniertem Verhalten führt. Undefiniertes Verhalten brauchst du auch nicht testen. Wenn falsche Parameter zu einer Exception führen, dann schreibst du das in die Doku und testest es auch. Aber mach nicht überall Exceptions rein, nur damit du was zum Testen hast, sondern nur dann Exceptions verwenden, wenn es sinnvoll ist.
-
PS: Ging an sboc, niccht dot.
-
Smilies in diesem Beitrag schrieb:
Du testest falsch. Wenn du z.B. den Zugriff aus Performancegründen so implementieren willst, dass die Gültigkeit von Parametern nicht überprüft wird, dann schreibts du in die Doku, dass falscher Zugriff zu undefiniertem Verhalten führt. Undefiniertes Verhalten brauchst du auch nicht testen. Wenn falsche Parameter zu einer Exception führen, dann schreibst du das in die Doku und testest es auch. Aber mach nicht überall Exceptions rein, nur damit du was zum Testen hast, sondern nur dann Exceptions verwenden, wenn es sinnvoll ist.
Interessant! Also wäre das ein klarer Fall für assert() und kein Unittest für den Fall, richtig?