Follow my new blog

Sonntag, 24. Februar 2013

TDD ohne Zauberei und Überraschung

TDD bleibt für mich aktuell, auch wenn es ein alter Hut ist. Das liegt einfach daran, dass TDD landauf, landab nicht das liefert, was es verspricht. Wenn gewöhnliche Entwickler nur nach monatelangem Studium in Klausur die TDD-Weihen empfangen können, dann liegt etwas im Argen.

In der dotnetpro stelle ich daher meine Gedanken zu einer Version 2.0 von TDD vor (Ausgaben 3/2013 und 4/2013). Aber auch hier im Blog habe ich dazu schon einiges gesagt, z.B. dass TDD in der heutigen Form für mich das Single Responsibility Principle verletzt. Anlass war für mich ein Video von Corey Haines.

Da war dann insbesondere ein Kommentator zu dem Blogartikel anderer Meinung. Und der hat auf ein aus seiner Sicht vorbildliches Beispiel verwiesen: Brett Schucherts Demonstration der TDD-Implementation eines RPN-Rechners.

Leider, leider hat mich die Demonstration auch wieder enttäuscht. Und wieder aus den selben Gründen wie bei Corey Haines:

  1. Es gibt keine Erklärung des Problems.
  2. Es gibt keine Sammlung und Priorisierung von Testfällen.
  3. Lösung und Implementation sind nicht von einander getrennt. Damit fällt die Lösung während des Codes quasi vom Himmel.

Zumindest Punkt 2 widerspricht ausdrücklich jeder TDD-Empfehlung, würde ich sagen. Deshalb kann die Demonstration nicht vorbildlich sein.

Und Punkt 3 macht die Demonstration zu einem “magischen” Event, der suggeriert, so könne es jeder Entwickler: Die Lösung einfach kommen lassen. Das wird schon.

Dabei haben sich alle, die TDD so demonstrieren, selbstverständlich über das Problem und auch ihren Lösungsansatz vorher ausführlich Gedanken gemacht. Entweder, indem sie sich hingesetzt und überlegt haben oder indem sie das Szenario einige Male ohne zu überlegen implementiert haben.

Ich kenne keine (!) TDD-Demonstration, bei der die Aufgabe live gestellt wurde und dem Demonstraten vorher unbekannt war. (Falls jemand jedoch zu so etwas einen Link hat, möge er ihn bitte in einem Kommentar hier hinterlassen.)

Also: TDD-Demos sind heutzutage weitgehend unrealistisch. Damit suggerieren sie, dass die TDD-Schritte red-green-refactor es allein richten. Das halte ich für mindestens fahrlässig, weil es ganz sicher in große Frustration bei vielen Entwicklern führt.

Und wie sollte dann eine TDD-Demo aussehen?

Ich lehne mich mal aus dem Fenster und behaupte: So wie im Folgenden.

Dafür nehme ich das Beispiel von Brett Schuchert auf: den RPN Rechner. Das Problem ist mir damit zwar auch nicht unbekannt, aber ich habe versucht, mein Vorwissen so weit wie möglich zurückzunehmen. Allemal esse ich mein eigenes Hundefutter und gehe das Problem für alle sichtbar nach den von mir vorgeschlagenen TDD 2.0 Schritten an.

image

Los geht´s…

Was ist eigentlich das Problem?

“Without requirements or design, programming is the art of adding bugs to an empty text file.” - Louis Srygley

Ich habe mir Brett Schucherts Lösung gar nicht ausführlich angeschaut. Seine Videos sind über 2 Stunden lang; er hat sich sehr viel Mühe gegeben. Außerdem wollte ich mich nicht für meine eigene Durchführung “kontaminieren”. Trotzdem habe ich natürlich beim Überfliegen Eindrücke gewonnen.

Mein erster Eindruck: Brett sagt niemandem, was das Problem eigentlich genau ist. Weder zeigt er am Anfang mal ein Beispiel für einen RPN Rechner, noch sagt er, was das Ziel seiner Entwicklungsarbeit ist. Er mokelt vielmehr 51 Minuten im ersten Video an einem API herum, von dem niemand so recht weiß, warum er so aussieht, wie er aussieht.

Wie kann man es besser machen?

Man macht erstmal eine Skizze vom Problem. Was sind die Anforderungen? Man versucht das Problem zu verstehen. Hier die erste Doppelseite meiner Analyseskizze:

image

Auf der linken Seite ist oben ein UI angedeutet und unten eine Folge von Eingaben mit zugehörigen Ausgaben; so habe ich mir die Funktionsweise eines RPN Rechners vorgestellt. (Dass Sie meine Schrift nicht lesen können, ist quasi unvermeidlich ;-) Aber das ist auch nicht nötig. Ich möchte Ihnen mit den Skizzenblättern nur einen groben Eindruck von meinem Vorgehen-/Denken vermitteln.)

imageIn der Mitte der linken Seite sehen Sie allerdings eine Verirrung. Da lag ich falsch mit meiner Vorstellung. Erst ein Blick auf den Taschenrechner von Mac OS X im RPN Modus hat mich eines Besseren belehrt. Das habe ich dann unten links und auch noch rechts ganz oben korrigiert.

Nach ca. 5-10 Minuten “Scribbeln” und mit dem RPN Rechner herumspielen war mir das Problem klar. Dafür musste ich aber auch ein Anwendungsszenario im Blick haben. Deshalb findet sich links oben zuerst eine UI-Skizze. Nur wenn ich weiß, wie ein Benutzer wirklich mit einem RPN Rechner umgehen will, sollte ich mir Gedanken zu einem darunter liegenden API machen.

Zu oft entwickeln wir einfach im luftleeren Raum, ohne Kontext. Wir imaginieren dann eine ganze Menge, was alles nötig sein könnte. Wir setzen dann ganz schnell eine technische Brille auf – und verlieren den Benutzer aus dem Blick. Damit ist der Verschwendung Tür und Tor geöffnet. Wir basteln dann nach den Regeln der Kunst an Zeugs herum, das nur wenig Realitätsbezug hat.

Das halte ich für einen großen Übelstand. Da werden unbewusst unökonomische Muster eingeschliffen. Deshalb finde ich es bei jeder Übung wichtig, möglichst konkret und benutzerbezogen zu sein. Nur so üben wir uns auch ständig in agilem Denken.

Lösungsansatz formulieren

“First, solve the problem. Then, write the code.” - John Johnson

Erst nachdem ich ein Verständnis für das Problem entwickelt hatte, konnte ich mich daran machen, über eine Lösung nachzudenken. Genau: nachdenken. Ich habe also nicht Visual Studio angeworfen, um mit dem Codieren anzufangen.

Erstens ist nichts einschnürender als Code. Zweitens wäre ich mit der Arbeit an den Rechner gekettet gewesen.

Mit meinem Notizbüchlein konnte ich jedoch in der S-Bahn weiter über die Lösung nachdenken. Und zwar so konkret, dass die Codierung für mich hinterher ganz leicht war [1].

Das Ergebnis sind die rechte Seite im oberen Bild und die beiden Seiten im nächsten Bild.

Im ersten Bild rechts sehen Sie mein Flow-Design. Rechts oben der big picture Flow mit allen Interaktionen des einzigen Dialogs der Anwendung. Das sind Enter (Zahl eingeben), Drop (Zahl vom Stack entfernen) und Operator auslösen.

Alle Domänenlogik fasse ich in einer EBC-Funktionseinheit zusammen: dem RPN Calculator.

Unten auf der rechten Seite im ersten Bild sehen Sie dann eine Verfeinerung der Interaktion, die über einen Operator ausgelöst wird. Da habe ich mir klar gemacht, was mit dem Operator und der aktuellen Zahl im “Rechenwerk” passiert, wenn denn etwas berechnet werden soll.

Bis hierhin hat es ca. weitere 7 Minuten gedauert. Nach runden 15 Minuten hatte ich also nicht nur das Problem verstanden, sondern auch einen Lösungsansatz. Dessen Kern war erstens ein Stack für die Zahlen, die auch im UI zu sehen sind, also die Operanden. Und zweitens gab es die Vorstellung eines Verzeichnisses von Operationen auf diesen Zahlen. Die ergab sich ganz natürlich aus der Verallgemeinerung der Interaktion des Benutzers in Bezug auf die Operatoren. Nicht jeder Operator für sich war für mich eine Interaktion, sondern sie alle sollten mit einer Interaktion abgehandelt werden, die mit dem konkreten Operator parametriert sein sollte [2].

Testfälle sammeln

Nach der Modellierung kannte ich den API des RPN Rechners. Ich musste mir nichts aus den Fingern saugen wie Brett. Dessen get/set für einen Akku und die Enter-Methode finde ich gänzlich unnatürlich. Er versucht da etwas 1:1 in einen API zu übernehmen, das er (ohne es uns wissen zu lassen) von einem UI abgeschaut hat. Aber warum sollte in einem API ein Repräsentant einer UI-Design-Entscheidung stehen? Dass es dort einen Enter-Button gibt, kann sich doch morgen ändern.

