Freitag, 29. März 2013

Ab auf die Walz – Software Craftsmanship ernst genommen

Jetzt will ich es endlich tun. Ich will auf die Walz gehen. Schon länger hatte ich mit dem Gedanken gespielt – doch mehr als der prio walk mit Stefan Lieser von München nach Nürnberg ist bisher nicht draus geworden.

Nun möchte ich des Rest aber nachholen. Gewandert bin ich genug. Jetzt möchte ich mitmachen. An verschiedenen Stationen möchte ich Teamkulturen kennenlernen, indem ich für eine begrenzte Zeit “ganz normal” mitarbeite. Als Trainer und Berater sehe ich zwar viele Teams, doch ist da mein Auftrag ihnen bei Veränderungen zu helfen. Deshalb erfahre ich nicht unbedingt, wie sie heute leben und weben. Das möchte ich ändern, indem ich mich ein Teil von ihnen werde. Mit geht es um den Ist-Zustand.

Den Artikel zu dieser Idee für Frühjahr und Sommer 2013 finden Sie in der dotnetpro 5/2013 – oder hier zum bequemen sofortigen Lesen:

Ich würde mich freuen, wenn Sie mir eine Station für meine Walz anbieten würden. Meine Erfahrungen kommen am Ende der Community zugute.

Dienstag, 26. März 2013

Entwerfen fürs Schreiben

Gerade merke ich es am eigenen Leibe, wie wichtig Entwurf ist. Nein, Software entwickle ich gerade nicht. Ich schreibe Texte. Aber ich erlaube mir, meine Erfahrungen dort auf die Softwareentwicklung zu übertragen.

Dass ich viel schreibe, ist ja nichts Neues. Warum fällt mir das Thema Entwurf dazu gerade jetzt ein? Weil ich anders schreibe als sonst.

Normalerweise sind meine Texte bis 20-25 A4 Seiten lang, inklusive Code, Abbildungen, Tabellen. Und das Thema kenne ich gut. Solche Texte kann ich leicht runterschreiben. Thema überlegen – es reicht eine grobe Überschrift oder die Idee für eine Anwendung – und dann losschreiben. Der Rest ergibt sich von selbst. Der Text gliedern sich beim Schreiben von allein. Manchmal unterbreche ich das Schreiben, um zuerst etwas zu codieren, danach geht es dann umso flotter weiter. Alles kein Problem.

Aber jetzt sitze ich an zwei Texten, die sind anders. Da kann ich nicht einfach so drauflosschreiben. Meine Schreiberfahrung und Themenkenntnis gliedert den Text nicht just-in-time. Deshalb muss ich einen Gang zurückschalten. Nachdenken ist angesagt. Entwerfen.

Das Drehbuch

Der eine Text ist ein Drehbuch. Das schreibe ich mit einer Freundin zusammen. Trotzdem ist es nicht einfach. Form und Genre sind für mich neu. Zum Inhalt nur soviel: Es ist eine Liebeskomödie. (Ja, ja, ich höre schon die Lacher. Aber mir macht es Spaß, mal etwas ganz anderes auszuprobieren. Hat sich einfach so ergeben. Ob das mal verfilmt wird? Vielleicht. Erstmal interessiert mich der Prozess.)

Bei dem Dreh konnten wir nicht einfach drauf los schreiben. Wir hatten zwar eine Idee – doch für 90 oder 100 Minuten Film ist das nicht genug. Da muss man sich an den Text ranrobben. Das ist Entwurfsarbeit at its best. Dazu gibt es eine Menge Literatur. Ein Klassiker ist Syd Fields “Das Drehbuch”. Den hatte ich schon vor vielen Jahren mal gelesen… und jetzt ziehe ich endlich Nutzen daraus. Die Zeit musste einfach reif werden. (Es hilft natürlich auch, dass ich nun eine Co-Autorin mit Erfahrung in dem Metier habe.)

Und was tun wir? Wir strukturieren. Kein Satz “Drehbuchcode” ist bisher geschrieben. Stattdessen hangeln wir uns top-down heran.

  • Ebene 1: Die Idee. Wir überlegen ganz grob, worum es geht. Was ist der Kern der Story? Welche Entwicklung sollen die Protagonisten durchlaufen? Haben wir eine “Moral von der Geschicht”? Das ganze passt auf 1 A4 Seite.
  • Ebene 2: Wir verfeinern die Geschichte. Ein paar Highlights kommen auf den Zettel. Wir überlegen, was Wendepunkte sein könnten. Woraus könnten sich Konflikte speisen? Erste Charakterstudien für die wichtigsten Protagonisten. Das passt auf vielleicht 10 A4 Seiten.
  • Ebene 3.1: Nächste Verfeinerungsstufe. Jetzt überlegen wir uns einen Handlungsverlauf, der für bestimmte Entwicklungen/Phasen schon Minutenzahlen zugeordnet bekommt. Dafür gibt es sogar eine Entwurfsskizze. Das ist schon alles ziemlich detailliert. Insgesamt kommen am Ende 30-40 Seiten Fließtext heraus, das sog. Treatment. Die Geschichte wird grob in Szenen eingeteilt erzählt.

image

  • Ebene 3.2: Gleichzeitig entwickeln sich die Charaktere. Wir erfahren immer mehr über ihre Motive und ihr Vorleben. Manchmal geben wir ihnen bewusst gewisse Züge, um in der Geschichte etwas zu bewirken; aber manchmal ergeben die sich von allein. Als hätten die Figuren ein Eigenleben.
  • Ebene 4: Das Drehbuch wird geschrieben. So richtig mit Dialogen und Einstellungsbeschreibungen. Der Umfang wird ca. 90-100 Seiten haben. Aber da sind wir noch nicht.

