Schluss mit defensiver Programmierung

Ein Muster von Trainingsteilnehmern, das ich häufig sehe, wenn wir die Resultate von Übungsaufgaben anschauen, ist das defensive Programmieren. Hier ein Beispiel:

Die Übungsaufgabe war die Function Kata fromRoman; es galt also, römische Zahlen in Dezimalzahlen zu konvertieren. Die zu schreibende Funktion lautete int fromRoman(string roman). In einer ersten Iteration sollten nur einziffrige römische Zahlen konvertiert werden, z.B. „X“ oder „D“.

Der Code, über den ich dann gestolpert bin, sah ungefähr so aus:

Zweierlei hat mich daran gestört, sogar sehr gestört.

  1. Es wurde vom Kontrakt abgewichen. Die vom „Kunden“ gewünschte Signatur sah int als Ergebnistyp für die Funktion vor, nicht int?. Ob das eine gute Entscheidung des „Kunden“ war, wäre zu diskutieren gewesen. Doch diese Diskussion fand nicht statt. Also war es die Aufgabe der Trainingsteilnehmer, die Funktion ohne nullable type umzusetzen.
  2. Im Code wurde eine Validation vorgenommen. Die Funktion prüft, ob überhaupt eine römische Zahl anliegt. Wenn nicht, reagiert sie ganz bewusst speziell. (Dass sie null zurückgibt, statt eine Exception auszulösen, ist eine Folge der Entscheidung für einen geänderten Ergebnistyp.)

Zu meiner Schande muss ich gestehen, dass ich in der Situation nicht ganz trainerprofessionell reagiert habe :-/ Ich war nicht ruhig, verständnisvoll, besonnen, sondern in meiner Verzweiflung etwas polterig. Leider. Sorry!

Vielleicht hat das ja nun aber auch etwas Gutes. Ich schäme mich und versuche auch auf diesem Weg eines Blogartikels, ein wenig Buße zu tun. Geschieht mir recht, nochmal Zeit zu investieren, um meinen Standpunkt klar zu machen. Jetzt in Ruhe.

Also, was hat mich denn so verzweifeln lassen? Meine Reaktion hatte ja nichts mit dem armen Teilnehmer zu tun, sondern nur mit mir selbst. In mir hatte sich etwas angestaut. Frust angesichts meiner Ohnmacht, dieses unselige Muster aus der Welt zu schaffen. Denn Stefan Lieser und ich sehen es wirklich in jeder Trainingsgruppe. Immer und immer und immer wieder fügen Entwickler dem Code, der unzweifelhaft geschrieben werden muss, weiteren hinzu. Die Begründung: „Das habe ich so gelernt. Das ist defensive programming.“

Was soll ich sagen…? Das macht mich sprachlos. Vielleicht ist es die Tragik, die dieses Gefühl noch verstärkt. Denn Entwickler wollen ja das Gute, sie bemühen sich um Qualität – und erreichen das Gegenteil. Sie optimieren lokal und temporär – indem sie im Moment des Codierens eine Qualitätssicherungsmaßnahme ergreifen –, doch verfehlen sie dadurch ein globales, langfristiges Qualitätsoptimum. Defensive Programmierung ist keine Clean Code Entwicklung!

Ok, was habe ich denn für ein Problem? Warum hat mich der obige Code lospoltern lassen? Weil defensives Programmieren das Geld des Kunden verschwendet.


GFU Clean Code Powerdays

In diesem Beispiel ist das ganz deutlich: Der „Kunde“ hat sich nicht gewünscht, dass eine Validation stattfindet. Das ist erstens an der beauftragten Funktionssignatur abzulesen; die enthält keinen nullable type. Das ist zweitens an der Liste der Testfälle abzulesen, die für die Funktion definiert waren. Es gab keinen Test, der die Übergabe von null oder leerer Zeichenkette (oder überhaupt irgendeiner ungültigen römischen Zahl) durchgespielt hätte. Das ganze Thema „ungültige Eingaben“ war sogar aus-drück-lich ausgeklammert.

