Samstag, 6. Juni 2009

Software-Steinzeit

In jüngster Zeit musste ich an einigen alltäglichen Beispielen erfahren, auf was für einem niedrigen Niveau die Softwareentwicklung leider noch steht. Es sind einige kleine, alltägliche, aber daher wohl leider auch typische Beispiele:

  • Ich besitze eine Zehnerkarte fürs Winterthurer Schwimmbad "Geiselweid". Eines Tages meldete der Einlass-Automat "Karte nicht lesbar". Aufgrund der Meldung befürchtete ich, dass die Karte beschädigt und ich um Punkte geprellt war und wandte mich an die Verkäuferin. Die winkte ab: "Das bedeutet nur, dass Ihre zehn Punkte aufgebraucht sind und Sie die Karte neu laden müssen." Hier war offensichtlich ein Kollege sparsam mit Fehlermeldungen gewesen. Ich sehe den Code im zugehörigen "embedded C" schon vor mir:

    if (! karte_lesen_und_punkt_verbrauchen( ) ) {
    send_error( "Karte nicht lesbar" );
    }

    Häufig wird das Fehlerhandling als ein Extra angesehen, das man immer noch hinzufügen könnte, wenn nach Implementierung der Kernfunktionen noch Zeit bleibt. Vielleicht hat das Team bis zum Projektende aber nur den Geradeausfall geschafft: Der Punkt von der Testkarte wurde beim Eintritt ins Schwimmbad sauber abgebucht. Dann knallten die Sektkorken. Um danach noch ein sauberes Fehlerhandling einzubauen, fehlten Zeit, Mittel und Motivation. Vielleicht fand der zuständige Entwickler es auch einfach zu langweilig, jeden Fehler mit einer separaten Fehlermeldung zu versehen.

  • Noch ein Beispiel von derselben Baustelle: Bei einer anderen Gelegenheit (und mit einer anderen Karte) war es der freundlichen Verkäuferin im Schwimmbad nicht möglich, mir den Punktestand der Mehrfachkarte zu sagen. Die einzige Möglichkeit wäre gewesen, sie in den Einlass-Automaten zu legen und somit einen Punkt zu verbrauchen: Danach wird die Anzahl der verbleibenden Restpunkte angezeigt. Es gab also wirklich nur - wie im obigen Code - die Funktion karte_lesen_und_punkt_verbrauchen(). Die Funktion karte_lesen() für sich war nicht im Angebot! Vielleicht hat der Projektleiter damals darauf verwiesen, dass eine Transaktion zum blossen Auslesen des Punktestands nicht Teil der Vereinbarung war und separat verrechnet werden müsste. Nach der territio mit Hilfe einer neuen Aufwandsschätzung nahm die Badeanstalt dann wohl von weiteren Schritten Abstand.

  • Die Fluglinie easyjet bietet ihre sehr billigen Flüge übers Internet zur Buchung an. Um sich lästiges Handling zu ersparen (Rechnungsversand, Mahnwesen, ...), bietet sie als einzige Zahlungsmöglichkeit die Buchung mit Kreditkarte an. Als ich versuchte, einen Flug mit meiner Kreditkarte zu buchen, brachte das System einen seltsamen Fehlercode ixp02, verbunden mit der Aufforderung, mich an den Support zu wenden. Nach Abklärung meiner Kreditkarte beim zuständigen Geldinstitut habe ich das getan: Im Lauf von zwei Wochen erfasste ich fünf Supportfälle zu diesem offensichtlichen Softwarefehler. Auch probierte ich bis zu zweimal täglich, den Flug zu buchen - ohne Erfolg. Eine Supporterin hatte es tatsächlich einmal geschafft, einen der zuständigen Programmierer auf das Problem anzusetzen. Und es änderte sich ganz kurz etwas: Statt der alten Bekannten ixp02 bekam ich für kurze Zeit einen HTTP-500-Abbruch ("Interner Serverfehler")! Der Programmierer hat also tatsächlich etwas geändert - dann gab es Abbrüche, er bekam kalte Füsse und kehrte schleunigst wieder zur früheren Version zurück! Ich musste schliesslich, damit der Preis des Flugs nicht ansteigt, das Ticket bar am Züricher Flughafen erwerben. Dabei zahlte ich 90 Franken Aufpreis, denn, so der freundliche Angestellte im Flughafen: "Sie können sich das Geld ja sparen, wenn Sie das Ticket im Internet buchen."

    Manche, denen ich diese Geschichte erzählt habe, vermuteten Geldgier als Motiv – als hätte es easyjet darauf angelegt, mit diesen Barverkaufs-Zuschlägen Profit zu machen. Ich persönlich glaube das nicht. Denn allein die Support-Arbeitsstunden, die ich der easyjet beschert habe, dürften weit mehr als 90 Franken gekostet haben. Ich glaube, hier hat der Faktor "Software-Steinzeit" zugeschlagen: Husch-husch entwickelte Applikationen, möglicherweise noch nicht einmal ein Testsystem zum Ausprobieren von Korrekturen: Darauf deutet der temporäre Absturz des Produktivsystems, als ein Programmierer sich daran machte, etwas zu ändern. Auf so etwas wie ein Testprogramm zu hoffen, mit dem der Code der Applikation unabhängig von einem realen Kredtikartenserver getestet werden kann, wäre geradezu naiv.

  • Vor kurzem besuchte ich die Dreigroschenoper im Züricher Schauspielhaus Pfauen. Vor Beginn der Aufführung wurde das Publikum darauf aufmerksam gemacht, dass es zu technischen Schwierigkeiten mit den Kulissen kommen könnte, da die Informatik bei den Proben mehrfach versagt hätte und es keine Umschaltung auf manuellen Betrieb gäbe - es gäbe also bei einem Ausfall keine andere Lösung, als die Störung in der Software zu beheben. Es könnte daher sein, dass während der Aufführung Techniker auf die Bühne kämen, die nicht zum Stück gehörten und bei deren Anblick wir uns nichts denken sollten. Sie würden auch von den Schauspielern ignoriert. Glücklicherweise funktionierte die Kulissentechnik an jenem Abend störungsfrei. Bemerkenswert fand ich, dass mit der Umstellung auf automatische Kulissenheber ein manueller Betrieb gar nicht mehr möglich ist.

  • In die gleiche Richtung geht es, wenn Automodelle entwickelt werden, die selbständig den Motor abschalten, wenn die Software der Meinung ist, dass ein Motorschaden vorliegt. Stellen Sie sich vor, wie der Mechanismus aktiv wird, während Sie gerade auf der Überholspur einer Autobahn unterwegs sind. Schon die elektrischen Fensterheber in Autos ärgern mich, wenn die Fenster nicht alternativ auch im Handbetrieb gekurbelt werden können.