Dass es so am besten funktioniert, habe ich immer gewusst. Aber ich habe nie selbst Zugang zu diesem Vorgehen gefunden. Insofern kann ich jetzt besser mit Ihnen fühlen, falls Sie es schwierig finden, sich auf Softwareentwurf einzulassen.

Mit ein wenig Anleitung durch meine Co-Autorin hab ich jetzt aber den Sprung in dieses Vorgehen geschafft – und es macht Spaß. Wir haben auch eine schöne Rollenverteilung gefunden. Ich bin eher der Mann fürs Grobe :-) Sie feilt dann die Details aus. Auch beim Drehbuch konzentriere ich mich also eher auf den Entwurf, das Big Picture, den Rahmen. Das liegt mir irgendwie.

Natürlich geht es am Ende wie bei Software nicht ohne Details. Die dreckige Dialogrealität will ausgefleischt werden. Aber das geht viel, viel einfacher auf der Basis eines mehrstufigen Entwurfs.

Der ist auch auf jeder Ebene ein Durchstich. Die ganze Geschichte wird immer wieder erzählt – nur eben mit unterschiedlichem Detailierungsgrad.

Das läuft natürlich nicht sauber in Wasserfallmanier von oben nach unten ab. Es ist eher Jo-Jo-Schreiben. Mal ganz oben, dann wieder kurz durchfallen auf die unterste Ebene. Wir wissen schon ein paar Einstellungen und Sätze für Dialoge, auch wenn wir eigentlich noch beim Treatment sind. Und manchmal gehen wir auch wieder auf eine höhere Ebene und drehen den Fluss der Geschichte in eine andere Richtung.

Das Fachbuch

Das Drehbuch ist Hobby. Arbeit ist das Buch über Flow-Design, an das ich mich nun endlich doch mal gemacht habe. Die Nachfrage ist gestiegen – und ich empfinde das Thema als inzwischen so stabil, dass sich das Aufschreiben lohnt.

Hier bin ich nun voll in meinem Metier. Trotzdem kann ich nicht einfach losschreiben. Dafür ist der Scope zu groß. Ein Blogposting wie dieses oder einen Artikel von 10 Heftseiten für die dotnetpro sind überschaubar. Da kann ich die Text-IDE Word aufklappen und losschreiben. Aber ein ganzes Buch… Nein, das geht nicht. Das würde zwar am Ende auch irgendwie alle Details enthalten – nur in einer nicht sehr leserfreundlichen Weise. Einen so großen Text beim Schreiben entstehen zu lassen, würde zuviel Refaktorisierungsaufwand bedeuten. Das ist wie beim Coden.

Also denke ich vorher über den Text nach. Auch auf mehreren Abstraktionsebenen.

  • Ebene 1: Grobe Gliederung. Ich unterteile das Thema in Bände. Jeden einzelnen will ich als eBook veröffentlichen, sobald er fertig ist. Es kommt also nicht alles erst am Ende als ein Buch heraus.

image

  • Ebene 2: Gliederung eines Bandes. Zu jedem Band überlege ich mir eine Kapiteleinteilung für den groben Erklärungsfluss. Das habe ich aber bisher nur für Band 1 getan. Ich möchte mich konzentrieren auf ein Inkrement. Denn nichts anderes sind die Bände. Jeder für sich bietet schon einen Nutzen.
    Ob es am Ende wirklich soviele Bände oder genau die werden? Keine Ahnung. Das ist auch nicht wichtig. Bisher ist ja kein Aufwand in Band 2 bis 5 geflossen.

image

  • Ebene 3: Ich schreibe mir zu jedem Kapitel Stichpunkte auf. Eine Sammlung von Material, das ich in den Kapitel behandeln will. Das habe ich für Band 1 auch für alle Kapitel gemacht. Hier nur ein Auszug für ein Kapitel:

image

  • Ebene 4: Ich schreibe jedes Kapitel in einem eigenen Word-Dokument. Das mache ich vor allem, um mich besser fokussieren zu können. Aber ich will damit auch technischen Problemen vorbeugen. Wer weiß, wie Word reagiert, wenn am Ende hunderte Bilder in einem Dokument sind und dessen Größe mehrere hundert MB umfasst?
    Das ist jetzt “Codieren”: Ich schreibe den Text, den Sie später lesen sollen. Aber beim Schreiben gliedere ich weiter. Das ist sozusagen situativer Entwurf. Vorher war strategischer und taktischer Entwurf. Hier die dabei entstandene Gliederung für ein Kapitel:

image

Auch dieser Prozess ist natürlich jo-jo-iterativ. Ich kann vom Schreiben jederzeit wieder in den Entwurf. Und ich kann jederzeit zwischen den Entwurfsebenen wechseln.

Der Entwurf gibt mir einen Überblick und definiert Ösen, in die ich dann Fließtext einhängen kann. Ich kann jederzeit an jeder Stelle arbeiten, weil ich weiß, wie der Zusammenhang ist. Aber ich ziehe es vor, Inkremente zu produzieren. Zuviel Springen ist auch nicht gut. Außerdem habe ich zu bestimmten Themenblöcken, die weiter hinten im Buch liegen, noch keine rechte Idee, wie sie konkreter aussehen sollten. Das mache ich auch abhängig von dem, was ich weiter vorne an Text produziere oder was mir bis dahin noch für Gedanken kommen mögen. Ich will keine Entwurfshalde auftürmen.

Auch beim Fachbuch erfahre ich also, wie wichtig es ist, vor dem Codieren, äh, Schreiben mal nachzudenken, einen Rahmen aufzuziehen, ein Big Picture zu entwickeln. Ein Plan ist gut – und ich erwarte nicht, dass der 100% eingehalten wird. Trotzdem ist er besser als keiner.