Dennoch sehen sich Entwickler immer wieder bemüßigt, Zusatzaufwand zu treiben, um solche Fälle abzudecken. Sie ignorieren Anforderungen oder sie imaginieren sie. Ich weiß nicht.

Was ist der Antrieb dahinter? Geht es wirklich darum, dem Kunden mehr Qualität zu liefern? Ich glaube nicht. Eher vermute ich den-eigenen-Arsch-absichern als Beweggrund. „Soll keiner sagen, meine Funktion hätte aufgrund falscher Eingaben Müll produziert!“ oder so mag der Gedanke sein. Nicht Altruismus, sondern Egoismus treibt Entwickler, das Geld des Kunden in defensives Programmieren zu versenken. Sie selbst wollen sich Ärger in der Zukunft ersparen.

Das finde ich verständlich; sie haben bestimmt einige Negativerfahrungen gemacht, die sie zukünftig vermeiden wollen. Doch das Mittel ist aus meiner Sicht gänzlich falsch gewählt.

Noch schlimmer wird das bei defensiver Programmierung wie dieser:

Ja, das hat tatsächlich jemand empfohlen. Sehen Sie das Problem? Gruselig, oder? Was, wenn der Button tatsächlich null ist? Dann crasht das Programm nicht. Aber es funktioniert auch nicht. Vielmehr passiert stillschweigend nichts. Und das ist ganz bestimmt sehr, sehr überraschend für den Benutzer. Daraus kann nur ein hässlicher Supportfall werden. Wer führt über diese Kosten Buch? Der Entwickler ist fein raus: kein Crash. Doch der Rest der Organisation hat Schweiß auf der Stirn. Die Uhr tickt.

Das ist großer Mist.

Mehr Vertrauen!

Defensive Programmierung ist vielleicht ein Mittel in einer Welt von Einzelkämpfern, die niemandem vertrauen können. Es ist eine letzte Zuflucht in einer feindlichen Umgebung.

Doch das kann doch nicht die Vorstellung von einem produktiven Arbeitsplatz sein. Die Zusammenfassung von Menschen in einem Team, ihre Anstellung in einem Unternehmen soll ja gerade Kosten durch Misstrauen vermeiden. Unternehmen, Teams, Projekte definieren Horizonte des Vertrauens. Das dient der Reduktion von Komplexität.

Menschen, die sie in einer Kneipe treffen, untersuchen Sie auch nicht erst auf Waffen. Wechselgeld im Supermarkt prüfen Sie auch nicht erst auf Falschgeld. Zwischen vielen Ländern in Europa sind sogar die Grenzkontrollen abgeschafft. Sie können nach Dänemark oder in die Schweiz fahren, ohne eine Prüfung Ihrer Identität.

Solches Vertrauen macht das Leben einfacher. Wenn Sie einen Eindruck davon bekommen wollen, wie es zugeht, sobald wir dieses Vertrauen nicht mehr haben, lesen Sie z.B. dies. Eine schreckliche Welt!

Also: Die Bequemlichkeit und Effizienz der Welt, die wir schätzen, hängt von der Reichweite unseres Vertrauens ab.

Das ist uns im täglichen Leben bewusst, deshalb sind wir bereit, dafür etwas zu tun. Als Gesellschaft geben wir einiges Geld aus für Organe, die solche Verhältnisse erhalten. Polizei, Gerichte, Gesundheitsamt, TÜV sind nur einige.

Nur die Softwareentwicklung scheint das noch nicht begriffen zu haben. Denn nach Aussage des Teilnehmers, der für mich der Anlass zum Poltern war (Sorry, again!), hat er die defensive Programmierung in der Ausbildung beigebracht bekommen. Das Misstrauen ist also Lehrmeinung. Der Wilde Westen lebt noch in den Köpfen der Lehrer. Kein Entwickler ist sicher vor einem anderen, deshalb müssen alle fleißig proaktive Verteidigungsmaßnahmen ergreifen?