Deshalb gibt es bei mir keinen sichtbaren Akku und auch keine Enter-Methode, sondern ein Push() und ein immer gleiches Resultat nach jeder Aktion. Push() abstrahiert von jedem UI. Es sagt vielmehr etwas über den Lösungsansatz für den RPN Rechner aus, dass es darin nämlich einen Stack gibt. Das ist echte Domänenlogik. Die abstrahiert von UI-Eigenheiten.

Mit dem Entwurf für den RPN Rechner konnte ich dann konkret über Testfälle nachdenken. Die sehen Sie auf der linken Seite der nächsten Abbildung:

image

Für die drei Methoden meines API gibt es drei Gruppen von Testfällen, die ich als Tabellen notiert habe. Etwas schöner sieht so eine Tabelle natürlich in Excel aus. Sie ist der Reinschrift meiner Skizzen für diese Dokumentation entnommen.

image

Weil man es nicht häufig genug sagen kann: Über Testfälle nachdenken und sie priorisieren kann man nur und ausschließlich, wenn man einen Lösungsansatz hat, zu dem ein API gehört.

Wer TDD vorführt, ohne Testfälle zuerst zu benennen, und wer nicht erklärt/erklären kann, warum deren Reihenfolge so ist, wie sie ist, der führt TDD falsch vor.

Wo Testfälle vom Himmel fallen oder sich überraschend ergeben, ist TDD magisch und suggeriert Einfachheit, die nicht vorhanden ist.

Was ist meine Erklärung?

  • Meine Testfälle gliedern sich von vornherein nach API-Methoden. Da gibt es keine Überraschung. (Was nicht heißt, dass ich mich Änderungen während der Implementation verschließe. Da kann es immer neue Erkenntnisse geben. Aber ich darf mit einer Idee beginnen.)
  • Innerhalb der Testfälle für eine API-Methode gibt es eine klare Aufteilung zwischen Eingaben und zugehörigen erwarteten Ausgaben.
  • Aus den Inputs kann nicht jeder Output erklärt werden. Der RPN Rechner hat einen Zustand. Der muss ebenfalls in die Testfälle eingehen.
  • Die Testfälle wachsen in Richtung zunehmendem Zustand, da der Input immer dieselbe Form hat.

Mein TDD wie unten zu sehen, ist also nicht magisch und nicht überraschend, sondern ganz handfest. Es folgt einem Plan, der durch Überlegen entstanden ist. Das hat maximal 5 Minuten gedauert.

Wem dieses Überlegen schon gleich zu viel sein sollte, wer das schon im Widerspruch zum rechten TDD sieht… Nun, dem habe ich eben nichts zu sagen. Wir leben dann auf verschiedenen Planeten. Das ist ok. Ich freue mich aber über jeden, der sich drauf einlässt. Dann können wir auch trefflich darüber debattieren, ob nicht der eine Testfall früher oder später liegen sollte oder der eine Schritt mehr oder weniger KISS ist.

Inkrementell vorgehen

Auch TDD tut gut daran, an den Kunden zu denken. Brett hat da allerdings niemanden im Blick. Er startet auch ohne UI. Das ist aus meiner Sicht aber kein Grund, nicht agil/inkrementell vorzugehen. Auch Entwickler, die einen API nutzen sollen, sind Kunden. Die Frage lautet deshalb für mich immer: Wie kann ich möglichst schnell einen kleinen Nutzenzuwachs bieten? Welchen Schritt kann ich tun, um etwas von Wert herzustellen, zu dem ein Kunde/Anwender Feedback geben kann?

Deshalb habe ich einen Moment darauf verschwendet, die rechte Seite im zweiten Skizzenbild zu füllen. Dort sehen Sie fünf Inkremente angedeutet, die sich an den Interaktionen des Dialogs orientieren.

  • Inkrement #1: Der Anwender kann Zahlen auf den Stack schieben, die Operanden. Das entspricht Push() auf dem RPN Calculator. Dazu kann der Anwender schon mal Feedback geben.
  • Inkrement #2: Der Anwender kann die aktuelle Zahl zum Stack-Top addieren. Jetzt ist der RPN Rechner schon ein bisschen nützlich, auch wenn er nur eine Operation beherrscht.
  • Inkrement #3: Der Anwender kann Zahlen vom Stack entfernen. Das entspricht Drop() auf dem RPN Calculator.
    Für diese Funktionalität in Inkrement #3 habe ich mich nur in Anlehnung an Bretts Demonstration entschieden. Er hat Drop() sogar noch vor der ersten Operation realisiert. Da wollte ich mich nicht lumpen lassen ;-)
    Ohne seine Vorlage hätte ich jetzt mit Operationen weitergemacht.
  • Inkrement #4 und #5: Jetzt endlich weitere Operationen. –, *, / als binäre Operationen und ! als unäre.

Damit fiel es mir leichter, mich bei der TDD-getriebenen Implementation zu konzentrieren. Den Preis von weiteren 3 Minuten habe ich dafür gern gezahlt. Denn ich konnte sicher sein, selbst wenn ich bei der Implementation unterbrochen werde – was sehr wahrscheinlich ist –, habe ich immer etwas in der Tasche. Nach jedem Inkrement kann ich den Griffel fallen lassen und der Kunde hat schon etwas in der Hand; zwischen Inkrementen liegen Sollbruchstellen der Implementation.

Implementation nach red+green+refactor

“Programs must be written for people to read, and only incidentally for machines to execute.” - Abelson / Sussman

Auch wenn ich nach meinen Überlegungen gar nicht mehr so viel Lust hatte zu implementieren – ich hatte den Eindruck, das Spannendste schon erledigt zu haben: die Problemlösung –, habe ich mich natürlich an Visual Studio gesetzt.

Das folgende Script zeigt die Schrittfolge meiner Codierung des RPNCalculator-Klasse. Das UI und die Integration von UI und RPNCalculator habe ich ausgelassen. Die sind nicht so spannend.

NUnit Tests für das UI gibt es – aber die sind als Explicit markiert. Sie dienen nur der Überprüfung, ob die Events korrekt gefeuert werden bzw. die Anzeige des Resultats korrekt erfolgt. Das ist kein Hexenwerk, ändert sich nicht häufig und kann auch mal manuell getestet werden, falls nötig.

Die Musik spielt in der Domänenklasse RPNCalculator. Darauf liegt auch bei Brett Schuchert das Augenmerk. Also los…

RPN Desktop Calculator TDD by Ralf Westphal

Wenn Sie sich die Schrittfolge näher ansehen, stutzen Sie vielleicht hier und da. Nicht alles mag in Ihren Augen TDD der reinen Lehre sein. Die strebe ich aber auch nicht an. Mir geht es um pragmatisches, realistisches TDD.

Dennoch hier ein paar Erklärungen, die Sie besänftigen mögen:

  • Bei Schritt 2.1 sehen Sie, dass ich über einen Konstruktor in das System under Test (SUT) Zustand injizieren will [3]. Ja, da bin ich ganz schamlos. Das mache ich einfach. Ich weiß, dass das SUT Zustand hat. Warum soll ich den nicht explizit setzen? Das spart mir u.U. eine Menge Verrenkungen, um mit anderen API-Aufrufen an einen Punkt im Test zu kommen, wo ich endlich das überprüfen kann, was gerade Thema ist.
  • In Schritt 3.2 habe ich mich hinreißen lassen, wider besseren Entwurfswissens eine ganz einfache Implementation zu wählen. Die arbeitet schon irgendwie richtig, aber sie entspricht nicht dem, was Ziel sein muss: eine Auswahl der Operation aus einer Liste. Ich verdrahte die Addition fest.
    Das habe ich gemacht, um hier die TDD-KISS-Gemüter zu beruhigen ;-) Im richtigen Leben hätte ich mir erlaubt, schon die Struktur zu implementieren, die Sie nun erst in 6.1 eingeführt sehen.
  • Zu meiner eigenen Überraschung hat der Test in Schritt 3 (leider irrtümlich so benannt) gleich grün geliefert. Das hätte eigentlich anders sein sollen – aber es zeigt, dass auch ein Nachdenken über Testfälle in endlicher Zeit nicht perfekt ist.
    Dafür will ich mich nicht schelten. Kann halt passieren. Macht nichts.
    Da muss ich mich nächstes Mal nicht noch doller anstrengen, um bessere Testfälle zu finden. Ich nehme es einfach so und freue mich, dass ich etwas Implementationsaufwand spare.
  • Bei der Implementation zu 4.2 könnten Sie einwerfen, dass die nicht KISS sei. Das mag sein – aber: WTF. Das ist mir egal. Ich weiß doch schon, wie Resultate aus dem RPNCalculator geliefert werden. Warum soll ich mich dümmer stellen, als ich bin?
  • Ha, jetzt haben Sie mich: Nach der Implementation von 5.1 ist ein anderer Test auf Rot gegangen. Das darf doch nicht sein!
    Ja, das mag gegen die reine Lehre verstoßen. Aber auch hier: WTF. Das kann mal passieren. Ich finde das nicht schlimm. Es ist eher ein Zeichen dafür, dass die Implementation in 5.1 KISS ist. Sie konzentriert sich nur darauf, einen Test grün zu bekommen.
    Mit einer kleinen Nachbesserung zurre ich die Regression dann in 5.2 wieder fest. Es ist kein größerer Schaden entstanden.
  • Nun Trommelwirbel… Jetzt zum Kern der Domänenlogik: der leicht erweiterbaren Liste von Operationen. Auf die hatte ich in 3.2 noch verzichtet – doch nun kann ich nicht mehr an mich halten.
    In einem Rutsch refaktorisiere ich die bisherige Addition und füge auch noch die Fakultät hinzu. Ja, bin ich denn wahsinnig geworden?
    Nein, ich finde das halb so wild. Es sind ein paar Änderungen, die ich da vornehme – aber sie sind allesamt trivial. Und wenn dabei etwas verrutschen sollte, sehe ich sofort, wo das Problem liegt.
    Geht gar nichts mehr, dann ist etwas mit Auswahl und Aufruf der Operationen falsch.
    Schlagen nur die Additionstests fehl, dann habe ich die Addition falsch implementiert.
    Schlägt nur der Fakultätstest fehl, dann ist dort etwas falsch implementiert.
    Der neue Inhalt von Calculate() ist auch nicht spontan entstanden, sondern steht schon im erste Skizzenbild rechts unten. Ich lese die Implementation quasi nur ab.
  • Für die weiteren Operationen gehe ich eine Abkürzung in 7.*. Ich stecke die Testfälle in TestCase-Attribute und füge dem _operations-Verzeichnis einfach nur kurze Lambda-Ausdrücke hinzu. Das Muster ist immer gleich. Die Addition hat es vorgemacht.