Beim Entwurf geht es nicht darum, alles komplett vorwegzunehmen. Dann wäre es kein Entwurf mehr, sondern schon Schreiben bzw. Codieren. Ein Entwurf ordnet vielmehr grob. Sein Ergebnis sind handhabbare Blöcke. Ein Kapitel ist so groß wie ein Artikel. Das kann ich ad hoc formulieren und strukturieren. Ein ganzes Buch jedoch, das ist zu groß. Dito bei Software. Eine ganze Software ist zu groß, um einfach mit dem Codieren anzufangen. Aber eine Klasse oder eine Methode, die durch einen guten Entwurf entstehen, sind überschaubar.

Es braucht daher für Software wie für Texte eine Entwurfsmethode. Das ist mir jetzt durch diese Schreiberfahrungen nochmal klar geworden.

Donnerstag, 21. März 2013

Softwareentwurf als ökonomische Notwendigkeit

Softwareentwurf ist selbstverständlich kein neues Thema. Früher war Softwareentwurf als der Codierung vorgelagerte Tätigkeit sogar ein zentraler Punkt jeder Informatikerausbildung. Das war dem Mangel an Prozessorkapazität und Speicher geschuldet. Die Turnaround-Zeiten bei der Codierung waren so lang, dass man sich besser gut überlegte, was man schreibt und dann der Übersetzung übergibt und dann laufen lässt. Das Motto lautete geradezu Think before coding. Fehler waren teuer. Ausdrücklicher Softwareentwurf war also ein ökonomisches Gebot.

Ab der zweiten Hälfte der 1980er begannen sich die Verhältnisse jedoch umzukehren, glaube ich. Der Preis für Fehler beim Codieren fiel plötzlich stark. Zuerst wurde die Hardware besser und verfügbarer (Stichwort PC), dann wurden die Codierungswerkzeuge besser (Stichwort RAD).

Der Engpass „Werkzeug“ wurde bald so weit, dass er durch einen anderen abgelöst wurde. Code war nicht länger das Problem, sondern Codierer. Es kam einfach nicht soviel Funktionalität auf die Straße, wie Kunden sich das wünschten. Das hatte mehrere Folgen:

  • Codierer konzentrierten sich mehr und mehr aufs Codieren. Der Softwareentwurf fiel aus der Gnade, da er scheinbar wertvolle Codierkapazität raubte. Man fühlte sich von der Knechtschaft durch die Maschine befreit und schüttelte daher gern damit bisher verbundene Notwendigkeiten ab.
  • Die Ausbildung der Codierer wurde „entformalisiert“, so dass sich schneller mehr von ihnen auf den Markt bringen ließen. Softwareentwurf war Ballast im Curriculum.
  • Man sann auf Mittel, sich das Codieren überhaupt zu sparen. „Wiederverwendbarkeit herstellen!“ wurde zum Schlachtruf der Softwareentwicklung.

Während viele Jahrzehnte lang (die 1950er bis 1980er) die Softwareentwicklung sich im Grunde um sich selbst drehen musste aufgrund einer eklatanten Mangelsituation bei der Hardware, verschob sich ab den 1990ern endlich der Fokus auf den Kunden. Und der ist seitdem unersättlich. Der Hunger nach Software ist ungebrochen, nein, sogar im stetigen Wachsen begriffen. Mehr Software und länger lebende Software sind gefragt. Berge an funktionalen und nicht-funktionalen Anforderungen soll die Softwareentwicklung erfüllen. Und zwar ASAP!

Software überhaupt auf die Straße zu bekommen, ist seitdem wichtiger, als alles andere. Nachfrage nicht zu bedienen kommt teurer als fehlerhafte Software. Nur in wenigen Bereichen ist die ökonomische Priorität bisher anders geblieben: da wo es um Menschenleben geht.

Doch dieses Bild verschiebt sich. Der Engpass hat sich wieder verschoben. Codierer sind nicht mehr das Problem, auch wenn weiterhin Softwareentwickler fehlen. Die grundsätzliche Kapazität von Codierern ist heute durch Hardware, Softwareinfrastruktur, Tools, Bibliotheken sowie Entwicklungs- und laufzeitplattformen so hoch... dass sie sich selbst durch ihre Produktionsweise behindern.

Der Engpass ist von Ressourcen – zuerst Hardware, dann Softwarewerkzeuge und Menschen – zu einem Grundsatz gewandert. Die Theory of Constraints nennt das einen Policy Constraint. Der lautet salopp formuliert: „Wir hauen den Code raus so schnell die Finger tippen!“

Dieser Grundsatz limitiert heute den Durchsatz der Softwareentwicklung. Die kommt nicht ins Stocken, weil sie unter Hardwaremängeln leidet oder softwaretechnisch irgendetwas nicht umsetzbar wäre. Nein, sie stockt, weil ihr Grundsatz einer überholten Ökonomie angehört.

Der Preis dafür, Nachfrage nicht zu bedienen, ist nämlich gesunken. Oder genauer: Der Preis dafür, Nachfrage heute nicht zu bedienen, ist gesunken. Er ist gesunken gegenüber dem Preis für die Auslieferung fehlerhafter Software. Vor allem wird er aber immer kleiner im Vergleich zu dem, was es kostet, morgen Nachfrage nicht mehr bedienen zu können.

Modern ausgedrückt ist der langjährige Grundsatz nicht nachhaltig.

Das war früher kein Problem. Genauso wie es früher kein Problem war, wenn eine Handvoll Menschen in einem großen Wald Bäume für den Hüttenbau und das Herdfeuer abholzte.

Für große, hungrige Zivilisationen funktioniert derselbe Umgang mit dem Wald jedoch nicht.

Wenn bei naiver Nutzung der Ressourcenbedarf über der „Ressourcenauffrischung“ liegt, dann ist das kein nachhaltiger Umgang mit Ressourcen. Dann kann man heute daraus Nutzen ziehen – aber morgen nicht mehr.

