Kategorie: Meinung


#Handwerk: Clean vs. pragmatisch?

3. Mai 2010 - 11:31 Uhr

Am Beispiel von Thomas’ Blogbeitrag “Null Verständnis” kann man wunderschön zwei Dinge erkennen: Erstens, wie schnell man aus einem praktischen Problem in eine Grundsatzdiskussion abgleiten kann und zweitens, dass es offensichtlich einen Unterschied zwischen möglichst sauberen Vorgehensweisen und pragmatischen Ansätzen geben kann. Oder ist dem nicht so?

Zunächst zum Thema “Grundsatzdiskussion”: Ich persönlich bin kein großer Freund dieser Form von Diskussionen, werden sie doch gerne anhand von vereinfachten Beispielen geführt und nähern sich schnell einem recht grundlegendem Level an, da ab einem gewissen Punkt das Selbstverständnis eines Teils der Diskussionspartner berührt werden könnte. Das beiseite gelassen, eignen sie sich jedoch wundervoll, um unterschiedliche Grundideen auszuarbeiten, wie es in diesem Fall geschehen ist: Auf der einen Seite die Fraktion, die gerne auf null als Rückgabewert von GetXXX-Funktionalitäten verzichten möchte, wenn diese als Parameter einen ID-Wert übergeben bekommen und der Datensatz nicht existiert (Begründung: ID kennzeichnet nun mal einen eindeutig identifizierten Datensatz, gibt es diesen nicht, ist irgendwas faul im Staate Dänemark – das Ganze kann dann wahlweise über ein Null-Objekt oder eine Exception abgebildet werden), auf der anderen Seite die Fraktion, die in diesem Fall eben null zurück gibt und das im Code abfragen und abfangen möchte.

Unabhängig davon, dass ich die Meinungen beider Fraktionen nachvollziehen kann (und dennoch gerne Null-Objekte einsetze, da sie die weitere Verarbeitung stark erleichtern können und oftmals der Wartbarkeit zuträglich sind), haben wir es hier mit einem eher grundsätzlicheren Problem zu tun: Clean Code vs. pragmatische Entwicklung, um es mal zugespitzt zu umschreiben. Dieser Widerspruch ist jedoch bei näherer Betrachtung keiner, jedenfalls soweit es mich betrifft, denn in Wahrheit haben wir es an dieser Stelle eher mit zwei verschiedenen Anforderungsmodellen zu tun:

Beiden Fraktionen geht es um einen möglichst sauberen, wiederverwendbaren und auch für andere Entwickler leicht verständlichen Code. Da sind sich beide einig, der Unterschied liegt darin, ob und wie mit Annahmen außerhalb des DataLayers umgegangen wird:

  • Die “Clean Coder” wollen, dass der Code vertrauenswürdig ist, d.h. er soll sich in jedem Fall eindeutig verhalten und außerhalb des Kontextes etwa eines DataLayers keinerlei Annahmen über dessen Struktur und Funktionsweise erfordern. Ich folge diesem Ansatz in der Regel. Das bedeutet für: Null-Objekte, da wo es sinnvoll ist (Benutzer gehören für mich in aller Regel dazu), null bei nicht relevanten Geschichten oder Dingen, die schon ganz eindeutig aussagen, dass es eventuell auch KEIN Ergebnis geben kann (FindXXX-Methoden wäre klassische Kandidaten dafür).
  • Die “Pragmatiker” haben einen anderen Schwerpunkt: Sie legen weniger Wert auf die oben angesprochene Vertrauenswürdigkeit von Code, denn die erarbeiteten Lösungen werden entweder meist allein verwendet, oder stellen Lösungen dar, die von einem Endkunden in aller Regel nicht selbstständig weiterentwickelt werden. Hier heißt es: Null statt eines Null-Objekts, denn hier ist außen bekannt, wie das Repository sich verhält und die Anwendungsfälle für etwa Null-Objekte halten sich meist in Grenzen.