That´s it. Ich denke, damit habe ich eine Lösung geliefert, die nicht nur funktioniert, sondern auch Martin Fowlers Forderung erfüllt:

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler 

Nicht nur den Code kann man verstehen. Der ist ja denkbar kurz. Er ist auch erweiterbar – und zwar von vornherein und nicht erst nach einem überraschenden Refactoring. Er war ja schon so gedacht. Wer einen weiteren Operator hinzufügen will, der setzt in das UI einen Button dafür und trägt eine Operation in das Verzeichnis ein. Fertig.

Eine Injektion von Operationen von außen oder auch nur eine eigene Klasse halte ich derzeit allerdings für überflüssig. Eine Refaktorisierung in dieser Hinsicht wäre für mich vorzeitige Optimierung. Die Anforderung dafür ist derzeit nicht aus dem Gesamtszenario ablesbar. Und einfach nur Prinzipien anwenden, weil ich sie gerade kenne, ist mir nicht Grund genug.

Nicht nur der Code ist aber verständlich, die ganze Lösung ist es. Weil es dazu ein Modell gibt und der Code dieses Modell widerspiegelt. Dazu gehört natürlich auch noch die Integration, die ich hier bewusst ausgelassen habe, weil ich mich auf TDD konzentrieren wollte. Die finden Sie bei Interesse jedoch im Repository.

Fazit

Bubbles don´t crash – das ist wahr. Genauso kann man sagen, Landkarten seien nicht das Terrain. Ja, und? Sind Landkarten deshalb nutzlos? Kaum. Genauso wenig sind Bubbles, d.h. Entwürfe auf dem Papier nutzlos oder verschwendete Zeit, nur weil sie noch kein Code sind. Das ist ja gerade ihr Zweck. Sie sollen kein Code sein, damit man mit ihnen viel schneller vorankommt bei der Lösungsfindung.

Wo Code ins Spiel kommt, wird es morastig. Wenn ich also über eine Lösung sinnen kann, ohne codieren zu müssen, kann ich mich nur freuen. Die ersten zwei Bilder haben gezeigt, dass ich das kann. Die spätere Implementation hat mich keines Besseren belehrt. Dennoch ist der Lösungsansatz auf Papier natürlich zunächst nur eine Hypothese. Das macht aber nichts. Im Gegenteil: Es geht gar nicht anders. Auch der Lösungsansatz, den Sie ohne Papier nur im Kopf während des TDD Code Slinging entwickeln, ist nur eine Hypothese.

Wenn ich Ihnen hier Skizzen und Nachdenken präsentiert habe, dann nur, um etwas explizit zu machen, das sich nicht einmal vermeiden lässt. Corey Haines tut es, Brett Schuchert tut es, Sie tun es, wir alle tun es – fragt sich nur wann und wie nachvollziehbar.

Ich finde meine Lösung insgesamt viel nachvollziehbarer als die der zitierten Herren. Da steckt keine Magie drin. Ich bin nicht (künstlich) überrascht über Testfälle, die sich auftun. Meine kleinen Überraschungen sehen Sie klar dokumentiert in der TDD-Schrittfolge und im Vergleich zu meinen Skizzen. Das ist real und nicht poliert.

Wenn Sie ein Guru sind, kommen Sie natürlich ohne all das aus. Ich kann das leider noch nicht. Ich muss noch nachdenken – und dann tue ich das gern in Ruhe. Nicht immer, aber allermeistens. Ab und an klappe ich Visual Studio auch schneller auf. Doch dann meist, um im Rahmen meiner Lösungsfindung etwas zu explorieren. Dann bin ich jedoch in einem anderen Modus, dann bin ich Forscher und nicht Ingenieur.

  • Forscher finden heraus, was ist. Sie dokumentieren Zusammenhänge.
  • Ingenieure nehmen die Ergebnisse von Forschern und finden sehr kreativ Problemlösungen. Sie schaffen Neues.
  • Handwerker schließlich wenden Lösungen vor allem an. Sie setzen um, reproduzieren.

Das sind drei zentrale Rollen, deren Hüte wir bewusst wechseln sollten. Wenn wir es nicht tun, riskieren wir Frust oder Schlimmeres.

Durch die bewusste Herausstellung von Problemanalyse und Lösungsfindung/Entwurf möchte ich den Rollen Forscher und Ingenieur Raum geben. Wir müssen Probleme zunächst erforschen, dann müssen wir mit den Heuristiken und Technologien unseres Entwicklerwerkzeugkastens state-of-the-art Lösungen entwickeln – und erst am Ende setzen wir die handwerklich sauber mit TDD um.

Alle drei Rollen nur in den red+green+refactor-Phasen innehaben zu wollen, tut uns nicht gut und dem Ergebnis auch nicht. Und letztlich tut es auch der guten Idee hinter TDD nicht gut.

Bottom line: TDD ist eine feine Sache – wenn man die nicht magisch betreibt und überlastet.

Endnoten

[1] Ja, ich will es geradeheraus sagen: Das TDD-Vorgehen, welches ich hier zeige, weil ich mich nicht lumpen lassen wollte, hat mich am Ende auch langsam gemacht. Die kleinsten Schritte waren für mich nicht wirklich nötig. Ich hätte größere machen können – und hätte nur in eine kleinschrittigere Gangart zurückgewechselt, wenn ich in ein Problem gelaufen wäre.

Das ist natürlich gegen die reine TDD-Lehre… Deshalb hab ich mich auch nicht hinreißen lassen… Doch es ist für mich ein realistischeres Vorgehen.

TDD-Kleinstschritte sollten kein Dogma sein, sondern eine Methode unter bestimmten Bedingungen. Und diese Bedingungen sind, dass man eben noch nichts über die Lösung weiß. Das tue ich ja aber, indem ich vorher darüber nachdenke.

Und dass das nicht nur eitle, nutzlose Gedanken sind, die nichts mit der Coderealität zu tun haben, dass ich mir so gar nichts vor dem Codieren vorstellen könne… Das versuche man mir bitte nicht einzureden. Irgendetwas müssen ein paar Jahrzehnte Softwareentwicklungserfahrung doch in meinem Hirn hinterlassen haben, oder?

Wichtig sind nicht stets und ausschließlich die allerkleinsten Schritte, sondern ein gesundes Gefühl dafür, ob man sich noch in bekannten Gefilden mit dem Code bewegt. Je unbekannter, desto kleiner die Schritte. Selbstverständlich. Aber Lösungen lassen sich eben auch noch auf andere Weise als durch Codieren erkunden.

Falls dabei mal etwas herauskommen sollte, das nicht KISS ist, dann finde ich das nicht schlimm. Ich habe in den Fall nämlich sehr wahrscheinlich Zeit gespart. Das kann mehr Wert haben als KISS-Code – solange die Verständlichkeit nicht grundsätzlich leidet.

[2] Für Brett Schuchert ist das eine überraschende Erkenntnis, auf die er nach knapp einer Stunde Codieren stößt. Er muss dafür seine Implementation weitere 20 Minuten refaktorisieren.

Das halte ich für unökonomisch, wenn ich durch 5 Minuten Nachdenken gleich darauf kommen kann, dass Operationen nicht durch einzelne API-Methoden repräsentiert werden sollten.

Ich halte es für keine Tugend, sich solcher Erkenntnis mit Macht zu widersetzen. Und es ist auch keine Tugend, nicht nach Wegen zu suchen, um solche Erkenntnis möglichst früh zu gewinnen.

