Dies ist meine Philosophie über Programmierung. Es gibt viele davon, aber diese hier ist meine.

Jeder Programmierer entscheidet mit jeder Zeile, die er oder sie schreibt, wie er oder sie ein Problem lösen möchte. Verwirrenderweise gibt es dabei oft keinen „richtigen“ oder „falschen“ Weg (ausgenommen natürlich von handwerklichen Fehler, die absolut betrachtet falsch sind). Viel mehr entwickelt jeder Programmierer eine Meinung, warum bestimmte Wege besser sind als andere. Diese Meinung wiederum basiert auf Attributen, die einem wichtig erscheinen.

Kalaschnikow?

Die Sturmgewehr-Familie, die mit der Kalaschnikow AK-47 begründet wurde, erfreut sich auch nach über siebzig Jahren Produktion immer noch einer überragenden Verbreitung. Im Gegensatz zu deutlich moderneren, fortschrittlicheren Konkurrenzmodellen überrascht die Langlebigkeit dieses Produktlinie – und die Anzahle der Kopien und Derivate, die seitdem entwickelt wurden.

Der AK-47 werden viele Dinge nachgesagt: Sie sei günstig, einfach zu produzieren, robust, geht tolerant mit Fehlbehandlung um und kann trotzdem gute Ergebnisse erzielen. Sie kann auch von wenig versierten Personen benutzt und repariert werden. Wie viele Konstruktionen aus sowjet-russischer Produktion ist die AK-47 unverwüstlich.

Unabhängig davon, wofür eine Waffe also solche und diese im Speziellen steht, kann man von diesem Gewehr eine Menge für die Programmierung lernen.

Kalaschnikow-Programmierung

Für gute Programmierung existieren Unmengen an ausformulierten best practices,Coding-Standards oder Anleitungen. Diese im Detail zu kennen ist aber gar nicht so notwendig, wenn hinter jeder Entscheidung bei der Programmierung eine konsistente Philosophie steckt – wie zum Beispiel die Kalaschnikow-Programmierung.

Im Detail sind diese Ideen einfach zu erklären, wenn wir uns als Programmierer folgende Dinge vor Augen führen:

  • Unser Code wird auf Situationen treffen, die wir nicht vorhergesehen haben. Falsche Werte, zu viele Werte, zu wenig Werte, abbrechende Verbindungen, Fehlbenutzung… als dies sind Dinge, die wir abfangen müssen. Als Programmierer können wir uns nicht auf schlechte Umstände berufen, sondern müssen wie ein Anschnallgurt in einem Auto oder die Displayfolie auf einem Smartphone davon ausgehen, das früher oder später etwas Schlimmes passieren kann.
  • Unsere Programmierung kann nicht alle Fehlerfälle vorhersehen und abfangen. Deswegen muss sie auch nachträglich verbessert werden können – durch uns, oder einen anderen Programmierer.
  • Wir arbeiten fast immer in Teams. Unser Code wird von Programmierern gewartet oder verändert werden müssen, die nicht das selbe Wissen wie wir haben – oder nicht die selben Tools. Ironischerweise können wir selber dieser Programmierer sein: Schmökert doch mal in Programmierung, die ihr vor zwei Jahren produziert habt, und versucht die für die Entwicklung notwendigen Tools aufzusetzen.

Deshalb muss für mich persönlich Programmierung die folgende Attribute besitzen – die den Attributen der Kalaschnikow-Gewehr-Familie ähneln:

  1. Sie muss eine Aufgabe präzise und möglichst wenig komplex lösen. Nicht weniger… aber auch nicht mehr.
  2. Sie muss tolerant mit Fehlern umgehen – und sinnigerweise im Falle eines Fehlers den Benutzer oder zumindest einem Programmierer mitteilen können, was der Fehler war.
  3. Sie muss verständlich sein, damit sie auch von anderen Programmierern mit anderem technischen Verständnis oder Tools über einen längeren Zeitraum hinaus gewartet werden kann.

Diese drei Kernideen, einmal verinnerlicht, erlauben eine ganze Menge Entscheidungen zu fällen – ohne jeden Handgriff vorher explizit durchdenken bzw. erforschen zu müssen. Deswegen nenne ich diese Philosophie Kalaschnikow-Programmierung.

Einfachheit und Verständlichkeit in der Programmierung…

Die Genialität einer Konstruktion liegt in ihrer Einfachheit. Kompliziert bauen kann jeder.

Sergej P. Koroljow

Als Programmierer lieben wir Abkürzungen, die uns schneller und produktiver machen. Und außerdem mögen wir es auch, unsere Genialität und Professionalität in unserer Arbeit zu beweisen. Das Problem entsteht, wenn wir unsere Arbeit von zu vielen, zu komplexen Ideen abhängig machen.

In diesem Sinne sollten wir nicht nur bei der Benennung von Komponenten daran denken, dass auch andere Programmierer diese verstehen können müssen. Auch übermäßig komplexe Sprach-Konstrukte oder wenig bekannte Mechanismen bzw. Funktionen müssen vermieden werden. Viele Programmiersprachen bieten für einige Problem wenig bekannte, sehr elegante Lösungen an – die aber leider dann kaum jemand versteht. Das Wort „obskur“ sollte niemals den Zustand von Programmierung beschreiben dürfen.