Wie nun aber richtig?

  1. Der Kunde bestimmt, was Anforderung ist.
  2. Vertrauensgrenzen sind explizit zu ziehen.

Vertrauensgrenze Benutzerschnittstelle

Der vornehmste Grund, um ein Validation von Input vorzunehmen ist der Wunsch des Kunden. Das bedeutet, als Entwickler müssen wir mit dem Kunden reden. Horribile dictu! Ja, reden. Wir müssen mit ihm über Schnittstellen reden. Nur an denen ist er ja interessiert. An Schnittstellen zeigt sich für ihn das Verhalten der Software, die er beauftragt.

Die offensichtlichste Schnittstelle ist die Benutzerschnittstelle: Menschen geben Daten ein, die Software antwortet mit Daten. Aber auch Service-Schnittstellen gehören dazu. Die werden von anderer Software genutzt, drehen sich jedoch auch nur um Input-Daten (request) die in Output-Daten (response) transformiert werden sollen.

Mit dem Kunden ist abzusprechen, welcher Input zu welchem Output führen soll. Der Kunde ist in diesem Fall wirklich mal König. Der zahlt, der sagt an. Das war im obigen Beispiel eindeutig der Fall.

Das bedeutet: Was er nicht ansagt, dafür will er im Zweifelsfall nicht zahlen oder noch nicht zahlen. Wenn Entwickler trotzdem Code schreiben für Verhalten, das der Kunde nicht gewünscht hat, dann verschwenden sie sein Geld. Massiv. Trotz allerbester Absichten. Sie handeln im besten Fall paternalistisch, im schlechteren arrogant.

Die korrekte Verhaltensweise für Entwickler ist: Wenn Sie eine mögliches Validitätsproblem sehen, dann melden sie das dem Kunden. Es gilt das ehrwürdige militärische Prinzip: Melden macht frei.

Ich kritisiere also nicht den Gedanken „Hier könnte aber etwas falsch laufen, wenn null übergeben würde.“ Im Gegenteil: Das ist sensibel beobachtet! Sehr gut!

Nur aus dem guten Gedanken ist dann im Training keine gute Maßnahme geflossen. Die wäre gewesen, mich als „Kunde“ darauf anzusprechen. Dann hätte ich wiederholen können, dass ich (derzeit) keine Behandlung invalider Daten wünsche oder hätte mich überzeugen lassen, dass das eine gute Idee ist. In dem Fall hätten wir zumindest ein Beispiel für unter diesen Umständen von mir erwünschtes Verhalten notiert und ggf. den syntaktischen Kontrakt angepasst.

Der typische Einwand gegen dieses Vorgehen: „Ich kann den Kunden doch nicht mit all solchen Kleinigkeiten belasten. Der erwartet, dass ich sowas selbstständig mache.“

Da ist etwas dran. Der Kunde hat tatsächlich eine Erwartung an unsere Kompetenz als Entwickler. Doch die ist nicht, dass wir alle Sicherheitsmaßnahme selbstständig ergreifen. Das gäbe es nämlich kein Halten mehr. Sicherheit ist unersättlich. Ohne Kontrolle stürzen wir in ein Sicherheitskaninchenloch.

Die Erwartung des Kunden ist vielmehr – zumindest, wenn er kostenbewusst ist -, dass wir ihn informieren. Mit unserer Kompetenz sollen wir ihn auf mögliche Probleme aufmerksam machen. Dann kann er entscheiden, wie zu verfahren ist. Nur so sichern wir uns auch wirklich ab gegen spätere Kritik des Kunden.

Kunden haben Prioritäten. Da mag eine Input-Behandlung mit Lücken dringender sein als eine ohne. Denn mit Lücken ist ein Durchstich schneller hergestellt. Entscheidet ein Entwickler selbstständig, lückenlos zu programmieren, sinkt die Geschwindigkeit in Bezug auf das, was dem Kunden am Herzen liegt.