[3] Eigentlich soll dieser spezielle Konstruktor internal sein. Das hab ich im TDD-Eifer übersehen. Sorry. Bei dem jetzigen Codereview für den Blogartikel kommt es ja aber heraus :-)

Donnerstag, 21. Februar 2013

Vorzeitige Optimierung durch Monolithen

Softwareentwicklung ist historisch ein Geschäft der knappen Ressourcen. Speicher war knapp, Speicher war langsam, die Prozessorgeschwindigkeit war gering, die Prozessorverfügbarkeit war niedrig und die Kommunikation zwischen Prozessoren/Maschinen war unmöglich bis schwierig.

Ich glaube, diese jahrzehntelange Not ist zum Bestandteil der Kultur der Softwareentwicklung geworden. So wie die Not der (Vor)Kriegszeit zur Kultur unserer Eltern und Großeltern gehört.

Der Teller wird leer gegessen. Man schmeißt keine Lebensmittel weg. Mit dem Essen spielt man nicht. Kerzenstummel und andere Reste werden gehortet. Socken müssen gestopft werden. Wer kennt solche Ermahnungen und Verhaltensweisen nicht von Eltern und Großeltern? Sie entstammen einer tief liegende Angst, dass das, was heute verfügbar ist, morgen wieder weg sein könnte. In der heutigen Zeit des Überflusses sind es allerdings Anachronismen [1].

Ähnlich kommen mir nun viele Reflexe von Entwicklern vor. Sie mahnen, nicht verschwenderisch mit Speicher umzugehen; sie bedenken zu allererst die Performance; sie misstrauen jeder Ressource und jeder Interaktion [2].

Dabei leben auch wir inzwischen in Zeiten des Überflusses. 48 KB oder 640 KB Hauptspeicher oder 5 MB Festplatten sind heute nicht mehr Stand der Dinge. Für viele (oder gar die meisten?) Anwendungsszenarien ist Hardware heute so groß, schnell und billig, dass sie keine Begrenzung mehr darstellt.

Komplette Unternehmensdatenbanken können wir heute im Hauptspeicher halten. Prozessoren haben mehrere Kerne. Festplatten sind schnell und groß. Rechner sind in Gigabit-Netzwerke eingebunden. Und quasi unendlich viele Rechen- und Speicherressourcen stehen auf Abruf weltweit zur Verfügung.

Doch die Mentalität ist immer noch eine der Knappheit. Das kann ich irgendwie verstehen; ich bin ja selbst in Zeiten der Hardwaremängel mit der Programmierung groß geworden. Jetzt sollten wir aber mal diese Haltung langsam ändern. Solange wir uns ihrer nämlich nicht bewusst sind und sie versuchen zu überwinden, verschwenden wir unsere Zeit und geistige Kapazität. Die sind nämlich tatsächlich noch genauso begrenzt wie früher.

In vielen Bereichen ist die Mangelkultur ein Anachronismus. Wir verhärten uns mit ihr gegen nützliche Veränderungen. Denn dass sich einiges verändern muss, ist unzweifelhaft. Beispiel: Softwareentwurf. Wir müssen Software anders entwerfen, damit die Strukturen evolvierbarer werden. Das heutige Legacy Code Problem darf sich nicht ungebremst so fortsetzen.

Für mich ist es ein Problem des Mangeldenkens, weil die uns so drückenden Softwaremonolithen aus meiner Sicht Kinder des Mangeldenkens sind. Denn warum sollte man Code zu einem Monolithen zusammenschweißen, wenn nicht aus Angst vor Performance- und Speicherproblemen?

Unter Monolith verstehe ich hier Software, die all ihre Aufgaben in möglichst wenigen Prozessen erledigt. Am besten ist das nur einer, eine Desktopanwendung (bzw. ein Batch) oder eine Web-Anwendung. Ein Betriebssystemprozess hostet dabei die gesamte Funktionalität. Das ist das Ideal.

Das ist einfach zu deployen. Das ist vermeintlich einfach zu entwickeln, da man alles in ein Visual Studio Projekt stecken kann. Das ist schnell, weil die gesamte Kommunikation innerhalb des Adressraums eines Prozesses stattfindet. Das ist technisch simpel, weil man sich nicht mit Infrastruktur herumschlagen muss.

Natürlich verstehe ich all diese Beweggründe. Ich habe auch so gedacht. Doch inzwischen glaube ich, dass wir uns damit keinen Gefallen tun, wenn wir so auf diese Mängel stieren. Das Deploymentproblem ist heute nicht mehr so groß wie noch vor 10 Jahren; die Kommunikationsgeschwindigkeit ist mit heutigen Prozessoren, Hauptspeichern und Netzwerkverbindungen auch zwischen Prozessen oft ausreichend. Und die Infrastruktur müssen wir nicht mehr in dem Maße selbst programmieren wie noch vor 10-20 Jahren [3]. Wer heute einen Monolithen entwickelt, der betreibt also vorzeitige Optimierung. Er optimiert für Mängel, deren Existenz nicht gesichert ist im Hinblick auf eine konkrete Domäne.

Unsere Kultur verhindert jedoch, dass wir dieses Potenzial voll ausschöpfen. Wo es nicht anders geht, da wird es getan. amazon, Twitter, Facebook, eBay usw. können nicht anders, als an die heutigen Grenzen zu gehen.

Wer allerdings heute ein CRM oder sonstige Intranet/Desktop-Software programmiert, der verschenkt Potenzial. Der schnürt sich ein in ein Korsett, dass man geradezu masochistische Neigungen vermuten könnte.

Wenn es denn sonst keine Probleme gäbe, wäre das ja nicht schlimm. Jedem seine Neigung ;-) Das Evolvierbarkeitsproblem halte ich jedoch für so groß, dass wir uns “Neigungsprogrammierung” in dieser Hinsicht nicht mehr erlauben können. Die mittel- bis langfristige Gesundheit unserer Software leidet darunter. Und auch die unserer Teams.

Wir müssen bessere Wege finden, um Software über die Jahre nicht nur zu retten, sondern entspannt weiterentwickeln zu können. Dazu gehört für mich immer mehr auch die Neuentwicklung. Ja, genau, Code wegschmeißen und neu machen.

Dass das nicht für komplette Anwendungen funktioniert, ist mir klar. Deshalb müssen wir aufhören, Monolithen zu bauen. Die können wir nämlich nur komplett ersetzen – was unökonomisch ist. Oder wir müssen sie zu Tode refaktorisieren.

Mit geht es um einen Mittelweg. In “Form Follows Feasibility” hatte ich schon darüber nachgedacht. Seitdem bin ich nur sicherer geworden, dass solch ein Mittelweg nötig und gangbar ist – wenn wir uns aus dem Mangeldenken befreien.

Die bewusste Strukturierung auch von Deskptop- oder Web-Anwendungen in mehrere Services, d.h. Komponenten mit plattformunabhängigem Kontrakt, ist mir sogar noch wichtiger geworden.

Erstens kann man sich mit plattformunabhängigen Kontrakten weniger in die Tasche lügen, was die Entkopplung von Code angeht. Zweitens bieten nur solche Kontrakte die Chance, sich vor der drohenden Systemrelevanz der Programmierplattform mit einhergehender Verkalkung des Teams zu schützen.

Jede Software als “web of services” zu denken, sollte der Default werden, glaube ich. Und erst wenn sich das handfest als suboptimal herausstellt, sollte mit einer Optimierung begonnen werden.

Endnoten

[1] Die Motive Respekt und Dankbarkeit hinter solcher Haltung will ich nicht ausschließen. Schön, wenn sie denn mitschwingt. Auch im Überfluss tun wir sicherlich gut daran, Bescheidenheit und Respekt unserer Umwelt entgegen zu bringen.

[2] Schizophren ist, dass diese Haltung komplett umkippen kann. Denn der Umgang mit Objekten in verteilten Anwendungen war (und ist?) von großer Naivität, gar Sorglosigkeit geprägt. Da spielten Skalierbarkeit, Robustheit und Performance lange so gar keine Rolle. Anders sind CORBA, EJB und DCOM und auch noch Teile von .NET Remoting nicht zu erklären.

[3] Eine Kommunikation zwischen Prozessen, die über die Welt verteilt sind, ist via Internet und Cloud API wie Pubnub oder iron.io heute eine Sache weniger Zeilen Code.

Dienstag, 19. Februar 2013

Bessere Prozesse für den Rest von uns

Die “Gurus” können es. Die können ihr TDD, OOP, XP, Scrum usw. Aber was ist mit dem Rest der Entwicklergemeinde?

In den meisten Teams gibt es auch einen, der es kann. Einen der ansagt. Aber was ist, wenn der mal ausfällt? Was ist mit dem Rest des Teams.

Mein Eindruck ist, wir verlassen uns noch zu sehr auf “Gurus”, “Meister” und “starke Persönlichkeiten”. Die bestimmen unsere Arbeit. Entweder verteilen sie sie. Oder sie definieren Methoden auf ihrem Level – die dann Normalsterbliche nur schwer selbst in ihren Arbeitsalltag transferieren können.