So ist das auch bei „naiver“ Softwareentwicklung. Die funktioniert solange auf grüner Wiese codiert wird. Das war viele Jahrzehnte der Fall. Der Markt war grün, die Software war grün. Es gab keine Altlasten. Und der Hunger nach Funktionalität war so groß, dass alles andere nur wenig zählte. Da konnte man überall seine „braunen Flecken“ hinterlassen. (Von drastischerem Vokabular sehe ich hier einmal ab.)

Doch das hat sich in den letzten 10-15 Jahren verändert. Die Kunden werden anspruchsvoller, sie tolerieren immer weniger Bugs. Vor allem aber muss heute Software schneller und enger am Kunden entwickelt werden. Die Budgets sind gesunken. Und Software ist umfangreicher denn je, muss also länger leben. Auch das ist eine Budgetfrage.

Die grüne Wiese ist bis zum Horizont ein braunes Feld geworden.

Deshalb ist nachhaltige Softwareentwicklung das Gebot der Stunde. Deshalb ist der bisherige Grundsatz der neue Engpass als Policy Constraint.

Daraus folgt für mich aber nicht nur, dass mehr Qualitätssicherung und automatisierte Tests und agile Vorgehensmodelle sein müssen. Es folgt auch, dass Softwareentwurf wieder einen festen Platz in der Softwareentwicklung bekommen muss. Das scheint mir eine ökonomische Notwendigkeit im Rahmen der historischen Bewegungen unserer Disziplin. Denn ausdrücklicher Softwareentwurf macht sich Gedanken über das Big Picture und das Morgen. Wer ständig über dem Code hängt und dort lokale Optimierung betreibt, der fliegt am Ziel Nachhaltigkeit vorbei.

Auszug aus dem Buch “Flow-Design – Pragmatisch agiler Softwareentwurf”, an dem ich nun endlich doch arbeite. Jeden Tag 4 Stunden schreiben… Puh… Demnächst mehr dazu hier im Blog.

PS: Gerade stolpere ich über eine Aussage von Dijkstra:

“[The major cause of the software crisis is] that the machines have become several orders of magnitude more powerful! To put it quite bluntly: as long as there were no machines, programming was no problem at all; when we had a few weak computers, programming became a mild problem, and now we have gigantic computers, programming has become an equally gigantic problem.”

Das entspricht ganz meinem Gefühl – auch wenn das, was er 1972 mit “gigantic computers” gemeint hat, heute lächerlich wirkt.

Mittwoch, 13. März 2013

Wider den sprachlichen Plattform Lock-In

One platform to rule them all – so könnte wohl das Motto vieler Softwareunternehmen/-abteilungen lauten. Man ist ein Java-Shop oder eine .NET-Bude oder eine Ruby-Schmiede usw. Hier und da machen einige Entwickler vielleicht einen Ausflug in andere Gefilde, doch das Hauptprodukt wird auf einer Entwicklungsplattform hergestellt.

Das ist kein Zufall. Das ist gewollt. Das will das Management so, weil Vereinheitlichung eine gute Sache ist. Dadurch kann man immer Geld sparen, oder? Und das wollen die Entwickler auch so, weil das weniger verwirrend ist. Es würde sich doch keiner mehr auskennen, wenn ein bisschen Code in Java, ein bisschen in C#, ein bisschen in Ruby und noch ein bisschen in JavaScript oder Clojure oder C++ vorläge. Oder?

Da ist natürlich etwas dran. Eine gewisse Vereinheitlichung, ein Fokus ist ökonomisch. Aber nur bis zu einem gewissen Grad. Darüber hinaus kippt Vereinheitlichung, d.h. Homogenität um in Monokultur mit all ihren Nachteilen.

Aus Plattform-Fokus wird dann Plattform Lock-In. Man kann dann nicht mehr anders. Die Plattform ist dann eine systemrelevante Größe. An der ist nicht mehr zu rütteln.

Das hat negative Auswirkungen auf die Evolution der Software. Die kann dann nämlich nicht mehr mit der Zeit gehen. Entwicklungen auf anderen Plattformen oder gar Entwicklungen ganz neuer Plattformen ziehen an ihr vorbei. Und oft genug sind es sogar Entwicklungen der Plattform selbst, die man nicht nutzen kann. Gerade neulich war ich wieder bei einem Team, das sich noch an .NET 2.0 bindet. Erweiterungsmethoden, Lambda-Ausdrücke, yield return, Linq, dynamische Typen, aync/await und mehr sind dort kein Thema.

Zu den negativen Auswirkungen auf die Software kommen negative Auswirkungen auf das Team. Teams verkalken, wenn sie Jahre lang auf eine Plattform starren. Sie werden immer mehr zu einer Echokammer, durch die dieselben alten Glaubenssätze und derselbe alte Code hallen. Sie werden damit immer unattraktiver für andere Entwickler.

Bei allem Verständnis für den Vereinheitlichungsreflex halte ich diese Nachteile für so offensichtlich, dass ich mich frage, warum der Plattform-Fokus landauf, landab so groß ist. Mir scheint es eine Lücke in der Argumentation dafür zu geben.

Die meine ich nun gefunden zu haben. Ich glaube, ich kenne jetzt das fehlende Argument der Entwickler für die eine Plattform. Und dieses Argumente ist keines, das sich auf einen Vorteil der einen oder anderen Plattform bezieht. Es geht vielmehr um die Kompensation eines Mangels.

Teams mangelt es an einer anderen Sprache für die Beschreibung von Software außerhalb ihrer Programmiersprache.

Oder anders herum: Ich glaube, Teams fühlen sich zu der einen Plattform hingezogen, weil deren Programmiersprache für sie das einzig verlässliche Kommunikationsmittel über Software darstellt.

Es geht weniger um technische Vorteile einer bestimmten Plattform. Es geht auch weniger um technische Vorteile durch Konzentration auf nur eine Plattform. Es geht auch nicht um personelle Vorteile durch eine Vereinheitlichung [1].