Beide Fraktionen haben somit unterschiedliche Anforderungen an Code, der scheinbare Widerspruch zwischen pragmatischem und sauberen Programmieren ist also keiner – wir haben es schlicht mit unterschiedlichen Verwendungsszenarien zu tun.

Ob, und in welchem Umfang man nun die Meinung der einen oder der anderen Fraktion teilen mag, ist eine andere Frage. Ich persönlich tendiere zur Clean Code-Ansicht, denn ich habe für mich die Sinnhaftigkeit von Null-Objekten schon seit geraumer Zeit erkannt. Dementsprechend definiere ich meine Entitäten und meine Schichten auch und kann mich so darauf verlassen, stets eine funktionsfähige Objektinstanz zu besitzen und entsprechend meinen Code einfacher strukturieren. Das Ergebnis: Wartbarkeit und Sicherheit steigen, meine Applikation verhält sich vorhersagbarer, ich kann mindestens ebenso gut testen, wie beim eher “pragmatischen” Ansatz.

Insofern erscheint mir hier der Clean Code-Ansatz im Endeffekt sogar pragmatischer zu sein.

4 Kommentare » | Allgemeines, Handwerk, Meinung

#Meinung: Warum Checked Exceptions in .NET fehlen

10. Juli 2009 - 00:06 Uhr

Anders Hejlsberg ist seines Zeichens der Erfinder von C#. In einem Interview, das er vor mehreren Jahren gegeben hat, lässt er sich über Checked Exceptions bzw. den Verzicht auf diese Checked Exceptions aus.

Zur Erklärung: Wenn innerhalb einer Methode einer .NET-Klasse eine Exception geworfen wird, dann muss ein Entwickler, der diese Methode aufruft, nichts machen. Er muss noch nicht mal irgendwie reagieren. Oftmals weiß er noch nicht einmal, das es in der Methode eine Exception geben könnte. Der Grund dafür liegt im Verzicht auf einen Mechanismus, der dem Entwickler zuverlässig mitteilt, das in dieser Methode eine bestimmte Exception auftreten kann und diese innerhalb der Methode auch nicht behandelt worden ist. Bei Java gibt es diesen Mechanismus – wenn man dort eine Methode definiert und innerhalb der Methode eine Exception per throw-Schlüsselwort wirft, muß man diese Exception per throws-Schlüsselwort auf Ebene der Methodendeklaration angeben. Das sieht dann etwa so aus:

public void tueWas(String input) throws MySuperDuperException, YourSuperDuperException { … }

Ruft man jetzt diese Methode auf und kümmert sich nicht um MySuperDuperException und YourSuperDuperException, dann weist einen der Java-Compiler dezent mit einem echten Fehler darauf hin. Kompilieren geht schlicht nicht, das geht erst dann, wenn entweder auf der aufrufenden Methode ebenfalls per throws angegeben worden ist, das diese Exceptions nicht behandelt werden oder wenn man eben das Naheliegende macht: try-catch verwenden. So oder so, als Entwickler ist man gezwungen, sich mit diesen Exceptions auseinander zu setzen.

Anders Hejlsberg sieht das anders. Er führt drei Punkte an, die seiner Meinung nach gegen diese Checked Exceptions sprechen: Versionierung, Skalierbarkeit und Unübersichtlichkeit. Gehen wir diese Punkte einmal der Reihenfolge nach durch.

1. Versionierung

Hier sagt Hejlsberg, das Interfaces unveränderlich sein sollten. Wenn man nun in der Weiterentwicklung eines Interfaces eine neue Exception einführt, dann würde das zwangsläufig zu Inkompatibilitäten und zusätzlichem Entwicklungsaufwand führen. Und die meisten Entwickler würden sich ohnehin nicht darum kümmern.