Wie anders ist es zu erklären, dass zum Beispiel TDD nach 10 Jahren immer noch kein Standard ist? Wie anders ist es zu erklären, dass Entwurf mit der UML nach mehr als 10 Jahren immer noch kein Standard ist? Mit Standard meine ich so etwas wie “Bei 80% der Entwickler akzeptiert und in täglichem Gebrauch.”

Ich behaupte nicht, dass TDD oder UML schlecht seien. Fragt sich nur, wem sie real helfen. Ich sehe einfach eine große Differenz zwischen Anspruch und Implementation. An der Verfügbarkeit von sprachgewandten Verkündern dieser und anderer Techniken oder Literatur darüber kann es nicht liegen. Woran dann?

Eine Folie in einem Vortrag von Sam Aaron hat mich darauf gebracht:

image

Sam hat eine Geschichte von Schachgroßmeister Gary Kasparov erzählt: Nachdem Kasparov gegen IBMs Deep Blue verloren hatte, hat er den Gegner Computer nicht verteufelt. Er hat ihn umarmt und versucht, mit Hilfe von Software insgesamt zu einem besseren Spieler zu werden. Er hat sich von Software beim Schachspiel sozusagen beraten lassen. Damit hat er dann jeden der willig war herausgefordert. Und geschlagen wurde Kasparov von zwei Unbekannten.

Darauf bezieht sich das Zitat. Kasparov war überrascht, dass ihn, den Großmeister (strong human), der sich auch noch von einer Maschine unterstützen ließ, zwei Nobodys in Schachdingen (weak human) übertreffen konnten, die sich auch von einer Maschine unterstützen ließen. Sein Schluss daraus: Sie hatten einen “better process”, mit dem sie gearbeitet haben.

Wenn mir eines immer bewusster geworden ist in den letzten Jahren, dann ist das die Begrenztheit der Möglichkeiten zu Veränderungen in Teams. Alle sind nämlich heute am Limit. Inwiefern das selbstverschuldet oder schlechtem Management anzuhängen ist, lasse ich mal dahingestellt. Es ist einfach so. Damit müssen wir leben.

Außerdem ist die Ausbildung von Softwareentwicklern immer noch (oder inzwischen?) schlecht. Nicht überall natürlich, aber weitestgehend.

Unterm Strich bedeutet das, Software wird vor allem von “weak humans” geschrieben. Wir brauchen daher Techniken, Methoden, Konzepte für diese “weak humans”. Das bedeutet, sie müssen sehr explizit und konkret sein. Sie müssen stark an die Hand nehmen. Sie müssen in kleinen Schritten zwischendurch erlernbar sein. Sie müssen in einer Welt voller legacy code und mittelalterlichem Management “bootstrapfähig” sein.

Das sage ich mit einer guten Portion Selbstkritik. Was Stefan Lieser und ich unter www.clean-code-developer.de zusammengetragen haben, entspricht dieser Forderung auch nicht immer. Und auch der Flow-Design Ansatz kann noch darauf zugefeilt werden.

Insgesamt jedoch habe ich in dem Kasparov-Zitat meinen Anspruch formuliert gefunden: Ich bemühe mich und will mich noch mehr bemühen, “normalsterblichen” Entwicklern zu helfen. Die große Zahl “weak humans” ist meine Zielgruppe. Für sie muss ich versuchen, was immer ich beschreibe, verständlich und nützlich zu machen.

Und ich glaube, danach sollten wir in der Branche insgesamt mehr streben. Wir müssen erkennen, dass die Softwarekrise nur mit mehr Entwicklern bewältigbar ist. Das führt allerdings fast notwendig dazu, dass deren allgemeines Ausbildungsniveau nicht steigt. Also sind Veränderungen im Alltag nur erfolgversprechend, wenn sie “weak humans” und “weak teams” und “weak companies” adressieren.

Damit will ich niemanden von einer (Selbst)Verantwortung zum Lernen und Streben nach Verbesserung entheben. Es geht mir vielmehr um die Anerkenntnis der schwierigen Umstände in und um Teams. Wir reden ja bei all den zitierten Methoden vor allem darüber, “Softwareherstellungsmaschinen” während des Laufens umzubauen. Und das ist eben schwierig. Da müssen wir bescheiden werden. Wir müssen auch sensibel werden für die Grunde, warum diese Methode oder jener Ansatz nicht so fruchtet, wie wir es uns vorstellen. Liegt das wirklich an unfähigen Entwicklern – oder sollten wir häufiger selbstkritisch sein und schauen, wo Methode und Ansatz verbessert werden können?

Als Lohn der Mühe winkt gem. Kasparov, dass noch bessere Methoden und Prozesse als heute diese “weak humans” in die Lage versetzen, bessere Software zu schreiben, als die heutigen “Meister” mir ihren Helfern es können. Ist das nicht ein attraktives Ziel, das wir versuchen sollten zu erreichen?

Wo Erfolge heute zu verzeichnen sind, ist das selbstverständlich toll. Aber wir sollten uns von diesen Erfolgen nicht einlullen lassen. Sie können unversehens zum Feind des Besseren mutieren. Das wäre doch schade, oder?

Also: Es gibt keine Methode, kein Konzept, keine Technik, keine Technologie, die nicht noch besser, vor allem einfacher gemacht werden könnte - und somit für “weak humans” leichter zu adaptieren ist. Usability ist auch hier Trumpf.

Donnerstag, 14. Februar 2013

Experiment Selbstverlag - Mein erstes Kindle-Buch

imageMein neues Buch ist endlich erschienen:

Systematisch produktiver und zufriedener
Pragmatische Schritte raus aus der Überlastung am Arbeitsplatz

Als Kindle-Buch bei amazon. Hurra, geschafft, endlich, nach ein paar Anläufen.

Vor Jahren hatte ich ja schon einige Bücher geschrieben, Bücher auf Papier, pBooks. Da gab es allenfalls zaghafte Versuche, die auch mal in PDF herauszubringen. Aber jetzt, knapp 10 Jahre später, ist alles anders. Heute sind Bücher elektronisch gleichwertig, eben echte eBooks. Und deshalb sind Produktion und Veröffentlichung anders möglich. Einen Verlag braucht man dafür nicht mehr – höchstens für das Marketing.

Und so habe ich mich mal aufgemacht, ein eBook im Selbstverlag herauszubringen. Bei diesem Experiment geht es mir um die Form und den Prozess, weniger um den Inhalt. Deshalb ist der Inhalt auch eine Zweitverwertung von Blogartikeln. Das soll aber natürlich den Wert des eBooks nicht schmälern, denn als Zusammenfassung für bequemes Lesen in einem Stück und aufbereitet für eReader bietet es etwas fürs Geld.

Insofern ist das eBook natürlich auch ein Experiment in puncto Monetarisierung von Inhalten. Ja, das will ich nicht verschweigen. Es geht auch ums Geld. Warum auch nicht?

Allerdings finde ich die Monetarisierung über Werbung sehr unschön. Sie mag funktionieren, doch hat sie für mich den Beigeschmack des Unehrlichen. In ihr steckt auch immer der latente Keim von Prostitution. Aber ich will nicht abschweifen… ;-)

Ohne Einnahme von Geld durch Werbung bleibt für die Monetarisierung nur eine direkte Transaktion mit dem Leser. Aber wie? Für Blogbeiträge ist das auch im Jahr 2012 immer noch schwierig. Das hat weniger mit Technik zu tun, würde ich sagen, sondern eher mit mangelndem “Standard”. Paypal mögen viele nicht, bei Micropayment-Diensten wie Flattr will sich niemand so recht auch noch anmelden…

Und dann stellt sich auch noch die Frage, ob jemand für das Medium “HTML-Seite” etwas bezahlen möchte? Das fühlt sich nicht sehr wertig an, oder? Jedenfalls nicht, wenn man für einzelne Inhalte zahlen soll und für andere wieder nicht. Als Abo mag das irgendwie noch funktionieren und so können Zeitungen sich langsam hinter Paywalls zurückziehen. Für Autoren ist das jedoch keine Option.

amazon als Veröffentlichungsplattform

Aber es gibt ja amazon! Da kommt für Autoren dreierlei zusammen:

  • Erstens haben wahrscheinlich 99% aller Leser schon einmal bei amazon gekauft. Es gibt also keine Anmeldehürde zu überwinden, um Geld fließen zu lassen.
  • Zweitens hat amazon es Autoren vergleichsweise sehr leicht gemacht, Inhalte anzubieten.
  • Und drittens ist das Kindle-eBook-Format derzeit nach meinem Empfinden das für den Leser einfachste [1].

Bis sich einfache (Micro)Payment-Lösungen per Smartphone durchgesetzt haben, scheint mir amazon für Autoren der einfachste Weg, zu einem direkten, ehrlich Austauschverhältnis mit dem Leser.