Wenn also eine solche Lösung verwendet wird, sollte diese zumindest dokumentiert werden – besser ist es aber gegebenenfalls, diese Lösung durch ein wenig komplexes, dafür gerne auch längeres Stück Programmierung zu ersetzen.

Wer keine Dokumentation für seine Programmierung hinterlässt, möchte in seinem Urlaub telefonisch Fragen dazu beantworten.

Vor geraumer Zeit war ich noch der Meinung, dass Programmierung ohne Inline-Dokumentation eine schlechte Angewohnheit ist. Inzwischen glaube ich das Gegenteil: Wenn eine Programmierung einer Erklärung für den nächsten Programmierer bedarf, ist sie möglicherweise einfach nur zu komplex und sollte unbedingt umgeschrieben werden.

Wir neigen dazu, Over-Engineering zu betreiben, wenn wir uns hinlänglich bekannte Probleme lösen. Um mögliche zukünftige Probleme vorweg zu nehmen, erzeugen wir oft erheblichen Aufwand – nur um später festzustellen, dass wir einerseits dieses angenommene Problem nie hatten, und andererseits die von uns entwickelte Lösung niemals weiter verwendet worden war. Hier sollte Pragmatismus und Augenmaß unser Leitbild sein.

Nicht zuletzt müssen wir uns bei überbordender Komplexität fragen, warum wir diesen Aufwand betreiben. Gemäß dem Pareto-Prinzip kann eine einfache, stabile Lösung kurz- wie auch langfristig weniger Aufwand verursachen – und ist damit in der Herstellung nicht nur einfacher, sondern auch günstiger.

…und Einfachheit bei den Werkzeugen

Alles sollte so einfach wie möglich gemacht werden, aber nicht einfacher.

Albert Einstein

Vor geraumer Zeit benötigte ein Webentwickler nur einen Webserver mit einem Scriptinterpreter, einen Datenbankserver, und einen Code-Editor. Diese Zeiten sind längst passé, und wir erweitern die notwendige Technologie immer wieder um neue Komponenten, die unsere Arbeit angenehmer und schneller macht – solange diese Technologie funktioniert. So kann es heute notwendig sein, für die Entwicklungsarbeit an einem Web-Projekt Docker und die passenden Docker-Images, Gulp oder Grunt, dazu NodeJS, vielleicht noch SASS, Git, lokale Linter (um nicht mit dem automatischen Linter im Continuous Integration zu kollidieren), Editorconfig für den richtigen Code-Style und Unmengen an weiterer Tools zu benötigen.

Wir, die wir diese Tools in jahrelanger Arbeit zusammengetragen, eingerichtet und später dann auch verstanden zu haben, haben damit für andere Entwickler in unserem Projekt eine Einstiegshürde geschaffen. Schlimmer noch können wir Teile in unsere Toolchain eingeführt haben, die ein Verfallsdatum haben und nach einer gewissen Zeit ein Projekt nur noch schwer wartbar machen. Und nicht zuletzt kann ein Defekt in einem Teil unserer Toolchain die sorgsam aufeinander gestapelten Abhängigkeiten zum Einsturz bringen. Wenn z.B. NodeJS nicht funktioniert, funktioniert Gulp nicht, funktioniert unser SASS-Compiler nicht, gibt es kein CSS.

Wenn wir schon für unseren Entwicklungsprozess eine komplexe Toolchain erfordern, müssen wir (z.B. in der README.md des Projekts) die grundsätzlichen Anforderung und die Installation dieser Tools erläutern. Im Idealfall liefern wir ein Installations-Skript mit, dass alle notwendigen Abhängigkeiten und Tools installiert. Wenn man ein paar mal solche Anleitungen oder Skripte geschrieben hat, wird man vielleicht verstehen, welches Erbe man zukünftigen Programmierern hinterlassen hat.

Welches Problem löst das – und welches Problem verursacht das?

Weitere Ideen und Inspiration zu diesem Thema finden sich in dem Artikel „Zurück zur Werkbank“.

Unser Verhältnis zu Code… und anderen Menschen

Gerade wenn wir an großen Projekten oder in einem Team arbeiten, sind wir als Programmierer einerseits gefordert, unsere fundierte Meinung einzubringen. Andererseits ist aber für das Gelingen unserer Programmierung notwendig, dass alle Beteiligten das selbe Verständnis für die Anforderung an die Umsetzung eines solchen Projektes haben.

Als Programmierer können und sollen wir eine eigene Meinung zu Tools, Coding-Styles, Dokumentations-Standards oder Testing haben. Diese persönliche Meinung wird aber in Teams bzw. in Projekten immer nachrangig zu beschlossenen Standards sein. Wenn wir persönlich der Meinung sind, dass Tabs besser geeignet sind zum Einrücken, im Projekt bisher aber immer vier Leerzeichen verwendet wurden, müssen wir aus Gründen der Konsistenz und Erwartbarkeit von Code uns an diese Vorgaben halten. Das macht unsere eigene Meinung nicht schlecht oder überflüssig. Aber eine Abweichung von der Norm ist in einem komplexen Umfeld immer eine Quelle für Fehler. Das Prinzip des Clean Code ist hier eine äußerst gute Grundlage.