Was ist das Gemeinsame an all diesen Beispielen? Es ist die Abhängigkeit von unzulänglicher Software. Softwareprodukte sind oft fehlerhaft, werden zugleich aber masslos überschätzt, sonst würde man es sich nicht erlauben, sie in Alltagsabläufen ohne Alternativen oder menschliche Eingriffsmöglichkeit einzusetzen.

Was sind die Ursachen für die Fehleranfälligkeit heutiger Software? Was läuft schief im Entwicklungsprozess? Dies ist oft diskutiert worden, auch in diesem Blog habe ich mir schon Gedanken darüber gemacht (Änderbarkeit, Tests). Konzepte zur Reduktion von Fehlern liegen schon lange vor: Schon das objektorientierte Programmieren und die konsequente Verwendung von Entwurfsmustern würden helfen, Fehler zu vermeiden und wartungsfreundlicheren Code zu produzieren. Obwohl die Objektorientierung schon in den 60er Jahren in der Sprache Simula präsent war, ist sie in der Praxis immer noch nicht in den Köpfen. Auch mit modernen objektorientierten Sprachen wie Java und C++ wird viel zu oft rein prozedural programmiert, als hätte es Objektorientierung und Entwurfsmuster nie gegeben oder als handelte es sich bei diesen Dingen um irrelevanten neumodischen Schnickschnack.