Für einen Beitrag wie diesen, ist Monetarisierung natürlich keine Option. Abhängig von Umfang und Inhalt wird es immer Inhalte geben, die Blogger/Autoren im Allgemeinen und ich im Speziellen kostenlos anbieten. Für Umfangreicheres jedoch glaube ich daran, dass der Trend zu einer Monetarisierung gehen wird. Das schmeckt dann nicht jedem Leser – aber das ist ok. Mit denen, die übrig bleiben und sich darauf einlassen, habe ich gern ein engeres Verhältnis, das mehr auf Gegenseitigkeit beruht.

Doch das ist derzeit noch weitgehend Theorie. Zuerst mal ausprobieren, wie das überhaupt technisch und vom Prozess her geht mit dem Selbstverlegen.

Vorgehen bei der Veröffentlichung

imageamazon macht das wirklich sehr simpel. Einfach bei Kindle Direct Publishing (KDP) anmelden und Manuskript hochladen.

Das Manuskript kann in Word geschrieben werden. Einfach ein paar simple Formatierungsregeln beachten [2]. Dann ist das kein Problem.

Allerdings aufpassen mit Bildern. Da mögen ein paar Experimente angezeigt sein, je nachdem um was für Abbildungen es sich handelt, Grafiken oder Fotos. Am Anfang habe ich dafür ein kleines Testdokument mit Bildern in verschiedenen Auflösungen zusammengestellt und bei KDP immer wieder nach Veränderungen hochgeladen.

Auf der KDP-Seite gibt es eine Vorschau, die verschiedene Kindle-Reader simuliert, und einen Download für das Manuskript als .mobi-Datei. Statt auf die Simulationen habe ich mich dann lieber auf mein iPad verlassen :-) Damit kann ich auch in Abbildungen reinzoomen, um die Qualität zu überprüfen.

Am Ende habe ich die meiste Zeit in Word für Mac OS X gearbeitet und die Grafiken nur mit 72dpi Auflösung eingebunden. Nur am Schluss habe ich Word für Windows angeworfen, um das Inhaltsverzeichnis einzusetzen – das macht WinWord nämlich besser mit Verweisen auf die Kapitel – und für den Export nach HTML – auch der scheint subtil besser zu sein als bei MacWord.

Beim Titelbild ist dann noch etwas Kreativität gefragt. Kann man selbst machen, wie ich es getan habe. Aber ein Profigrafiker würde dafür auch kein Vermögen in Rechnung stellen. Mit einem einfarbigen Titelbild sollte man sich in jedem Fall nicht zufrieden geben ;-)

Nach dem Hochladen des finalen Manuskripts hat es dann nochmal 2 Tage gedauert, bis das Buch im amazon Shop zu sehen war. Das mag daran gelegen haben, dass ich es an einem Sonntag abgeschickt hatte. Dazu kam dann allerdings auch noch eine Nachfrage von amazon, die Material im Buch entdeckt hatten, das frei im Web verfügbar ist. Ja, wie denn auch nicht. Es sind ja meine Blogartikel :-) Da musste ich nochmal bestätigen, dass ich der Copyright-Inhaber bin.

Preisfindung

Ein bisschen hat mich noch die Preisfrage umgetrieben. Wie viel “darf” so ein eBook kosten? 0,99 EUR oder 1,49 EUR oder 2,68 EUR oder 5,79 EUR oder 8,45 EUR? Ich habe mich für den niedrigsten Preis entschieden, der mir als Autor noch 70% vom Nettoverkaufspreis bringt. Das ist ein Experiment. Runtergehen kann ich eher mit dem Preis, als ihn rauf zu setzen.

Einen einzelnen Artikel kann man bei vielen Zeitschriften online als PDF für 0,99 EUR oder 1,50 EUR kaufen. Das sind dann vielleicht 4-5 Heftseiten, also ca. 8-9 A4 Seiten. Mein eBook enthält aber rund 30 A4 Seiten, also ca. 3-4 Mal so viel wie ein Artikel. Deshalb denke ich, dass 2,68 EUR Verkaufspreis angemessen sind.

Ein anderer Vergleichsmaßstab ist für mich – wie auch im Klappentext angedeutet – der Preis eines Stückes Kuchen oder ein Heißgetränk im Coffeeshop. Da geben wir alle ohne zu zucken 2,50 EUR oder auch weit mehr aus, um uns einen sehr flüchtigen Genuss zu verschaffen. Insbesondere wenn wir “to go” wählen, leistet der Coffeeshop nicht mehr, als einen Becher auszugeben. Wir nutzen keine weitere Infrastruktur. So schlappen wir durch die Gegend mit 2,50 – 4,90 EUR in der Hand, die in 15 Minuten im Magen verschwunden sind. Für eine ganze Stunde “Genuss” würden wir mithin 15,00 – 20,00 EUR ausgeben – das ist mehr als im Kino. Selbst “Rauchgenuss” ist viel billiger.

Ich gebe auch Geld aus für solche modernen Heißgetränke. Dabei sitze ich jedoch meist in meinem Lieblings-Coffeeshop. Ich bezahle deshalb eher für die “Aufenthaltsmöglichkeit” als das Getränk. Aber einerlei. Wenn ich bereit bin, für solch flüchtigen Genuss soviel Geld auszugeben, dann finde ich es nicht zu hoch gegriffen, für 45 – 60 Minuten Lesestoff 2,68 EUR anzusetzen. Vor allem, wenn der Lesestoff auch noch den Anspruch hat nachzuwirken; ich hoffe ja, beim Leser etwas mit dem eBook-Inhalt zu bewirken.

Aber, wie gesagt, das mit dem Preis ist ein Experiment. Für Musik haben sich inzwischen 0,99 EUR pro Titel eingebürgert. Dafür bekommt man 3 – 6 Minuten Ohrenschmaus. Bei eBooks müssen wir noch herausfinden, wo Käufer und Leser möglichst reibungsfrei zusammenfinden.

Nach dem Experiment…

…ist vor dem Experiment. Mein Kindle-Experiment habe ich erstmal abgeschlossen. Von der Idee bis zur Verfügbarkeit des eBooks im amazon Shop hat es 6 Tage gedauert – davon 2 bei amazon.

Aber das Selbstverlegen ist damit nicht zuende. So ist das Buch nur bei amazon erhältlich. Wie kriege ich es denn aber auch noch auf andere Plattformen oder auf Papier – für die, die das dringend wollen?

Es sind also noch einige weitere Experimente nötig. Mit CreateSpace bietet amazon einen Weg zum Papier. Alternativ könnte ich mich aber auch für lulu.com oder epubli.de entscheiden. Und was ist mit anderen eBook-Plattformen? Wer den lokalen Buchhandel unterstützen möchte, hätte es lieber, ein eBook über einen anderen Kanal zu kaufen, bei dem auch sein Buchhändler etwas davon hat. Vielleicht probiere ich mal bookrix.de als Vermittler aus.

Und natürlich werde ich beim Inhalt experimentieren. Zweitverwertung als eBook finde ich völlig in Ordnung. Doch ich will auch neue Inhalte für eBooks schreiben.

Endnoten

[1] Kindle-Reader gibt es als Gerät und Apps für jede Plattform. Die Übertragung des eBooks auf Gerät/App ist ein no brainer. Und der Lesefortschritt sowie die Notizen werden über Gerät/App hinweg synchronisiert. Diese Einfachheit bietet derzeit sonst kein Anbieter, nicht Thalia mit textunes und auch nicht libri mit seiner eBook Reader App. Und iTunes? Ist zwar für den Leser einfach, doch für den Autor schwierig. Ohne Vermittler, der mir das Administrative und auch die Abrechnung einfacher macht, würde ich da nichts einstellen.

[2] Es gibt einige Literatur im Web und bei amazon dazu, was es bei Kindle-Büchern zu beachten gibt, z.B.

Dienstag, 12. Februar 2013

Wenn Agilität abhebt

Ich bin ja ein großer Freund von agilem und schlankem Vorgehen in der Softwareentwicklung. Aber derzeit scheint mir die Agilität ein bisschen unbescheiden. Sie hebt immer mal wieder ab. So auch in diesem Artikel von Henrik Kniberg (meine Hervorhebung):

image

Was soll das denn? Vor 2000 wussten Softwareunternehmen nicht, wie man Software ausliefert? Ja, ist das so gewesen?

Und vor 2000 haben Unternehmen keine nützliche, funktionierende Software (“working software”) ausgeliefert, falls sie es denn überhaupt geschafft haben, welche aus der Tür zu bekommen? Ja, ist das so gewesen?

Ich hatte in den 1980ern und 1990ernn schon den Eindruck, mit “working software” zu arbeiten. Und ich habe mit meiner Branchensoftwarefirma 10 Jahre lang selbst “working software” ausgeliefert. Sonst hätten mein Partner und ich uns und unseren Angestellten kein Gehalt zahlen können. (Er liefert übrigens immer noch “working software” aus und lebt davon – obwohl er ganz unagil arbeitet.)

Dass etwas im Argen war mit der Art, wie wir in unserer Branchensoftwarebude Software entwickelt und ausgeliefert haben, ist unbenommen. Und es liegt auch heute noch viel im Argen bei dem, wie Software entwickelt wird. Natürlich bin ich dafür, dass mehr Teams, agil und/oder schlank werden.