Nein, die eine Plattform dient auch als die eine Sprache. Denn wie ein Blick auf aktuelle Konferenzprogramme zeigt, gibt es keine Sprache, um über Code zu reden. Es gibt nur Code und Technologien/Frameworks. Auf der Meta-Ebene findet im Grunde nichts mehr statt.

Früher gab es die ehrwürdige Disziplin des Softwareentwurfs. Die hatte den Anspruch, eine Lösung sprach-/plattformunabhängig zu beschreiben. Zugegeben, das ist manchmal ein bisschen weit getrieben worden. Doch der Grundgedanke war richtig, finde ich. Code war “nur” ein notwendiges und nicht triviales Implementationsdetail.

Diese Disziplin hat eigene Hochsprachen entwickelt. EBNF, RegEx, Zustandsdiagramm, Struktogramm, UML-Diagramme, sogar noch Blockdiagramme mit dem Schichtendiagramm als Sonderfall gehören dazu.

Doch das ist heute alles hohles Geschwätz. Wer nicht codiert, verschwendet Zeit an Flüchtiges. Diagramme haben keinen Bestand, sie veralten in dem Moment, da man sie implementiert. Also gar nicht erst damit anfangen. Jedenfalls nicht offiziell.

Ich kenne kein Tutorial, das eine Vorgehensweise zur Entwicklung von Software von Anforderungen bis zum Code beschreibt, in dem nicht schnellstmöglich zur Tastatur gegriffen wird. Code ist Trumpf! Code ist die einzige Wahrheit! Das wird mantrahaft wiederholt. Und alles andere wird entweder gar nicht thematisiert oder rein textuell abgehandelt. Und so wird aus einem Text ein anderer. Keine Abstraktion, keine Visualisierung. Diagramme sind selten und unsystematisch [2].

Code und Infrastruktur: das ist der Inhalt des Entwicklerlebens.

Und weil das so ist, müssen Code und Infrastruktur vereinheitlicht sein. Denn sie stellen die einzige Sprache dar, auf die Verlass ist.

Was aber nun, wenn Entwickler sich üben würden, ihre Software auch anders ausdrücken zu können. UML hat dazu einen Vorschlag gemacht – leider einen zu schwerfälligen. Vielleicht geht es ja aber auch einfacher. Ich denke, die Suche nach einer leichtgewichtigen Weise zur Beschreibung von Software, ist lohnend [3]. Dabei geht es ja nicht um einen Ersatz für Programmiersprachen. Dann wäre nichts gewonnen. Leichtgewichtigkeit bedeutet Entlastung von den ganzen Details, die sich beim und durch Codieren ergeben. So wie das Codieren in heutigen Hochsprachen auch eine Entlastung ist von ekeligen Details, wie sie bei der Assembler- und Maschinencodeprogrammierung wichtig sind. Speichermanagement, Registerallokation, Kontrollstrukturenbau und vieles mehr liegt hinter uns.

Der Gewinn einer plattformunabhängigen, vom Code abstrahierenden Sprache läge für mich nicht nur in verbesserter Möglichkeit, allein oder im Team über Codestrukturen nachzudenken. Softwareentwurf könnte damit also besser skalieren. Er würde von einer persönlichen Angelegenheit, die beim TDD-Codieren passiert, zu einer Angelegenheit des Teams. Die Collective Software Ownership würde steigen.

Der Gewinn würde außerdem in einer Befreiung aus der Umklammerung einer Plattform bestehen. Endlich könnten Entwickler verschiedener Plattformen an einem Tisch sitzen und gemeinsam über Softwarestrukturen nachdenken. Die würden sie unterschiedlich implementieren, je nach Plattform. Aber sie hätten zunächst einmal eine gemeinsame Vorstellung vom bigger picture.

Eine leichtgewichtige Sprache, um über und (weitgehend) unabhängig von Code zu “reden”, würde die Softwareherstellung also aus dem Plattform Lock-In befreien. Endlich könnte Software leichter aus Code auf verschiedenen Plattformen zusammengesetzt werden. Und das würde die Evolvierbarkeit von Codebasis und Team beflügeln.

Expliziter Entwurf in einer Entwurfssprache ist kein Selbstzweck. Mir scheint er vielmehr eine Bedingung für die Möglichkeit nachhaltiger Softwareentwicklung.

Medizin, Soziologie, Psychologie, Land- und Forstwirtschaft und Politik haben erkannt, dass Monokulturen mittelfristig schädlich sind. Überall da, wo es um Leben, um Entwicklung geht, fördert Vielfalt dieses Ziel. Nur bei der Software-Entwicklung glaubt man noch Gegenteiliges. Da setzt man noch auf Homogenisierung, Monokultur, Vereinheitlichung, Konsolidierung.

Damit sollten wir aufhören. Dafür ist aber eine praktikable Entwurfssprache und –methode nötig. Daran sollten wir arbeiten. Dazu brauchen wir mehr Ideen [4].

Endnoten

[1] Eine Plattformvielfalt kann personell nur von Vorteil sein. Nicht nur würde sie Diversität im Denken fördern, was inzwischen allgemein anerkannt einen immer größerer Überlebensvorteil für Unternehmen darstellt. Noch konkreter bedeutet Plattformvielfalt eine viel größere Auswahl an Entwicklern.

Wer nur Java oder C# macht, der kann nur aus der Java- oder C#-Community rekrutieren. Wer aber Java und C# und Python und JavaScript und Ruby macht, der kann in all diesen Communities nach neuen Teammitgliedern suchen.

Wo Plattformvielfalt Programm ist, gibt es keine zähneknirschende Einstellung plattformfremder Entwickler mehr, die dann mühsam auf die eigene Plattform umgehoben werden müssen. Dann kann jeder mit seiner Plattform zum Gelingen der Software viel schneller beitragen. Das spart bares Geld!