Ich sage, dass das eine komplette Nicht-Aussage ist, denn man führt Exceptions nicht mal einfach so ein (es spricht ganz definitiv nicht für eine durchdachte API, wenn die bei jedem Release geändert wird) und ganz nebenbei kann ich einfach eine neue Exception definieren, die von einer existierenden (und per throws deklarierten Exception) erbt. Dann muss auch nix neu implementiert werden. Das Argument ist schlicht nicht stimmig. Das Einzige, was hier stimmt, ist die Aussage, dass sich Entwickler ohnehin nicht darum kümmern würden – was nun aber letztlich auch eher gegen-, als für sie spricht und Zeichen einer recht unprofessionellen Arbeitseinstellung ist.

2. Skalierbarkeit

Hejlsberg führt aus, das die Checked Exceptions dazu führen würden, das die Entwickler viel zu viele Exceptions zu behandeln hätten und stattdessen eher auf generische try-catch(Exception e)-Statements wechseln würden. Damit hat er grundsätzlich sicher recht – die Programmierer machen das tatsächlich so. Aber aus diesem Grund auf die Checked Exceptions verzichten, nur weil ein paar Zeitgenossen schlicht keine passende Arbeitseinstellung haben? Das ist kein Argument.

Weiterhin merkt er an, das Checked Exceptions tendenziell eher lokaler behandelt werden. In einer großen Applikation sollte es aber ein zentrales Fehlerhandling geben. Soweit folge ich seiner Argumentation noch. Aber: Auch in Java ist es möglich, Exceptions erneut zu werfen und sie – nach der lokalen Behandlung – dem zentralen Fehlermanagement zuzuführen. Insofern greift seine Aussage schlicht nicht. Dazu kommt, das der Ansatz, ohne Checked Exceptions zu arbeiten, eher zu globalen try-catch(Exception e)-Statements oder noch besser try-finally-Statements führt – Fehlerbehandlung und die gezielte Reaktion auf Ausnahmen sieht schlicht anders aus. Das ist – zugespitzt formuliert – schlicht schlechtes und amateurhaftes Programmieren, was am Ende des Tages die Applikation unzuverlässiger und langsamer macht. Nö, das Argument kann man so nicht stehen lassen.

3. Unübersichtlichkeit

Dieses Argument hängt mit dem Skalierbarkeitsargument zusammen: Es wird sehr unübersichtlich, jede mögliche Exceptionform zu behandeln. Aus diesem Grund gibt es bei .NET das try-finally-Konstrukt, was die Auseinandersetzung mit den verschiedenen Exceptions vermeiden helfe und somit der Übersichtlichkeit zugute komme. Sagt jedenfalls Anders Hejlsberg. Ich persönlich halte das für eine komplett falsche Strategie: Ich kann doch nicht schlicht die Fehler ignorieren, statt auf sie zu reagieren! Und selbst wenn meine Reaktion im Ignorieren bestünde, hätte ich mich wenigstens beim Schreiben des Codes damit auseinander gesetzt! Beim try-finally muß ich selbst das nicht (mehr) tun.

Zusammenfassend bleibt festzuhalten, das ich vom Verzicht auf die Checked Exceptions in .NET absolut nicht überzeugt bin. Je mehr Projekte ich mit .NET umsetze, um so mehr stört mich das. Ich meine, natürlich mache ich es so, wie man es sollte:

  • Eigene Exception-Typen definieren
  • Exceptions werfen und das auch im Kommentar anzeigen
  • Exceptions so gezielt und so nah wie möglich abfangen

Alles keine Frage, geschieht auch so. Aber irgendwie ist es – durch die Verwendung von Kommentaren und den Verzicht auf einen dedizierten Kompilerfehler – eben eine reine Kann-Geschichte. Damit sorge ich für Unsicherheiten und für unnötige Fehler, und genau das sollte ja eigentlich vermieden werden.

1 Kommentar » | .NET, Handwerk, Java, Meinung