Sonntag, 18. September 2011

Daten und Code

Der Mathematiker Bill Gosper hat einmal gesagt: "Data is just a dumb kind of programming."[1] Das widerspricht unseren Denkgewohnheiten: Wir finden es gut und richtig, Code und Daten sorgfältig voneinander zu trennen. Es gilt als unsauber, in den Code Daten aufzunehmen und als unmöglich, Daten direkt auszuführen: Sie können allenfalls von einem Programm eingelesen werden und die Programmausführung steuern - etwa in Form von Customizing.

Aber das Zitat von Gosper weist zuerst einmal darauf hin, dass Code und Daten näher beieinanderliegen, vergleichbarer sind als es den Anschein hat. Schliesslich besteht auch Programmcode aus Daten: Er ist eine Aneinanderreihung von Schlüsselwörtern, Symbolen, Literalen und besonderen Zeichen aus dem Vorrat einer Programmiersprache. Wie alle Daten ist Programmcode "nur" Information. Ein Compiler erzeugt aus Code ein ausführbares Programm: Das ist eine Abfolge von Bytes, die zur Laufzeit dem Prozessor (oder der virtuellen Maschine) zur Ausführung vorgelegt werden. Die Bits und Bytes der Op-Codes werden dann im Prozessor nach fest verdrahteten Regeln interpretiert und ausgeführt. Auch Codezeilen sind demnach Daten. Es gibt keinen grundsätzlichen Unterschied zwischen Customizingdaten, die von einem Programm eingelesen werden und seinen Ablauf beeinflussen, und Programmcode, der von einem Prozessor eingelesen und verarbeitet wird.

Da alle Änderungen in unserem System über das Transportwesen vom Entwicklungs- über das Konsolidierungs in das Produktivsystem gelangen und all diese Transporte in einer Liste dokumentiert werden, habe ich mir eine Liste der letzten 100 Änderungen in unserem System auf die Frage hin angesehen, welcher Art die durchgeführten Änderungen waren [2] – mit folgendem Ergebnis:
  • Etwa die Hälfte aller Änderungen wurden am Code vorgenommen.

  • Etwa ein Drittel der Änderungen betraf steuernde Daten: Customizing, Anwendungsparameter und transportrelevante Stammdaten.

  • Die übrigen Einträge der Liste führten teilweise gar nicht zu Transporten, weil sie reine Beratungsfragen darstellten oder ein nicht reproduzierbares Symptom beschrieben. Ferner gab es Transporte von Texten und Übersetzungen


Interessanter als "Ist es eine echte Fehlermeldung oder die Anforderung eines neuen Features?" finde ich jedenfalls die Frage, auf welcher Ebene und mit welchem Aufwand geändert wurde: Gibt es Änderungsarten, die günstiger sind als andere?

Jede Änderung bedeutet, dass der Wert einer bestimmten steuernden Grösse geändert wird - seien es Teile von Programmcode oder Einträge im Customizing. Die Frage nach dem Aufwand ist die Frage, wie gut man an diese Steuergrösse herankommt, wie gut der Kontext der Steuergrösse im System die Semantik dieser Grösse darstellt, wie kontrollierbar die Auswirkungen der Änderung sind und zuletzt: ob ein Entwickler nötig ist, um die Änderung durchzuführen.


  1. Lokalisierung der Steuergrösse Wie viel Aufwand benötige ich, um die Schraube zu finden, an der ich drehen muss?

  2. Isoliertheit Wie seiteneffektfrei ist die nötige Änderung?

  3. Darstellung Wie steuert die änderbare Grösse das System? Wie ist diese änderbare Grösse modelliert? Wie leicht ist diese Steuerung für den Leser des Modells (des Codes, der Customizingtabelle etc.) nachvollziehbar?

  4. Zuständigkeit Muss die Änderung durch einen Entwickler oder kann sie durch den Prozessberater, der die Änderung anfordert, selbst geleistet werden? Im letzteren Fall würde man sich eine Instanz sparen, was den Aufwand für die Durchführung der Änderung natürlich reduziert.