Auch die testgetriebene Entwicklung hilft, stabile Programme zu produzieren. Wer neuen Code von der ersten Zeile an testgetrieben entwickelt, reduziert seine Fehlerrate beträchtlich. Durch die Technik der testgetriebenen Entwicklung kann eine befriedigende Abdeckung des Codes durch Tests erreicht werden.

Aber es bleibt eine hohe Kunst, ein Programm so zu schreiben, dass es geradlinig und gut lesbar den genauen Zweck ausdrückt, dem es dienen soll: Ohne überflüssigen Schnörkel, ohne duplizierten Code, ohne toten, ohne auskommentierten Code und andere Kommentarwüsten, mit klaren und guten Benennungen von Variablen und Codeeinheiten, die Abstraktionsebenen trennend, mit flüssig verwendbaren API-Schnittstellen. Wie ein Gaukler seine Stücke präsentiert, als wären sie gar nicht schwer, so muss es auch mit gutem Code sein: Er muss sich lesen, als wäre alles, was dort geschieht, ein Kinderspiel. Satz für Satz steht da ja ganz klar, was die Maschine machen soll!

Ist daher die Software Craftmanship, ein Standes- und Qualitätsbewusstsein des Entwicklers das Gebot der Zeit? Reflexionen über Code und daraus abgeleitete Maximen, wie sie Robert C. Martin in seinem neuen, exzellenten Buch Clean Code anhand von ausführlich diskutierten Code-Refaktorisierungen darstellt, sind für alle Entwickler dringend empfohlen. Die Arbeit an der Codequalität ist etwas, was viel zu wenig gelebt wird. Schon zu Beginn einer neuen Entwicklung sollte man verschiedene Alternativen der Implementierung durchdenken, statt sich gleich für einen Weg zu entscheiden. Welches Modell wäre am erfolgversprechendsten? Wo kann bereits bestehende Funktionalität im System wiederverwendet werden? Welche Mitspieler sollen zur Laufzeit zusammenwirken, wie sollen sie miteinander reden, was sind ihre Aufgaben? Während der Realisierung werden zunächst die DDIC-Typen, die Klassen und Methodenschnittstellen entworfen, dann können bereits die ersten Unit Tests hinzugefügt werden, die dann schrittweise zur Implementierung führen. Nach Fertigstellung einer Codeeinheit, eines Moduls oder auch nur einer Methode sollte man über die gewählte Implementierung reflektieren, den produzierten Code kritisch betrachten und noch einmal an ihm herumfeilen, solange man die Möglichkeit dazu hat, da das Objekt noch in einem änderbaren Transportauftrag enthalten ist. Hierbei ist man in der Nahsicht-Phase: Es gilt, Variablen und Methoden wenn nötig auch noch mehrfach umzubenennen, bis der sie verwendende Code sich klar und flüssig liest. Methoden sind zu extrahieren, tote Variablendeklarationen zu entfernen, weitere wesentliche Testfälle zur Unit Test Section hinzuzufügen.