[2] Hier uns da tauchen mal UML-Diagramme auf: ein Klassendiagramm, ein Sequenzdiagramm. Auch Blockdiagramme gibt es hier und da. Doch auch wenn deren Syntax definiert sein mag, ihre Nutzung ist unsystematisch. Sie ist punktuell, steht in keinem Zusammenhang, ist nicht Teil einer Methode oder übergreifenden Sprache oder Kultur.

Diagramme werden nicht als gleichberechtigtes Mittel neben Code angesehen. Sie sind eher Notlösungen, wenn man grad nicht anders kann.

[3] Natürlich wäre es auch gut, wenn Entwickler sich mit mehr Programmiersprachen beschäftigen würden. Dann wären andere Plattformen ihnen nicht so fremd; sie wären offener, für manche Funktionalität auch mal etwas anderes auszuprobieren.

Dass der wahrhaft polyglotte Programmierer jedoch bald Realität würde, sehe ich nicht. Genauso wenig wie Menschen, die mehr als zwei Fremdsprachen sprechen, selten sind. Bei den natürlichen Sprachen hat sich deshalb eine Universalsprache zur kulturübergreifenden Verständigung durchgesetzt: Englisch.

Allerdings ist das kein Englisch, das alle Feinheiten der englischen Sprache enthält, sondern eine Verflachung. Allemal das gebrochene gesprochene Englisch. Doch das reicht für die meisten Situationen, in denen viele Details, Feinheiten, Nuancen ausgelassen werden können.

Warum nicht genauso in der Softwareentwicklung? Womit natürlich keine verflachte 3GL gemeint ist, sondern eine echte Abstraktion.

[4] Wie gesagt, die UML hat einmal versucht, so etwas zu sein. Doch sie ist gescheitert. Ich würde sagen, an Hybris. Sie hat sich nicht beschränken wollen in Breite und Tiefe. Oder es waren zu viele Köche an diesem Brei beteiligt?

Das bedeutet nicht, dass die UML nichts verwertbares enthielte. Ich sehe aber nicht UML 3.0 als Lösung, sondern eher etwas anderes, das hier und da aus der UML etwas entlehnt, ansonsten aber eigenständig ist.

Mittwoch, 6. März 2013

Software als Web of Services

Für mehr Evolvierbarkeit von Software wie Team müssen wir Software grundsätzlich anders aufgebaut denken, glaube ich. Derzeit stehen Objekte im Vordergrund. Das halte ich für eine überholte Sichtweise. Sie entstammt einer Zeit, da Software weniger umfangreich war und nur mit knappen Ressourcen betrieben wurde.

Historisch hat alles mit einem bunten Gemisch von Anweisungen und Daten innerhalb eines Betriebssystemprozesses angefangen. Der Prozess spannt die Laufzeitumgebung für den Code auf.

image

Unterprogramme entstanden erst, als Programme umfangreicher wurden. Sie sind ein erster Schritt zur Kapselung von Code. Die Zahl der Anweisungen konnte durch sie weiter steigen, ohne dass die Übersichtlichkeit drastisch gesunken wäre.

image

Als nächstes entwickelte man Container für Daten. Sie sollten zu größeren Einheiten zusammengefasst werden. Die Sprache C spiegelt diesen Entwicklungsstand wider: sie bietet Unterprogramme (Prozeduren und Funktionen) sowie Strukturen.

image

Anschließend gab es einen letzte Schritt: die Objektorientierung. Sie hat Strukturen und Unterprogramme zu Klassen zusammengefasst. Dadurch wurde Software nochmal etwas grobgranularer, so dass sich mehr Anweisungen innerhalb eines Prozesses übersichtlich handhaben ließen.

image

Parallel gab es eine Entwicklung von Bibliotheken. Zuerst wurden in ihnen Unterprogramme und Strukturen zusammengefasst, später Klassen.

imageimage

Doch auch wenn Bibliotheken schon lange existieren, sind sie aus meiner Sicht nie zu einem gleichwertigen Container neben Klassen aufgestiegen. Sie werden nicht konsequent zur Strukturierung und Entkopplung im Sinne der Evolvierbarkeit eingesetzt. Ihr Hauptzweck ist es, Wiederverwendung zu ermöglichen. Sie sind die Auslieferungseinheiten für Code, der in unterschiedlichen Zusammenhängen genutzt werden können soll.

Die hauptsächliche Strukturierung von Software befindet sich mithin auf dem Niveau der 1990er, als die Objektorientierung mit C++, Delphi und dann Java ihren Siegeszug angetreten hat.

Die Bausteine, aus denen Software zusammengesetzt wird, sind unverändert feingranular; es sind die Klassen. Der Umfang von Software ist seitdem jedoch kontinuierlich gestiegen. Mehr Hauptspeicher macht es möglich. Früher waren es 640 KB oder vielleicht wenige MB; heute sind es 4 GB und mehr.

Nur eine weitere Veränderung hat sich noch ergeben. Die Zahl der Prozesse pro Anwendung ist gewachsen, weil die Zahl der beteiligten Maschinen gewachsen ist. Das Ideal ist zwar immer noch, so wenige Prozesse wie möglich laufen zu haben, doch Mehrbenutzeranwendungen erfordern für Performance und Skalierbarkeit oft mindestens zwei Prozesse, einen für den Client und einen für den Server.

image

Im Vergleich zur Zahl der in einer Anwendung involvierten Bibliotheken und vor allem der Klassen, ist die Zahl der Prozesse jedoch verschwindend klein. Prozesse sind heute keine Mittel zur Strukturierung von Software im Sinne der Evolvierbarkeit. Sie werden als notwendige Übel betrachtet, um andere nicht-funktionale Anforderungen zu erfüllen.

Komponenten für industrielle Softwareentwicklung

Bibliotheken sind Konglomerate von Klassen. Die gehören inhaltlich zwar zusammen, nur ist ihre Leistung nicht sehr klar definiert. Ihr Angebot, der Kontrakt, ist implizit.