In komplexen Systemen, die einen Querschnittsaspekt der Betriebswirtschaft eines grossen Unternehmens abbilden, etwa dessen Logistik, kann eine steuernde Codestelle praktisch nicht mehr per Dokumentation ermittelt werden. Die Dokumentation kann bestenfalls noch einen groben Anhalt geben, in welcher Richtung (in welchen Programmen, Klassen, Funktionsgruppen) man suchen muss. Es wird für die Änderung ein konkreter Anwendungsfall benötigt, der die fehlgeschlagene Erwartung zeigt. Da in der Regel keine strikte, sondern nur die schwache Form von Code Ownership praktiziert wird, muss man als Nicht-Experte des jeweiligen Gebietes mit dem Debugger herausfinden, wie das System aktuell funktioniert und an welcher Stelle man es verändern müsste, um den Ablauf zu ändern. Ohne tiefere Kenntnis des Programmentwurfs, ohne Kommunikation mit den Programmautoren, ist das eine heikle Aufgabe, an der man grandios scheitern kann.

Der Code in einer 3GL-Programmiersprache ist meist ausführungsorientiert, nicht problemorientiert. Man muss sich beim Programmieren gewissermassen in das Ausführungsmodell der Maschine eindenken, und meist bleibt es dabei: Der Code entsteht in einer Anpassung an die Ausführungslogik der Maschine. Die Sichtweise von Jack Reeves "Der Code ist Design"[3], wonach der Programmcode vor allem als ein Text für menschliche Leser gedacht ist - und nur in einer speziellen Syntax formuliert ist, die die maschinelle Ausführung ermöglicht - dass Programmcode also ein maschinell ausführbares Designdokument ist, ist in den Köpfen noch nicht sehr verbreitet. Man klebt zu sehr an den Details der Maschine, hört zu schnell mit Code-Verbesserungen auf, sobald "es läuft" oder gibt zu schnell auf, wenn die Maschine nicht so reagiert wie erwartet, nur um dann an anderen Orten eine brutale Alternativlösung einzubauen.

Das leitet zum zweiten Thema über, der Isoliertheit einer Änderung. Je mehr ein System bereits durch Änderungen verschraubt ist, umso schwieriger ist es, Seiteneffekte zu überblicken. Das gilt vor allem, wenn der Code Wiederholungen enthält. Mit statischer Codeanalyse ist es meist zu aufwendig, alle Prozesse zu finden, auf die sich eine Änderung auswirkt. Die einzige Möglichkeit, die Seiteneffekte von Änderungen mit guter Qualität zu kontrollieren, wäre ein dichtes Sicherheitsnetz von automatischen Tests – Unit Tests ebenso wie Integrationstests. Je weniger automatische Tests existieren, umso grösser ist das mit einer Änderung verbundene Risiko, umso grösser wird bei jeder Änderung die Angst, unerwünschte Nebeneffekte zu produzieren. Das bremst auch den allfällig vorhandenen guten Willen zur Verbesserung von Codequalität!

Wie kann man die Darstellung von Code verbessern? DSLs sind ein Ansatz, um von ausführungsorientierter zu problemorientierter Programmierung überzugehen. Ich habe im Blog Domänenspezifische Programmierung in ABAP einige Beispiele gegeben, wie man Ausführungsdetails im Programmcode verbergen kann. Die DSL ist eine Ebene über der 3GL-Programmiersprache angesiedelt und steuert mit einer klareren, aber auch spezialisierteren Syntax bestimmte Teilaspekte des Systems. Das alles ist aber noch Zukunftsmusik, auch wenn sich im Bereich der Tools und Frameworks für DSLs einiges tut[4], und wir in ABAP das Business Rules Framework BRFplus als neues Werkzeug in unserem Instrumentarium erhalten haben.