Dies ist alles wichtig und richtig, und ein grosser Teil meiner Arbeit besteht darin, dies zu tun. Auch meine Kollegen versuche ich in dieser Richtung zu motivieren. Software Craftmanship wird helfen, die Programmqualität zu verbessern. Auch das Bemühen um Reduktion von Code bei gleicher Funktion - etwas, was Peter Sommerlad Decremental Development nennt (Slogan: "make Software 10% its size!") - wird helfen, denn weniger Code bedeutet auch weniger Fehler. Aber die besten Standards der Software Craftmanship werden nicht ausreichen, die gegenwärtigen Probleme mangelhafter Codequalität zu lösen. Appelle an die Entwicklertugenden können schlicht ignoriert werden. Und auch mit Zertifikaten, also Scheinen zur Bescheinigung scheinbarer Leistungen, können wir die Codequalität niemals sicherstellen (wenn sie auch vielleicht um ein paar Prozentpunkte besser wird). Aus demselben Grunde helfen auch keine Code-Kontrolleure: auch die sind zertifiziert! Wirklich helfen könnte letztlich nur eine Entwicklungslandschaft (womit ich die Programmiersprache und die IDE meine), die es unmöglich macht, schlechten Code überhaupt hinzuschreiben! Das ist gegenwärtig zweifellos nur eine Vision, könnte aber, wenn es Wirklichkeit wird, für die Softwareentwicklung eine Basisinnovation bedeuten.

David Hilbert hat einmal gesagt: Die Physik ist für die Physiker eigentlich viel zu schwer. Ebenso kann man sagen: Die Programmierung ist für die Programmierer eigentlich viel zu schwer. Das ist nicht herabwürdigend gemeint, sondern hat etwas mit unseren Werkzeugen zu tun. In der Regel arbeiten wir mit hochkomplexen Werkzeugen - uns stehen Turing-vollständige Programmiersprachen und umfangreiche Bibliotheken für bereits gelöste Probleme zur Verfügung. Das klingt eigentlich nicht schlecht. Nur ist der Clue der effizienten Programmierung, dass man nicht alles tut, was man tun kann. Entscheidend ist die Beschränkung auf das Wesentliche, auf die je nur benötigten Mittel. Ein konkretes Problem bedarf zu seiner Lösung immer nur eines kleinen Befehlssatzes, einer Art Mini-Programmiersprache, mit der die Freiheitsgrade des konkreten Problems möglichst präzise gefasst werden können.

Ein Beispiel hierfür liefert SAP: Die Vision hinter SAP R/3 ist, das Systemverhalten durch das Customizing zu steuern. Eine standardisierte, von SAP entwickelte Software wertet ein semantisches Modell aus, das sich vom Kunden mittels Einträgen in Datenbanktabellen ausprägen lässt. Das Customizing steuert also, wie sich das System verhält - während der durchlaufene ABAP-Code die Implementierung der verschiedenen, im Customizing jeweils möglichen Werte enthält. Der Kunde kann, wenn das semantische Modell gut entworfen ist, die Maschine nach eigenen Bedürfnissen programmieren, ohne im klassischen Sinne programmieren zu müssen: Er muss keine ABAP-Programme schreiben – die hat ihm die Entwicklung bereits geliefert. Der ABAP-Code stellt gewissermassen den Interpreter dar, mit dem das Programm des Kunden - eine Sammlung von Einträgen in Customizingtabellen - ausgeführt wird: Wenn er im Datenbankfeld "Positionstyp-Fakturarelevanz" (TVAP-FKREL) den Wert A einträgt, wird in den produktiven Anwendungen des SD-Moduls "Kundenauftragsbearbeitung" und "Lieferungsanlage" der Code durchlaufen, der eine voll belieferte Kundenauftragsposition in den Status Erledigt setzt. Trägt er ein G ein, so wird eine andere Codestrecke aktiv, die die Position erst erledigt, wenn die gelieferte Menge auch fakturiert wurde. Ungültige Werte kann er nicht eintragen, da eine Domänenfestwertprüfung aktiv ist (noch benutzerfreundlicher ist es, einen solchen Wert als Listbox zu präsentieren). In unserem SAP-Release ECC 6.0 gibt es die stattliche Anzahl von 26.473 solchen Customizingtabellen, wenn ich die Tabelle DD02L richtig ausgewertet habe – ein hochkomplexes semantisches Modell also, das dafür aber alle betriebswirtschaftlichen Prozesse eines Unternehmens gut abdeckt.