Das ändert sich erst mit Komponenten. Komponenten sind Bibliotheken mit explizitem und separatem Kontrakt.

Mit Komponenten ist es möglich, Software industriell zu entwickeln. An allen Komponenten kann gleichzeitig gearbeitet werden, auch wenn sie gegenseitig Leistungen nutzen. Die Abhängigkeit zur Entwicklungszeit bezieht sich ja nur auf den Kontrakt.

image

Idealerweise würde Software also mit Komponenten statt Bibliotheken strukturiert. Erstens ist die Kopplung zwischen Komponenten durch den expliziten Kontrakt geringer als zwischen Bibliotheken. Implementationen lassen sich dadurch leichter austauschen [1]. Zweitens steigt durch Komponenten die Produktionseffizienz.

image

Komponenten verändern gegenüber Bibliotheken allerdings nicht die Granularität von Software. Sie ziehen eher kein neues Abstraktionsniveau ein, wie es Bibliotheken in Bezug auf Klassen und Klassen in Bezug auf Methoden getan haben.

Außerdem verlangen Komponenten wie Bibliotheken und Klassen für eine Zusammenarbeit Übereinstimmung in der Plattform. .NET-Komponenten können .NET-Komponenten nutzen und Java-Komponenten können Java-Komponenten nutzen. Aus meiner Sicht gibt es keine breit praktikable Lösung, um universell plattformübergreifend Komponenten zu koppeln.

Das soll den Wert von Komponenten nicht schmälern. Sie sind sehr hilfreich und weit unterschätzt. Dennoch sollten wir auch noch einen Schritt weiter denken.

Services für Flexibilität

Der Betriebssystemprozess ist heute immer noch die Funktionseinheit, mit der wir am natürlichsten und einfachsten Code entkoppeln. Er spannt einen eigenen Adress- und Sicherheitsraum auf. Und er läuft autonom auf mindestens einem eigenen Thread.

Darüber hinaus ist der Prozess aber auch die Funktionseinheit, die wir mit einer Entwicklungsplattform assoziieren. Ob der Code eines Prozesses mit C, C++, C# oder Ruby oder Python oder Perl oder in Assembler geschrieben wurde, sehen wir dem Prozess nicht an. Von Prozess zu Prozess kann die Plattform wechseln.

Damit wird der Prozess für mich interessant im Sinne der Evolvierbarkeit. Denn die ist begrenzt, solange die Plattform eine systemrelevante Größe ist. Wer sein ganzes Team auf unabsehbare Zeit an eine Entwicklungsplattform bindet, wer den Plattformwechsel also nicht vom ersten Tag an als (wünschenswerte) Möglichkeit im Auge behält, der begibt sich auf einen unglücklichen Pfad von Co-Evolution von Software und Team: beide erstarren im Verlauf von 4-8 Jahren. Nicht nur wird es immer schwieriger, neue Funktionalität in die Codebasis einzupflegen; es wird auch immer schwieriger, echte Innovationen denken zu können und neue Leute ins Team zu holen.

Am Ende wacht dann ein Team nach Jahren auf und findet sich abgehängt. Die Kunst hat sich weiterentwickelt – nur kann man davon nicht profitieren. Weder hat sich das Team kontinuierlich Kenntnisse angeeignet, noch konnten Kenntnisse in die Codebasis einfließen.

Ohne Komponenten gibt es kaum Codeeinheiten, an denen etwas hinter einer Barriere einfach mal ausprobiert werden könnte, wenn die Plattform neue Features hergibt.

Und selbst mit Komponenten gibt es keine Codeeinheiten, an denen eine vielversprechende Plattform ausprobiert werden könnte. Oder ein vielversprechender Bewerber, dessen Plattformkenntnisse leider nicht so tief sind – der aber Experte auf einer anderen Plattform ist.

Der Code ganzer Prozesse ist heute gewöhnlich zu groß, um solche Veränderung zu erlauben. Auf der anderen Seite sind Prozesse die Codeeinheiten, mit denen solche Veränderungen auf Plattformebene einzig möglich sind.

Deshalb plädiere ich dafür, Software aus mehr, viel mehr Prozessen zusammenzusetzen als heute üblich.

Der Zweck dieser Prozesse ist aber ein anderer als der heutige. Heute dienen sie vor allem kundenrelevanten nicht-funktionalen Anforderungen wie Skalierbarkeit oder Sicherheit. Ich möchte sie nun ja aber zu einem Mittel der Flexibilität, der Evolvierbarkeit von Code und Team erheben. Deshalb nenne ich die Prozesse, um die es mir geht, Services.

Ein Service ist für mich ein Prozess mit einem expliziten und separaten und vor allem plattformneutralen Kontrakt.

image

Ein Service ist mithin eine Kombination aus Komponente und Betriebssystemprozess. Er definiert ganz genau und unabhängig von jeder Implementation, was er leistet. Und er ist autonom, d.h. ausgestattet mit eigenem Adressraum und asynchron gegenüber anderen Services.

Das macht einen Service zu einer vergleichsweise schwergewichtigen Funktionseinheit. Aber ich behaupte, das macht nichts. Es lassen sich in jedem Prozess jeder Anwendung Aspekte finden, die in Services verpackt mit einander arbeiten können, ohne dass dadurch ein Performanceproblem entstünde.

Mein Bild von Software sieht damit so aus:

image

Die Services innerhalb eines Prozesses können in einer Hierarchie zu einander stehen. Eher sehe ist sie aber auf derselben Ebene, verbunden in einem Netzwerk: a web of services.

image

Jenachdem, wie der plattformneutrale Kontrakt eines Services aussieht, kann er auch einfach von einem Prozess in einen anderen bewegt werden. Am Ende sind die umschließenden Prozesse nur Hosts für Services, leisten aber nichts selbst für die Domäne. Sie werden quasi unsichtbar.