Wenn uns geschlossene Beschlüsse nicht gefallen, sollten wir auf eine Änderung hinwirken. Wenn eine Änderung beschlossen wurde, müssen wir diese aber auch konsequent umsetzen. Mitten in einem Projekt einzelne Teile in einem anderen Zeichensatz oder mit einem neuen Datenbank-Adapter zu betreiben löst an der Stelle, an der wir den Code verwenden, vielleicht einiges an Problemen – aber der nächste Programmierer steht auf einmal vor zwei verschiedenen Wegen, wie ein Problem gelöst werden kann. Dies ist eine Quelle für Missverständnisse und Fehler, sowie für langsam vor sich hinrottenden Code.

Einsame Entscheidungen können wir nur für Projekte treffen, bei denen nur wir betroffen sind. Sobald Kunden, Nutzer oder andere Programmierer involviert sind, müssen wir uns auch über ihre Bedürfnisse klar werden.

Fehlertoleranz

Wir sollten niemals erwarten, dass eine Variable den Wert enthält, den wir annehmen. Bei einer Nutzereingabe ist dies offensichtlich, bei einer Abfrage aus einer Datenbank oder einer Schnittstelle fällt uns diese Annahme schon deutlich schwerer.

Dabei geht es gar nicht so sehr um korrektes Quoting – wobei inkorrektes Quoting nicht nur Fehler, sondern gegebenenfalls auch Sicherheitsprobleme auslösen kann. Viel mehr geht es darum, Fehlerbehandlung als einen Weg zu begreifen, der auch die Performance steigert. Die Prüfung auf das Vorhandensein eines Werts kann es erlauben, frühzeitig aus einem komplexen Programmteil zurückzukehren, ohne mit falschen Grundannahmen eine aufwändige Berechnung durchzuführen. Auch im Bereich des Templatings kann die Prüfung auf eine leere Variable die Ausgabe ganzer Template-Teile überflüssig machen.

Unsere Programmierung kann auch auf andere Art und Weise fehlertoleranter werden. In fast jeder Sprache erlauben Funktionen eine Bereinigung von Variablen, zum Beispiel in dem umschließende Leerzeichen mittels trim entfernt werden, oder ein String per Casting sicher in eine Zahl verwandelt werden kann. Die einfache Konvertierung in erwartbare Werte kostet uns als Programmierer und unser Programm meist deutlich weniger Aufwand als den Nutzer die Korrektur einer fehlerhaften Eingabe abnötigen würde – und erspart möglicherweise die Programmierung eines Prozesses, der den Nutzer zur Fehlerkorrektur auffordert.

Nicht zuletzt können wir mittels Mustererkennung bzw. -ersetzung Fehler erkennen und vielleicht sogar unbemerkt beheben. Wenn der Nutzer bei der Eingabe einer Telefonnummer oder Kreditkartennummer keine anderen Zeichen als Ziffern eingeben soll, können wir als Programmierer diese Nicht-Ziffern-Zeichen doch problemlos entfernen – und müssen diese stupide Aufgabe nicht unseren Nutzern auferlegen.

Wenn wir eine Fehlermeldung oder Exception-Message schreiben, muss diese Fehlermeldung außerhalb des lokalen Kontextes Sinn ergeben. Die Fehlermeldung „Ein Fehler trat auf, wenden Sie sich bitte an Ihren Administrator“ ist besonders dann frustrierend, wenn man selber der Administrator ist. Benutzen wir doch die von jedem Programmierer einforderbare Fähigkeit Dinge zu beschreiben auch für Fehlermeldungen, und verwandeln ein „Connection fail“ in ein „Der HTTP-Aufruf https://www.example.com scheitert mit Status-Code 503.“. Niemand beschwert sich über zu aussagekräftige Fehlermeldungen.

Weitere Ideen und Inspiration zu diesem Thema finden sich in dem Artikel „Die Verantwortung von Software“.

Fazit

Die Menge an praktischen Handlungsempfehlungen aus den drei Kernideen der Kalaschnikow-Programmierung sind deutlich vielfältiger, als dieser Artikel auch nur im Ansatz beleuchten kann. Dementsprechend sind die hier aufgeführten Anekdoten eher Beispiele – aber nur die Spitze des Eisbergs. Vielmehr ist bei der Kalaschnikow-Programmierung das Grundverständnis bzw. die Grundhaltung die entscheidende Konstante, von denen ausgehend Entscheidungen getroffen werden können.

Ich persönlich schaffe es auch nicht immer, mich an diese Idee zu halten. Aber sie führt mich immer wieder zurück, und hilft mir bei der Entscheidungen zwischen ansonsten technisch gleichwertig korrekten Lösungen.