Dieses Konzept hat nicht nur leidlich gut funktioniert, sondern bedeutete für betriebswirtschaftliche Software eine regelrechte Revolution. Das Customizing war aus meiner Sicht der eigentliche Erfolgsfaktor des Konzepts Standardsoftware. Ein Set von Customizingeinstellungen kann als ein Programm angesehen werden. Wenn das Customizing das Programm ist, dann ist, wie oben erläutert, das ABAP-Anwendungsprogramm der Interpreter für dieses Programm. Auch dies ist eine ungewohnte, aber korrekte Sichtweise. Mit anderen Worten: Durch das Customizingkonzept wurde eine Meta-Ebene der Programmierung eingeführt. Statt fehleranfälligen Code in einer Programmiersprache zu schreiben, wurden an der Geschäftslogik orientierte Datenmodelle entwickelt, und der steuernde Code ist eine konkrete Instanz dieses Datenmodells. Da es viele Kunden gab, deren Geschäftsprozesse sich durch das Customizing ausprägen liessen, während der interpretierende ABAP-Code nur einmal geschrieben werden musste, rechnete sich die Sache.

In den letzten zehn Jahren wurde das Customizingkonzept jedoch immer unzulänglicher. Je mehr es auch in Unternehmen auf schnelle Änderbarkeit der Prozesse ankam, rückte die ABAP-Ebene wieder in den Vordergrund. Es kamen immer mehr Anforderungen, die durch das bestehende Customizingmodell nicht mehr abgedeckt werden konnten. Auch wurde die ABAP-Software zunehmend das schwache Glied in der Kette. Mit traditionellen Programmierparadigmen erstellter Code ist zu schwerfällig und zu fehleranfällig, um die neu geforderte schnelle Änderbarkeit zu gewährleisten.

Folgendes könnte hier Abhilfe schaffen: auch für die (ABAP- oder Java-)Programmierung selbst eine Meta-Ebene einzuführen! Für ein konkretes Problem muss die gewählte Lösung in einer einfachen Syntax (die dann nicht mehr ABAP ist, auch nicht Java) formuliert werden können. Die Syntax muss freier als ein reines Customizing-Datenmodell sein - der Code würde wie bisher als Freitext verfasst. Aber sie muss reduzierter als die einer traditionellen Programmiersprache und in ihrer Begrifflichkeit auf das konkrete Problem eingeschränkt sein. Was sie von ABAP und Java erben sollte, ist die Ausdruckskraft und Lesbarkeit des Codes. Was sie nicht erben sollte, ist deren Vollumfänglichkeit. Das Resultat wäre, dass der entstehende Code wesentlich kleiner wird als der äquivalente ABAP- oder Java-Code für die gleiche Aufgabe. Das ist natürlich kein Wunder, sondern nur einer geschickten Delegation an bereits bestehende Programmeinheiten zu verdanken. Gewisse Standardaufgaben wie Parsing sind in den Tools ein für allemal gelöst, mit denen eine solche Syntax ausgeprägt werden kann.

Was ich hiermit beschreibe, sind domänenspezifische Sprachen. Nüchtern gesehen ist es nichts anderes als der alte Bibliotheksgedanke: Einmal programmierte Funktionen in Bibliotheken zur Verfügung zu stellen. Nur erfolgt die Nutzung der Bibliothek - der Aufruf der API's - nicht auf derselben Code-Ebene, nicht einmal in derselben Programmiersprache wie die, in der die Bibliothek selbst programmiert ist, sondern in einer dem konkreten Problem angemessenen Metasprache.

Entwickler würden mit einer solchen Meta-Ebene das Kunststück vollbringen, sich am eigenen Schopf aus dem Code-Sumpf zu ziehen. Auch Zwischenebenen im Programmcode selbst sind denkbar, die in zunehmender Abstraktion optimal zugeschnittene textförmige Beschreibungen der Probleme darstellen. Wenn dieser Schritt einmal in grossem Stil gemacht ist, wird man über Probleme wie die eingangs erwähnten als Zeugnisse aus der Software-Steinzeit lächeln.

Keine Kommentare :