Software besteht damit aus Services. Und Services können zusammenarbeiten, auch wenn sie auf verschiedenen Entwicklungsplattformen implementiert sind. Innerhalb eines Host-Prozesses können Services geschrieben in C#, Java, Ruby und Python kooperieren.

image

Solche Vielfalt muss nicht sein. Aber sie kann sein! Es ist möglich, eine Software, deren Services zunächst mit nur einer Plattform implementiert sind, schrittweise zu überführen. Service für Services wird einfach neu geschrieben. Das ist bezahlbar, solange ein Service keine systemrelevante Größe hat. Wenn man es sich nicht leisten kann, einen Service (oder zunächst einmal eine Komponente) neu zu schreiben, falls es schwierig wird mit Veränderungen und Refactoring… dann hat man sich in die Ecke gepinselt. Dann ist die Software wahrlich zum Monolithen erstarrt.

Sie mögen nun einwänden, die Kommunikation zwischen Services sei doch viel zu langsam. Aber wissen Sie das? Haben Sie es ausprobiert, ob Sie höhere Geschwindigkeit brauchen? Ein Roundtrip mit TCP braucht ca. 0,125msec, einer über die I/O-Streams 0,1msec, einer über einen embedded Web-Server wie Nancy im schlechtesten Fall 0,55msec – und ein direkter Aufruf nur 0,00065msec, d.h. er ist rund 500 Mal schneller.

Ja, so ist das. Aber diese im Vergleich schlechteren Werte für cross-service Kommunikation im Vergleich zu cross-component Kommunikation sagt nichts darüber aus, ob cross-service für Ihren konkreten Fall wirklich zu langsam wäre. Haben Sie das überhaupt schon einmal in Erwägung gezogen und ausprobiert? Wahrscheinlich nicht.

Genau dazu möchte ich Sie aber ermuntern. Ich möchte Sie einladen, ein web of services auszuprobieren. Experimentieren Sie damit – denn es gibt etwas zu gewinnen. Sie gewinnen all die Vorteile, die das Internet für seine Evolution und die der an ihm beteiligten Services schon nutzt.

Falls Sie glauben, ich sei der einzige mit solch einer spinnerten Idee, dann schauen Sie einmal bei infoq.com vorbei: Der Vortrag “Micro Services: Java, the Unix Way” bricht ebenfalls eine Lanze für diese Denkweise.

Ich bin überzeugt, wir müssen diesen evolutionären Schritt gehen. Wir müssen ein nächstes Abstraktionsniveau für Funktionseinheiten erschließen. SOA ist mir da schon zuviel. Aber Actors sind mir zuwenig.

Ich glaube mit “einfacheren” Services, die keinen weiteren Anspruch haben als einen separaten plattformneutralen Kontrakt für Funktionalität in einem eigenen Prozess, kann das funktionieren. Technisch ist das kein Hexenwerk. Jeder kann ein web of services aufziehen. REST sehe ich in dieser Hinsicht nicht als hübschen Ansatz für Web-Anwendungen, sondern als grundsätzliches Paradigma der Servicekopplung auch im Kleinen. Für REST braucht es keinen Web-Server; Sie können REST auch mit Service implementieren, der Requests per Standard-Input entgegen nimmt und Responses auf dem Standard-Output liefert.

Es ist vielmehr eine Frage des Willens, so zu denken. Es ist eine Frage Ihres Wertesystems: Was schätzen Sie höher ein? Garantiert bessere Evolvierbarkeit durch Plattformflexibilität – oder vermeintlich benötigte höhere Kommunikationsgeschwindigkeit?

Das ist für mich der Weg für die effizientere Herstellung von evolvierbarer Software: Funktionalität in Komponenten kapseln für bessere Entkopplung innerhalb einer Plattform; Funktionalität in Services kapseln für bessere Entkopplung in plattformneutraler Weise.

Und wenn Sie dadurch nebenbei auch noch Ihre Prozessorkerne besser ausnutzen und die Verteilbarkeit von Code steigt und Sie auf einen größeren Pool von Entwicklern zurückgreifen können bei Neueinstellungen… dann ist das auch nicht schlecht, oder?

Endnoten

[1] Es gibt mindestens zwei Gründe, Funktionseinheiten durch einen expliziten Kontrakt leicht komplett austauschbar zu halten.

Der erste ist leichtere Testbarkeit. Auch wenn es nur eine Implementation des Kontraktes für Produktionszwecke je geben wird, kann es für Tests sehr hilfreich sein, ganz einfach eine Attrappe hinter dem Kontrakt anfertigen zu können.

Der zweite Grund ist die Möglichkeit zur Neuentwicklung. Hinter einem Kontrakt kann die gesamte Implementation ausgetauscht werden, ohne dass Kontraktnutzer davon tangiert würden.

Beispiel: Wenn eine Komponente den Datenbankzugriff kapselt, dann wollen Sie in Tests von abhängigen Komponenten, nicht immer auch tatsächlich auf die Datenbank zugreifen. Also entwickeln sie z.B. eine Attrappe, die Datenbankzugriffe durch Zugriffe auf das Dateisystem simuliert.

Und auch wenn Sie nie beabsichtigen, ein anderes Datenbanksystem als MySQL zu benutzen, kann es Ihnen helfen, die gesamte Implementation der Datenbankzugriffskomponente einfach wegzuschmeißen und neu zu schreiben. Denn Refactoring kann sich irgendwann als zu aufwändig herausstellen.

Ganz zu schweigen davon, dass es mit einer Komponente für den Datenbankzugriff einfach ist, von MySQL umzusteigen auf Oracle oder SQL Server alternativ zu MySQL anzubieten. Doch diese Art der Austauschbarkeit sowie die Möglichkeit zur Wiederverwendung sind für mich eher die geringeren Gründe, mit Komponenten zu arbeiten.