Aber wir sollten doch bitteschön nicht verkennen, wie viel geleistet wird ganz ohne Agilität und Schlankheit. Die Softwareentwicklung hat nicht erst mit der Agilität angefangen. Und Agilität ist bei weitem nicht in alle Köpfe, Teams und Unternehmen bisher vorgedrungen. Sind deshalb die, die noch nicht agil oder schlank sind, Hinterwäldler und Dummköpfe? Kriegen die nichts gebacken?

Ein bisschen Bescheidenheit stünde der Agilitätsbewegung gut zu Gesicht, finde ich. Mit solchen Aussagen gewinnt man keine neuen Freunde. Eine abgehobene, arrogante Agilität nützt niemandem.

Und wie kann das Abheben vermieden werden? Ich glaube weiterhin, dass dafür eine glasklare Definition von Agilität nötig ist. Henrik Kniberg bleibt Sie trotz Link auf das agile Manifest schuldig. Und Martin Fowler weiß es nicht besser, sondern erkennt sie einfach, wenn er ihr mal begegnen sollte.

Ob Sie nun meiner persönlichen Definition von Agilität zustimmen oder ihre eigene haben… Wir sollten uns darüber unterhalten, was Agilität im Kern ausmacht. Dass mit ihr “working software” hergestellt werden kann, ist nichts Neues und nichts Besonderes. Ein bisschen mehr Butter bei die Fische darf schon sein.

Was ist also das klar umrissene Nutzenversprechen der Agilität, das sie vom Bisherigen abhebt? Wie kann Agilität einem gestandenen Geschäftsführer, der seit 20 Jahren nützliche und funktionierende Software an den Markt bringt und 20 Leute damit ernährt, in nicht arroganter Weise nahegebracht werden?

Und dann: Mit welchen Merkmalen, Hilfsmitteln, Methoden, Konzepten, Paradigmen versucht Agilität dieses Nutzenversprechen einzulösen? Warum sollen die funktionieren und nicht anderes?

Mein Empfinden ist, dass es bei all der Literatur zu Agilität noch Erklärungsbedarf gibt. Die Marketing-Arbeit der Agilisten ist nicht vorbei. Die Zeit für siegerhafte Arroganz ist noch nicht gekommen. Und sollte am besten auch nie kommen.

 

PS: Und dass das heutige Problem vor allem sei, das falsche Produkt zu bauen, halte ich auch noch nicht für ausgemacht. Sicherlich ist das ein nicht zu leugnendes Problem – nur hat das nicht speziell mit Softwareentwicklung zu tun. Für viel dringender halte ich das Problem mangelnder Evolvierbarkeit. Die Unwartbarkeit, das Brownfield, die Legacy Code Berge… sie drohen überall. Denn was will man denn machen, wenn man denn nun endlich weiß, welches Produkt man bauen will – und man kriegt es nicht hin? Dann ist das Händeringen umso größer. Deshalb: Ich bin dafür, zuerst die Bedingung für die Möglichkeit des richtigen Produktes anzugehen. Und das ist eine Codebasis, die sich in welche Richtung auch immer geschmeidig anpassen lässt.

Donnerstag, 7. Februar 2013

Performance vom Schwanz her aufzäumen

Wer ist es eigentlich, der höhere Performance fordert? Der Kunde, ist doch klar. Den meine ich aber nicht. Mir geht es um etwas Grundlegenderes.

Wenn wir über Performance reden, dann haben wir ein Delta im Hinterkopf, eine Dauer zwischen zwei Ereignissen. Es geht um ein auslösendes Ereignis, an dem Input hängt, und ein abschließendes Ereignis, an dem Output hängt.

Das kann die Dauer zwischen der Eingabe einer URL und der Anzeige der Webseite sein; das kann die Dauer zwischen der Eingabe von Zahlen und der Anzeige einer Funktionskurve sein; das kann die Dauer zwischen dem Bewegen eines Mauszeigers und der Reaktion einer Figur auf dem Bildschirm sein.

Immer geht es um Delta = T_Ergebnis – T_Auslöser. Höhere Performance bedeutet, dass Delta kleiner wird.

Jetzt zurück zur Ausgangsfrage: Wer fordert denn aber, dass Delta kleiner wird?

Es sind zwei Rollen im Spiel: der Auslöser und der Empfänger. Der Auslöser startet die Verarbeitung, deren Performance in Frage steht. Er liefert den Input oder zumindest Teile davon; der Rest kann aus Zustand kommen, auf den die Verarbeitung Zugriff hat.

Der Empfänger andererseits ist derjenige, den das Ergebnis der vom Auslöser gestarteten Verarbeitung interessiert. Er betrachtet den Output und fällt daraufhin Entscheidungen. Output ist ein Unterschied, der für ihn einen Unterschied macht, d.h. Information.

image

Nun zum dritten Mal: Und wer interessiert sich nun für ein kleineres Delta? Immer nur der Empfänger.

Das ist mir heute aufgegangen. Es ist immer nur der Empfänger von Output, dem daran gelegen sein kann, den Abstand zum auslösenden Ereignis zu reduzieren, weil das irgendeinen Vorteil für ihn hat.

Es hat lange für mich gedauert, bis ich das begriffen habe. Bis heute. Jahr um Jahr hab ich geglaubt es sei der Auslöser, den Performance interessiert. Oder genauer: Ich habe Auslöser und Empfänger nicht einmal unterschieden. Sie sind oft dieselbe Person oder ich habe vor allem mit dem Auslöser als Kunde gesprochen.

Aber es gibt in Performancefragen immer beide Rollen und nur der Empfänger ist maßgeblich.

Das hat nun Auswirkungen auf die Performancediskussion, würde ich sagen. Denn eine Steigerung der Performance ist nur da relevant, wo ein Empfänger erstens eine Ahnung davon hat, wann das auslösende Ereignis vor dem Empfang stattgefunden hat. Oder es ist generell von Wert, dass das Delta zwischen Auslöser und Empfang klein ist, auch wenn unklar ist, wann das auslösende Ereignis stattgefunden hat. Beispiele:

  • Sie sind Empfänger von Informationen über Bücher. Die Performance ist für Sie dabei vor allem wichtig, wenn Sie Auslöser der Beschaffung dieser Informationen sind. Sie stöbern bei amazon und wünschen sich natürlich möglichst zügig ein Abfrageergebnis. In dieser Doppelrolle ist Performance für Sie wichtig.
  • Sie sind wieder Empfänger von Informationen über Bücher. Dieses Mal haben Sie jedoch keine Kontrolle über eine Auslösung. Das initiale Ereignis für eine Verarbeitung ist nicht eine Abfrage, sondern das Einpflegen von Titelinformationen. In diesem Fall interessiert Sie die Performance des online Shops nicht. Wenn überhaupt, machen Sie sich Gedanken über die Performance von amazon als Organisation mit ihren Prozessen.
  • Jetzt sind Sie Empfänger von Adressinformationen in einer Faktura-Anwendung. Sie müssen Kunden mahnen. Was ist das für Sie auslösende Ereignis in Bezug auf Adressen? Das Einpflegen. Damit haben Sie nichts zu tun. Das macht eine andere Abteilung, z.B. der Verkauf, jedenfalls nicht die Buchhaltung. Wie lange das Softwaresystem also braucht, um eine Adresse “zu verarbeiten”, ist für Sie nicht wichtig.
  • Auf der anderen Seite sitzt nun der Auslöser einer Adressveränderung. Ist der an der Performance einer Adressenspeicherung interessiert? Nein. Er ist ja nur Auslöser. Wenn er eine Datenänderung erfasst hat, will er nur schnell weiter zur nächsten Datenerfassung. Wie lange es dauert, bis eine Adressenänderung überall wirksam wird, ist ihm einerlei, da er kein Empfänger ist.
  • Schließlich sind Sie Geschäftsführer einer Pizzakette. Sie sind Empfänger von Verkaufszahlen. Was ist Ihre Erwartung an die Performance der Unternehmenssoftware? Auf Ihre Abfrage wollen Sie schnell eine Antwort. Da sind sie sowohl Auslöser wie Empfänger. Aber der Auslöser einer Veränderung im Datenbestand sind Sie nicht. Sekunden- oder auch nur stundengenaue Auskunft müssen Sie nicht über die Zahlenentwicklung haben. Ihnen reichen die Zahlen des Vortages oder gar der Vorwoche.

Sie ahnen, worauf ich hinaus will. Ich möchte eine Lanze für mehr asynchrone Verarbeitung brechen. Und ich möchte dafür sensibilisieren, dass Performance im Auge des Empfängers liegt. Die Frage ist immer, was der für ein Verhältnis zu welchem auslösenden Ereignis hat.

Ist das auslösende Ereignis eine Query oder die Ankunft von Daten im System?

Mein Empfinden ist, dass das zu oft nicht unterschieden wird. Der Auslöser wird naiv gleichgesetzt mit dem Empfänger. Und beim Empfänger denkt man nicht an den Unterschied zwischen Datenankunft und Abfrageauslösung.