Der Aufwand für defensive Programmierung ist ja nicht zu unterschätzen. Das ist Logik. Die muss zuerst hingeschrieben werden, dann muss sie getestet werden. Das sind pro Funktion 2-3 Minuten. Bei 10 Funktionen sind es schon 30 Minuten. Wenn das 3 Entwickler pro Tag tun, sind es 90 Minuten. Schnell werden also Hunderte Euro durch defensive Programmierung verbrannt, die kein Kunde gewünscht hat.

Kleine Übung: Wer defensiv programmiert, tut einfach 5€ pro Maßnahme in die Teamkasse. Wie wäre das? :-)

Dazu kommt der Aufwand beim Lesen. Vielleicht ist der defensive Code noch schnell hingeschrieben. Doch er wird ja viel, viel häufiger gelesen als geschrieben. Jedes Mal ist er dann zu verstehen. Das kostet einen kleinen Aufwand. Defensiver Code verrauscht den eigentlichen Code. Er ist dessen Verständlichkeit abträglich. Die Wandelbarkeit von Software sinkt also durch defensiven Code, der ohne Auftrag geschrieben wird. Mittelfristig schießt sich der Entwickler, der ihn schreibt, damit sogar in den eigenen Fuß.

Vertrauensgrenze Modulschnittstelle

Absicherungscode, der einer expliziten Kundenanforderungen folgt, ist nun eigentlich gar kein defensiver Code mehr. Er ist ausdrücklich gewünscht. Es findet keine ängstliche Selbstverteidigung mehr statt.

Echt defensiver Code ist demgegenüber nie vom Kunden ausdrücklich gewünscht. Er erfüllt keine funktionale Anforderung und auch keine nicht-funktionale Effizienzanforderung. Vielmehr ist er zur Herstellung der Investitionssicherheit gedacht und fällt damit in einen Bereich, den der Kunde eher nicht im Blick hat. Defensive Programmierung ist eine verzweifelte Maßnahme Einzelner, um noch etwas Korrektheit und Produktivität zu retten. Ach, wie tragisch…

Doch woher kommt diese Verzweiflung? Woher das Misstrauen? Es ist ein Resultat mangelnder Kommunikation. Denn da, wo nicht kommuniziert wird, da bleiben und entstehen weiße Flecken. Man weiß nicht, was der andere denkt und tut. Unsicherheit greift Raum, dann Misstrauen – allemal wenn es zu negativen Erfahrungen kommt -, schließlich unternimmt man persönliche Absicherung.

Das, was sich auf gesellschaftlicher Ebene abspielt, spiegelt sich im Kleinen in Teams, in denen nicht ausreichend kommuniziert wird.

In Dresden und anderswo mag es helfen, zu versuchen, Misstrauen durch mehr Gespräche in Kaffeeküchen zu überwinde. Integration beginnt mit einem freundlichen Wort und einem offenen Ohr.

Für Softwareteams reicht das jedoch nicht aus. Sie bedürfen systematischerer Kommunikation.

Test-first

Die erste Maßnahme in dieser Hinsicht ist test-first Codierung. Tests sind die zentrale Dokumentation für gewünschtes Verhalten. Wer nachschauen will, wie Software benutzt werden soll, der studiert automatisierte Testfälle. Sie stellen eine lebendige Dokumentation des Soll-Zustands dar. Damit dienen sie nicht nur der Absicherung gegen Regressionen, sondern auch der Kommunikation im Team. Niemand soll sagen können, er hätte nicht gewusst, wie korrekte Parameter für eine Funktion aussehen.

Und wenn die vorhandenen Tests eine Frage zum Softwareverhalten nicht beantworten… dann schreibt man einen weiteren Test, der exploriert, wie sich Software de facto bisher verhält. Vielleicht ist das schon ausreichend, vielleicht regt das aber auch weitere Kommunikation an.