Wer ist schliesslich für eine Änderung zuständig? Änderungen am Code müssen natürlich immer von einem Entwickler gemacht werden - bei schwacher Code Ownership von irgendjemandem aus dem Team. Ein SAP-Prozessberater kann zwar den Code anschauen, aber da er in seiner täglichen Arbeit an Prozessen und nicht am Ausführungsmodell einer Maschine orientiert ist, ist selbst das Lesen von Programmcode für ihn eine mühsame Übung. Anders sähe es aus, wenn der Code selbst prozessorientiert wäre, das steuernde Modell des Programms also aus dem für die blosse Programmausführung nötigen Code herausextrahiert wäre. Womit wir wieder beim DSL-Thema sind. Der Berater könnte dann, so wie er aktuell das Customizing seine Domäne gut kennt, auch ein in einer DSL formuliertes Programm gut kennen und Änderungen entweder vorschlagen oder sogar selbst durchführen.

In allen vier Punkten gewinnen die Daten – der "dumme Code" nach Gosper – gegenüber dem schlauen Code, dem 3GL-Programmcode, was die schnelle Änderbarkeit betrifft. Die für eine Änderung des Systemverhaltens nötige Customizingeinstellung ist in der Regel schneller lokalisiert als eine Codestelle. Sie ist auch leichter zu ändern und ist im Kontext – Viewpflege mit Anwendungsdoku und F4- und F1-Hilfe für das steuernde Feld – klar nachvollziehbar. Wenn nicht der Umweg über einen Entwickler gewählt werden muss, sondern der Anforderer der Änderung diese auch gleich selbst umsetzen kann, spart man weitere Zeit. Lediglich bei der Isoliertheit kann man bei einer Datenänderung ebenso in die Komplexitätsfalle laufen wie bei Codeänderungen. Wenn ich eine neue Auftragsart einführe, kann ich nicht wissen, an welchen Stellen auf die bestehenden Auftragsarten programmiert ist und die neue Auftragsart mit aufgenommen werden muss.[5] Egal ob Code oder Daten - zur zuverlässigen Kontrolle von Seiteneffekten hilft letztlich nur das Netz automatischer Tests.

Wenn sich das Verhältnis in der Changeliste umkehren würde: Wenn die Hälfte der Änderungen als Datenänderungen und nur ein Drittel als Codeänderungen gemacht werden müssten, hätte das bereits eine deutliche Verringerung der Aufwände zur Folge und würde das Unternehmen somit agiler machen: Es kann schneller auf Änderungen reagieren. Wir arbeiten an diesem Ziel, wenn wir, wo immer es möglich ist, die Steuerung des Programms – in der betriebswirtschaftlichen Begrifflichkeit, nicht in Begriffen der Maschine – aus dem Programmcode extrahieren und in geeignete Daten auslagern. Das können klassische Customizingtabellen sein, oder auch moderne Datenstrukturen wie die des Business Rule Frameworks, oder eine Instanz einer geeignet definierten DSL.


[1] Zitiert in dem Aufsatz von Charles Petzold, On-the-Fly Code Generation for Image Processing, in: Andy Oram, Greg Wilson (Eds.), Beautiful Code, O'Reilly, Cambridge 2007, S. 105.
[2] Über die Frage, ob es sich bei den Änderungen um "echte Fehler" oder um Entwicklungswünsche, Nice-To-Haves und dergleichen handelt, kann man sich in Meetings sehr schön erhitzen. Aber für die Reflexion sind diese Klassifizierungen uninteressant. Ich versuche, die vorbelasteten Ausdrücke "Bug", "Fehler", "Korrektur" zu vermeiden und spreche schlicht von "Änderungen".
[3] Jack W. Reeves, What is Software Design?, http://www.developerdotstar.com/mag/articles/reeves_design_main.html
[4] Siehe http://www.languageworkbenches.net/
[5] Die Ursache dafür ist also wieder der nicht überblickbare Code. Zwar kann man im Beispiel der Auftragsarten eine globale Konstante mit allen verwendeten Werten einführen. Man hat aber keine Garantie, dass sich jeder daran hält oder ob nicht ein zweiter Entwickler in Unkenntnis der Existenz der ersten einfach eine weitere, zweite Konstante mit derselben oder einer leicht abweichenden Wertemenge definiert hat. Und das ist kein theoretisches Beispiel!

Keine Kommentare :