Solange hohe Performance billig herzustellen ist, macht die fehlende Unterscheidung auch nichts. Wenn Performance aber anfängt die Diskussion zu dominieren, wenn sie sich immer wieder in den Weg der Arbeit an anderen Aspekten wirft, dann sollten Sie genauer hinschauen.

Mir scheinen drei Begriffe dafür interessant: Änderungsfrequenz, Abtastfrequenz und Reaktionszeit.

Die Änderungsfrequenz gibt an, wie häufig sich Daten in relevanter Weise ändern. Mein Lieblingsautor schreibt jedes Jahr ein Buch. Der Benzinpreis ändert sich vielleicht 5 Mal am Tag bei meiner Tankstelle. Die Temperatur draußen ändert sich unter Umständen stündlich.

Wenn für mich diese Ereignisse grundsätzlich interessant sind, so ist nicht unbedingt jedes Ereignis auch relevant. Ich möchte zwar keine Neuerscheinung meines Lieblingsautors verpassen, interessiert mich der Benzinpreis nur alle paar Tage und die Temperatur auch nur gelegentlich während des Tages.

Während die Änderungsfrequenzen 1/Jahr, 5/Tag und 24/Tag sind, sind meine Abtastfrequenzen vielleicht 12/Jahr, 1/Woche und 2/Tag.

Wenn ich häufiger abtaste als das Ereignis eintritt, dann ist mir an möglichst kleinem Verzug gelegen. Besser als solches Polling wäre natürlich eine Benachrichtigung. Leider bietet amazon so etwas bisher nicht an, wenn ich es recht sehe. Beim Buch möchte ich im Mittel also nicht später als 2 Wochen nach Erscheinen (halbe Periode der Abtastfrequenz) darüber informiert sein. amazons Performance muss nur so gut sein, wie mein Anspruch an meine Reaktionszeit ist.

Wenn sich Daten jedoch häufiger ändern als ich abtaste… dann ist mir die Aktualität offensichtlich nicht so wichtig. Zwischen zwei Abtastungen kann sich ja viel ändern. Der Benzinpreis könnte um 50% schwanken, ohne dass ich es merke, auch ohne dass es für mich relevant wäre, weil mein Tank noch so voll ist, dass sich nachtanken nicht lohnte. Was bedeutet das für die Performance der Datenpflege? Ich denke, die Performance ist in diesem Fall nicht so wichtig. Denn ob ich den tatsächlich aktuellen Wert erfahre, wo mich doch zwischenzeitliche Werte nicht interessiert haben, oder einen etwas älteren, weil die Verarbeitung des aktuellen noch nicht abgeschlossen ist, das scheint mir einerlei.

Daraus ergibt sich für mich zusammenfassend:

In Performancefragen ist zuerst zu klären, wer Auslöser und Empfänger einer Datenveränderung sind.

Dass sowohl Auslöser wie Empfänger in Bezug auf ihre unmittelbare Interaktion mit der Software immer beides sind, ist trivial. Dadurch darf man sich nicht verwirren lassen. Wenn einer von beiden einen Befehl absetzt, dann will er schnellstmöglich danach weitermachen. Die Software soll nicht einfrieren. Das ist jedoch unabhängig von den dahinter stehenden Datenänderungen im System!

Wer Daten einpflegt, will nur sicher sein, dass die Daten der weiteren Verarbeitung verlässlich übergeben wurden. Das muss schnellstmöglich geschehen.

Und wer Daten abfragt, der will schnellstmöglich eine Antwort. Wie aktuell die Daten jedoch sind, die in die Antwort eingehen, das ist eine ganz andere Sache.

image

Zwischen Übergabe an die Verarbeitung und Abfrage kann eine lange Zeit liegen. Nur diese Zeit ist maßgeblich für die Verarbeitungsgeschwindigkeit. Ausschlaggebend ist die gewünschte Reaktionszeit. Denn danach wird sich ein Empfänger für seine Abtastfrequenz richten; die muss seinem Anspruch an Datenaktualität entsprechen.

Performanceoptimierung ist mithin eine Sache des Zugs: Wie oft zieht ein Interessent an den Daten? Wie ist seine Abtastfrequenz in Bezug auf die Änderungsfrequenz der Daten?

Zäumen Sie das Performance-Pferd von diesem Schwanz her auf. Nur wenn die Abtastfrequenz über der Änderungsfrequenz liegt, scheint mir Tuningaufwand gerechtfertigt. Und unterscheiden Sie deutlich zwischen dem, der Datenänderungen anstößt (Einpfleger) und dem, der sie empfängt (Abfrager). Meine Vermutung ist: Sie werden viele Möglichkeiten finden, die Datenverarbeitung asynchron zu machen. Und das ist eine sehr entspannende Aussicht, finde ich.

Freitag, 1. Februar 2013

Softwareentwurf in der hohlen Hand

Gerade komme ich wieder von drei Trainingstagen zum Thema agiler Softwareentwurf. Ein inhouse Training für ehemalige C bzw. C++ Entwickler, die sich noch an .NET gewöhnen müssen. Aber es hat vielleicht gerade deshalb besonders Spaß gemacht, weil sie noch nicht so “objektorientiert verdorben” sind. Ihnen steckt vor allem die C-Praxis in den Knochen, die sie anscheinend offen für den Softwareentwurf mit Softwareuniversum und Flow-Design gemacht hat. Denn wenn die prozedurale Programmierung in einem Recht hatte, dann darin, dass es zuerst und vor allem um Funktionalität und nicht um Daten geht.

Aber von dem positiven Feedback, das Stefan Lieser und ich dort bekommen haben, wollte ich gar nicht sprechen. Die gute Stimmung bei den Trainingsteilnehmern hat vielmehr mich selbst nochmal unseren Ansatz frisch erleben lassen.

Abends saß ich nämlich im Hotel bei einem gemütlichen Kaminfeuerchen und habe an einer Anwendungsidee herumgebastelt. Eine Web-Anwendung, die helfen soll, eine Klippe des Kennenlernens über online Partnerbörsen zu umschiffen. Wie das gehen soll wird die dotnetpro demnächst berichten ;-) Technologisch mit von der Partie werden jedenfalls AppHarbor, Nancy und iron.io sein.

Da saß ich also und habe darüber nachgedacht, wie die Idee in Software funktionieren könnte. Welche Seiten sollte so ein Website haben? Wie sollten die Seitenübergänge sein? Was sollte dabei passieren? Welche Klassen/Komponenten sollte es geben? Wie würden Feature ineinander greifen?

Im Kopf kann ich so etwas bewegen – aber nur bis zu einer gewissen Grenze. Dann muss ich einen Dump machen. Irgendwie muss ich meine Gedanken und Ergebnisse manifestieren. Visual Studio hatte ich vor dem Kamin aber nicht zur Hand. Warum auch? Ich brauche keinen Code, um Software zu entwerfen.

So hat mir denn ein kleiner Notizblock des Hotels geholfen. Der war nur handtellergroß mit schmalen Blättern. Aber er hat mir gereicht wie diese Bilder zeigen:

image

image

Wenn Sie nicht wissen, was die Symbole bedeuten und meine Schrift nicht lesen können, macht das nichts. Ich will Ihnen ja nicht erklären, was die Anwendung tut und wie ich gedenke, das zu realisieren.

An dieser Stelle soll nur rüberkommen, dass ich mit zwei kleinen Zetteln die Software entwerfen konnte. Ich weiß nun, welche Klassen ist brauche. Ich weiß, welche Methoden die haben. Ich weiß, wie alles zusammenhängt und die Funktionalität herstellt. Ich weiß, welche Daten fließen. Ich weiß, wie ich APIs kapseln muss. Ich weiß sogar schon, wie ein guter Teil des Codes aussehen muss.

Den Entwurf der beiden Bilder kann ich “so runtercodieren”. Da ist alles drin, was ich brauche. Nur das Layout der Seiten fehlt. Aber das gehört ja auch nicht zum funktionalen Entwurf. Wichtig ist dafür nur, welche Interaktionen der Benutzer mit Dialogen hat. Und die stecken in dem Modell drin.

Durch das Training geöffnet habe ich frisch erlebt, wie effektiv Softwareentwurf mit Papier und Stift sein kann – wenn man eine geeignete Methode hat ;-) Ich muss nicht darauf warten, dass Softwarestrukturen durch Tests wachsen. Ich brauche keine IDE, um bei einer Anwendung voran zu kommen.

Klar, am Ende muss implementiert werden. Bubbles don´t crash. Ich weiß nicht, ob mein Entwurf 100% korrekt/ausreichend ist. Aber ich bin ein gutes Stück voran gekommen. Ich habe mit “systematischer Kritzelei” das Codieren solide vorbereitet. Wenn ich dann Details mit TDD austreiben will, weiß ich, wo ich ansetzen kann.

Und all das steckt in zwei Blättchen mit Flow-Designs. Das fand selbst ich, der ich täglich mit Flow-Design arbeite, irgendwie bemerkenswert cool.