Nicht nur automatisierte Tests sind jedoch wichtig, sondern sogar sie zuerst zu schreiben, also vor dem Produktionscode. Erstens werden sie dann nicht so leicht vergessen, zweitens bietet ihre Codierung Gelegenheit, sich aus Sicht eines Softwarenutzers Gedanken darüber zu machen, was denn eigentlich passieren soll. Man lügt sich weniger in die Tasche durch schon vorhandene Kenntnis „der Innereien“. Weniger Bestätigungsfehler.

Entwurf

Und woher kommen die Schnittstellen, auf die automatisierte Tests angesetzt werden? Sie sind das Ergebnis eines expliziten Entwurfs. Womit ich beim großen Sorgenkind der Softwareentwicklung bin.

Im Fehlen eines expliziten Entwurfs, der diesen Namen verdient, sehe ich den Hauptgrund für die Praxis der defensiven Programmierung. Es wird nicht gelehrt, wie explizit und systematisch vor der Codierung entworfen werden kann. Damit entsteht in der Kommunikation im Team eine eklatante Lücke. Einzelne Entwickler werden zu schnell sich selbst überlassen. Das treibt sie in die Unsicherheit. Dann wollen sie ihren Arsch absichern.

Ein systematischer Entwurf von Software durch das Team bestimmt die wesentlichen Module zur Realisierung anstehender nicht-funktionaler Anforderungen. Sein Ergebnis sind Funktionen, Klassen, Komponenten, Services mit klaren Schnittstellen und Beziehungen.

Weniger geht gar nicht.

Und dann werden in dieser Struktur ausdrückliche Vertrauensgrenzen gezogen. Das Team definiert gemeinsam, zwischen welchen Modulen Misstrauen herrschen soll. Denn Misstrauen hat auch etwas Gutes: es entkoppelt.

Dadurch entsteht eine interne Anforderung zur Herstellung von Wandelbarkeit. Die ist zu erfüllen mit Code, der über die Verhaltensanforderungen des Kunden hinausgeht. Das ist völlig ok, ja sogar notwendig. Das erwartet der Kunde zurecht vom Team.

Doch Achtung: Es ist eine Teamentscheidung, wo diese Grenzen verlaufen. Einzelne Entwickler kompensieren hier wieder nicht ihre persönliche Unsicherheit. Deshalb würde ich auch in diesem Fall nicht von defensiver Programmierung sprechen.

Selbstverständlich hält das Team solche Entscheidungen fest: in Form von syntaktischen und semantischen Kontrakten. Nichts anderes sind Funktionssignaturen bzw. Interfaces und Tests.

Interne Vertrauensgrenzen manifestieren sich damit geplant im Code. Sie sind keine ad hoc Entscheidungen. Einzelne Entwickler sind bei der Codierung von Logik in Funktionen nur für deren Korrektheit zuständig. Ihre Aufgabe ist es in dem Moment nicht, darüber zu spekulieren, wie irgendwer diese Funktion benutzen könnte.

Nutzungsspekulationen sind Sache des Entwurfs und damit des Teams.

Fazit

Schluss mit defensiver Programmierung! Sie ist ein Relikt aus Zeiten, da man noch glaubte, man müsse als Softwareentwickler nicht reden. Doch kein Softwareentwickler ist eine Insel.

Softwareentwicklung ist ein „Teamsport“. Der funktioniert nur, wenn man sich vertraut. Dazu gehört gekonnte Kommunikation und geeignete Dokumentation von Entscheidungen.

Wo defensive Programmierung noch an der Tagesordnung ist, da fehlt es an diesen Kompetenzen. Das Resultat: geringe Produktivität, Verschwendung von Kundengeldern. Traurig, traurig.

Doch zum Glück gilt: Gefahr erkannt, Gefahr gebannt. Es ist nie zu spät, mit besserer Kommunikation zu beginnen und untaugliche Gewohnheiten abzulegen. Now is the time!

Entwurf und test-first Entwicklung lassen sich lernen. Es winkt Entspannung durch mehr Vertrauen.

Posted in Clean Code Development, Grundsätzliches.