Montag, 29. Juni 2009

Wie gut ist Ihr Job? [OOP 2009]

Mögen Sie eigentlich Ihren Job? Sind Sie zufrieden? Nicht nur mit dem Gehalt, nein, so insgesamt. Immerhin verbringen Sie “auf der Arbeit” ca. 1/3 Ihrer Zeit oder gar knapp 50% Ihrer “Wachphasen”. Da wäre es doch gut, diese Zeit zufrieden zu verbringen, oder?

Also: Wie geht es Ihnen mit Ihrem Job?

Wenn es Ihnen schwer fallen sollte, darüber nachzudenken, dann grämen Sie sich nicht. Das ist ganz natürlich. Ein erstes Gefühl haben Sie sicherlich schnell. Aber im Einzelnen werden Sie unsicher sein. Sie haben es nicht imagegelernt, ihre Arbeit bewusst und detailliert zu reflektieren. Ihr Arbeitgeber ist daran kaum interessiert. Und in Ausbildung oder Schule steht das einfach nicht auf dem Lehrplan.

Zum Glück gibt es aber andere, die sich darüber Gedanken machen. Der DGB zum Beispiel. Deshalb hat der eine Studie zur Arbeitszufriedenheit in Auftrag gegeben. Darüber berichtet gerade die Zeitschrift Psychologie heute (7/09) in ihrem Artikel “Kann man sich in seinen Job (neu) verlieben?”.

15 Kriterien werden darin genannt, anhand derer Sie überprüfen können, ob Sie einen guten oder schlechten Job haben:

    1. Gibt es Qualifizierungsangebote? Lerne ich etwas bei der Arbeit?
    2. Habe ich die Möglichkeit, eigene Ideen einzubringen?
    3. Kann ich im Betrieb aufsteigen?
    4. Habe ich Einfluss auf Planung und Menge meiner Arbeit?
    5. Erhalte ich klare Anforderungen und alle notwendigen Informationen?
    6. Wie gut führen meine Vorgesetzten?
    7. Wie gut ist die Betriebskultur?
    8. Bekomme ich Hilfe von meinen Kollegen?
    9. Ist meine Arbeit nützlich für die Gesellschaft?
    10. Gibt es eine faire und verlässliche Arbeitszeitgestaltung?
    11. Muss ich oft unter Zeitdruck arbeiten?
    12. Werde ich herablassend behandelt? Muss ich meine Gefühle verbergen?
    13. Ist meine Arbeit körperlich schwer oder einseitig?
    14. Habe ich häufig Angst um meine berufliche Zukunft?
    15. Kann ich von meinem Einkommen leben? Entspricht das Einkommen meiner Leistung?

Über diese Kriterien mag man im Einzelnen diskutieren. Fehlt da z.B. die Frage nach der Erfüllung durch Spaß am Metier? Oder entsteht Sinnhaftigkeit vor allem durch gesellschaftliche Relevanz oder nicht vielmehr durch ein Gefühl des “gewollt und gebraucht seins” vor Ort im Unternehmen? Als Ausgangspunkt und vor allem Vergleichsmaßstab sind die Kriterien jedoch nicht schlecht.

Wie steht es also mit Ihnen? Wenn Sie Ihre Arbeit daran messen, wie gut oder schlecht ist sie? Das können Sie leicht herausfinden, indem Sie einen Fragebogen kostenlos online ausfüllen. Das dauert 5 Minuten und sieht im Ergebnis dann z.B. so aus:

image

Hier habe ich mal versucht, mich in die Situation eines “abhängig beschäftigten” Softwareentwicklers zu versetzen. Auf der linken Seite der Zufriedenheitsindex dieses fiktiven Entwicklers, der seinen Job als “knapp vorbei an schlecht” einschätzt mit einem Index von 54. Rechts der Durchschnitt der Befragungen aus der Studien über viele Berufsgruppen hinweg.

Auffällig: Bei der Studie liegen die Werte dichter beieinander. Ich halte also den fiktiven Softwareentwickler in einigen Bereichen für sehr zufrieden (z.B. bei Kollegialität oder auch der Möglichkeit, Kontrolle über seine Arbeit auszuüben), andererseits ist er aber auch sehr unzufrieden (z.B. bei der Weiterbildung, der Arbeitszeit/-intensität oder auch den Aufstiegschancen).

Das sind natürlich nur Annahmen aufgrund meiner Begegnungen mit Softwareentwicklern in vielen Betrieben durch meine Berater- und Trainerarbeit. Sozusagen ein “gefühlter Durchschnitt”.

Aber liege ich damit so falsch? Sagen Sie es mir. Füllen Sie auch den Fragebogen aus und schauen Sie, wo Ihre Zufriedenheit positiv – was zu wünsche wäre – oder negativ abweicht. Motivieren Sie Ihre Kollegen es auch zu tun. Sprechen Sie über Ihre Ergebnisse – auch mit Ihrem Chef. Und: Wenn Sie mögen, schicken Sie mir Ihre Auswertungen als Bilder per Email (inkl. einiger Zusatzangaben: Sind Sie selbstständig/angestellt? Wie groß ist das Team, in dem Sie arbeiten? Welche Position haben Sie? Wie groß ist die relevante Organisationseinheit um Sie herum (Unternehmen, Abteilung)?). Ich verspreche, in jedem Fall Ihre Angaben vertraulich zu behandeln, wenn ich aus den eingegangenen Auswertungen eine kleine Galerie zusammenstelle oder – bei genügend Masse – einen Branchendurchschnitt berechne.

Ich bin gespannt, wie Sie sich mit Ihrem Job fühlen!

 

PS: Da es etwas Überwindung kosten mag, mir das Ergebnis zu schicken und sich damit ein Stück zu öffnen, hier meines sozusagen als “Vorleistung”:

image

Sie sehen, ich fühle mich zufriedener als der fiktive Entwickler. Warum? Weil ich sehr selbstbestimmt arbeiten kann. Zwar knickt die Kurve bei der Arbeitsplatzsicherheit ein, da ich ohne größere Organisation im Rücken wenig Puffer habe, aber ansonsten empfinde ich quasi alles “im grünen Bereich”.

Den Einbruch bei “Sinngehalt” zählt für mich nicht wirklich. Meinen Sinngehalt ziehe ich nicht aus einer größeren gesellschaftlichen Relevanz meiner Arbeit. Das gute Feedback aus der Community und auch der Zuspruch meiner Familie wiegen für mich schwerer.

Manche Fragen sind für mich als Freiberufler natürlich auch nicht passend. Den Führungsstil eines Vorgesetzten kann ich nicht bewerten, weil ich keinen habe, musste aber einen Wert im Fragebogen eintragen.

Insofern ist mein Ergebnis wie auch Ihres immer mit einem Körnchen Salz zu schmecken. Aber das finde ich nicht schlimm. Es geht nicht um einzelne Werte, sondern die Tendenz.

Und nun kommen Sie: hier gehts zum Fragebogen.

Montag, 22. Juni 2009

Wartung und Evolvierbarkeit [OOP 2009]

Bei meinen Gedanken über die merkwürdige Plötzlichkeit der Unwartbarkeit habe ich eigentlich meinem Credo zuwidergehandelt. Ich habe von Unwartbarkeit geschrieben, obwohl ich immer wieder sage, es gäbe keine Wartung bei Software.

Es gibt keine Softwarewartung

Dabei bleibe ich auch. Es gibt keine Wartung von Software im üblichen Sinn. Der bezieht sich nämlich auf die Weiterentwicklung nach den ersten Releases. Maschinen können gewartet werden. Software aber nicht, jedenfalls nicht in Bezug auf ihre Funktionalität. Sie enthält keine Teile, die kaputt gehen könnten und die man daher proaktiv austauscht, bevor sie kaputtgehen. Das tut Wartung bei Maschinen.

Wenn eine Maschine kaputt geht, dann wird sie repariert. Um das zu vermeiden, wartet man sie. Man erhält Funktionalität proaktiv. Software hingegen geht nicht kaputt, sondern ist höchstens kaputt - nur man bemerkt es erst später. Ein Bug tritt nicht durch Verschleiß wie bei einer Maschine auf. Reparaturen an Software (bug fixing) beziehen sich also immer auf eine bis dato nur unentdeckte "Kaputtheit". Im Umkehrschluss bedeutet das, Software hat keine Verschleißteile. Also ist Wartung nicht nur unnötig, sondern unsinnig. Funktionalität ist entweder fehlerfrei existent - oder nicht. Software ist insofern binär. Da gibt es keinen schleichenden Übergang, den man mit Wartung verhindern könnte.

Aufmotzen statt warten

Statt von Wartbarkeit spreche ich deshalb lieber von Evolvierbarkeit. Schon allein deshalb, weil in Wartung keine Innovation steckt, keine Weiterentwicklung, sondern nur Erhaltung eines IST-Zustandes.  Software muss gerade deshalb eben nicht wartbar sein, sondern vor allem eines: evolvierbar. Sie muss flexibel sein, sie muss einfach erweiterbar sein. Denn wenn eines gewiss ist bei der Softwareentwicklung, dann ist es der kaum versiegende Anforderungsstrom. Je erfolgreicher eine Software, desto breiter und länger dieser Strom. Das erste Release ist nur der Anfang eines hoffentlich langen Softwarelebens durch viele kleine und große Veränderungen hindurch.

Mit Wartung hat das dann nichts zu tun. Das ist eher Evolution im allgemeinen Sinn des Lateinischen evolvere ("ausrollen, entwickeln, ablaufen"): die Software entwickelt sich weiter, sie rollt und rollt immer weiter... und wird natürlich darin auch irgendwann langsamer. Ihre Lebenszeit durch viele Veränderungen hindurch läuft irgendwann ab. Aber bis dahin... Nein, da wird sie nicht gewartet, sondern aufgemotzt.

Aufmotzen oder Pimping, das sind für mich passende Begriffe für das, was wir mit Software machen. Wir packen immer mehr und mehr in eine Software rein. So, als würden wir ein Auto aufmotzen. Bei "Pimp My Car" sieht das Ergebnis am Ende auch ganz anders aus, als der Hersteller es bei Auslieferung mal gedacht hatte. Genauso ist es mit Software.

Die Frage ist daher, wie einfach der Hersteller es macht, sein Produkt später aufzumotzen. Autohersteller machen sich darüber keine Gedanken. Weil Autos aber keine so komplexen Maschinen sind, kann man sie trotzdem noch recht gut aufmotzen.

Bei Software ist das anders. Wenn wir das Aufmotzen da nicht voraussehen und einplanen, dann wird es schwer. Wie man an vielen Projekten sieht. Evolvierbarkeit bzw. Aufmotzbarkeit ist für mich daher das passende Wort für eine wesentliche Eigenschaft von Software. Und ich glaube, dass wir erst wirklich verlässlich evolvierbare Software herstellen werden, wenn wir den Unterschied zwischen Wartbarkeit und Evolvierbarkeit ganz bewusst wahrnehmen.

Und doch gibt es Softwarewartung

Soweit der Blick auf die Funktionalität von Software. In Bezug auf sie gibt es keine Wartung, kein proaktives Handeln zu ihrer Erhaltung. Wenn der Kunde eine neue Funktionalität möchte, dann wird sie reaktiv eingebaut. Voraussetzung dafür ist Evolvierbarkeit.

Die übliche Wartung sichert proaktiv Funktionalität zu. Allgemeiner formuliert ist Wartung jedoch eine Tätigkeit, die proaktiv irgendeine Qualität erhält. Funktionalität ist nur eine sehr naheliegende Qualität, die sich bei Maschinen mit Wartung erhalten lässt und daher Wartung für Software nahelegt.

Welche anderen Qualitäten hat Software denn aber noch? Vielleicht lässt sich ja der Wartungsbegriff für sie retten. Performance oder Skalierbarkeit oder Sicherheit oder Robustheit sehe ich auf einer Stufe mit Funktionalität. Entweder ist Software so performant oder robust, wie sie sein soll - oder sie ist es nicht. Da gibt es keinen schleichenden Übergang. Bei der Hard-/Software-Infrastruktur mag das anders sein. Dort kann man Teile proaktiv austauschen, um eine Qualität zu erhalten. Das fällt für mich aber unter Administration statt Softwareentwicklung.

Eine andere Qualität jedoch scheint mir durchaus ein Fall für die Wartung. Es ist die Evolvierbarkeit, mit der ich oben die Wartung vom Thron gestoßen habe. Denn eben diese Evolvierbarkeit ist nicht entweder vorhanden oder nicht, sondern verfällt über die Zeit. Sie stellt sich sozusagen durch ihren Zweck selbst ein Bein. Indem sie es leicht machen soll, Software zu verändern, sie wachsen zu lassen, zehrt sie sich selbst auf. Jede funktionale Veränderung von Software lässt ihre Evolvierbarkeit ein kleinwenig erodieren. Evolution verschleißt die Evolierbarkeit.

Wartung auf der Meta-Ebene

Damit ist Evolvierbarkeit selbst ein Kandidat für Wartung. Von der konkreten Funktionalitätsebene wandert Wartung sozusagen auf die Meta-Ebene. Software warten bedeutet also nicht wie bisher, Funktionalität zu erhalten und schon gar nicht, neue Funktionalität herzustellen, sondern die Voraussetzungen für neue Funktionalität zu erhalten.

Softwarewartung ist die Bedingung für die Möglichkeit von Veränderung. Bisher war Softwarewartung eben diese Veränderung selbst. Jetzt ist sie ihre Voraussetzung. Das ist ein Unterschied, finde ich. Ein erheblicher Unterschied.

Clean Code Developer wird damit zu einem Werkzeugkasten für die Softwarewartung. Das hatte ich bisher nicht so gesehen. Eine solche Assoziation fühlte sich falsch an. In Bezug auf den bisherigen, falschen Gebrauch des Begriffs Wartung war sie es auch. Mit der hiesigen neuen Definition jedoch wird die Assoziation korrekt.

So bin ich nun im Reinen mit dem Begriff Wartung. Ich muss nicht länger darauf bestehen, dass es keine Softwarewartung gibt. Sie hat vielmehr eine andere Aufgabe als bisher. Evolvierbarkeit ersetzt nicht Wartbarkeit. Wartbarkeit bezeichnet jetzt nur einen Zustand, in dem Evolvierbarkeit immer wieder leicht herzustellen ist. Wartbarkeit ist sozusagen Meta-Evolvierbarkeit ;-)

Evolution ist die Reaktion auf Veränderungen in der Umwelt. Wartung ist die proaktive Erhaltung von Evolvierbarkeit.

Warum überrascht Unwartbarkeit? [OOP 2009]

Warum sind Projekte immer wieder überrascht, dass sich ihr Code so schlecht warten lässt? Fehler zu beheben wird immer schwieriger. Neue Kundenwünsche einzubauen dauert immer länger. Mir scheint, viele fühlen sich damit so plötzlich konfrontiert wie mit Weihnachten.

image Am Anfang ist diese Situation noch in weiter Ferne; man hat von ihr gehört und will auch rechtzeitig darauf achten. Aber es ist ja noch so lang bis dahin. Dann vergeht die Zeit mit Tagesgeschäftalltagsstress... und - zack! - steht die Unwartbarkeit vor der Tür und man hat eben doch vergessen, sich frühzeitig darum zu kümmern.

Warum ist das so? Immer wieder. Es mangelt ja nicht an Kenntnis, dass es Unwartbarkeit gibt. Irgendwie liegt sie meistens jedoch in der Zukunft und betrifft ohnehin eher die anderen.

Bei Weihnachten rührt die gefühlte Plötzlichkeit des Auftretens sicherlich vom Stellenwert her. Weihnachten ist nicht so wichtig wie die Dinge des Tagesgeschäftes. Außerdem scheinen die Weihnachtseinkäufe so leicht zu erledigen, dass dafür immer noch irgendwie Zeit ist. Das ist ja auch durchaus wahr. Selbst am heiligen Morgen lassen sich noch Geschenke kaufen - und das wird genutzt, wie die vollen Geschäfte zeigen.

In Kauf genommen wird dabei jedoch, dass man dann damit zufrieden sein muss, was noch übrig ist. Die Qualität leidet also darunter, dass Geschenke auch noch bis kurz vor knapp gekauft werden können. Man ist eher bereit, sich mit dem, was übrig ist zufriedenzugeben, als rechtzeitig einzukaufen.

Aber das ist natürlich unkritisch im Verhältnis zu dem, was in Projekten passiert, wenn sie sich von der Unwartbarkeit überraschen lassen. Deshalb lohnt die Frage doppelt, woher denn dort diese Überraschung kommt?

Abgesehen einmal von einer allgemeinen Überschätzung oder gar Überheblichkeit, die das Problem entweder weit in der Zukunft, eher bei anderen oder als eigentlich nicht so schlimm einstuft, scheint mir hier die Natur der Sache der Kern des Problems. Das ist die Struktur von Software. Deren Komplexität macht nämlich die Wart- oder Unwartbarkeit aus.

image Mir scheint nun, dass die Sorglosigkeit um Umgang mit der Wartbarkeit das Ergebnis einer Fehleinschätzung des Wachstums eben dieser Komplexität ist. Ich kann mir die Überraschung über Unwartbarkeit nur vorstellen als Überraschung über ein schnelleres Wachstum als erwartet. Man weiß, dass die Komplexität über die Zeit zunimmt, man weiß, dass damit die Gefahr von Unwartbarkeit droht. Soweit sind sich alle einig. Aber wer im Projekt ist, glaubt daran, dass die Komplexität überschaubar wächst. Dass sich die Unwartbarkeit langsam ankündigt und dann immer noch gegengesteuert werden kann. Und genau das sieht mir wie ein großes Missverständnis aus!

 

image

 

Komplexität wächst nicht linear! Doppelt soviel Code bedeutet nicht doppelt soviel Komplexität und damit nur etwas verringerte Wartbarkeit!

Komplexität wächst vielmehr exponenziell! Und damit wächst auch die Unwartbarkeit exponentziell. Das bedeutet, mit der Unwartbarkeit ist es wie mit den Seerosen. Ein Beispiel aus der 10. Klasse: "Wenn die Seerosen auf einem Teich im Verlauf von 3 Monaten (90 Tage) jeden Tag ihre Zahl verdoppeln, wann bedecken sie den Teich zur Hälfte? Nach a) 10 Tagen, b) 45 Tagen, c) 60 Tagen, d) 89 Tagen?"

image Wenn wir die Wartbarkeit von Software mal mit der Schiffbarkeit des Teiches in diesem Beispiel gleichsetzen und Schiffbarkeit (mit Ruderbooten) definieren als "Max. 25% des Teiches sind von Seerosen bedeckt", wann tritt dann Unschiffbarkeit ein? Am 88. Tag. Ganz plötzlich.

Am 87. Tag ist der Teich nur zu einem 1/8 mit Seerosen bedeckt. Am 86. Tag ist es nur 1/16 oder knapp 6% usw. Das heißt, 90% der Zeit von 3 Monaten sind die Seerosen auf dem Teich kaum sichtbar; es sind hübsche Inselchen die weit weniger als 1% der Teichfläche bedecken. Dann jedoch, im Verlauf von 5 Tagen explodiert ihre Zahl. Wer am 87. Tag knapp 10% Bedeckungsfläche wahrnimmt und denkt, man solle gelegentlich mal überlegen, die Seerosen zurückzuschneiden, der muss nur ein Wochenende nicht hinschauen und hat keine Chance mehr zur Reaktion. Dann ist der Teich nämlich plötzlich komplett bedeckt.

Genau so scheint es mir mit der Unwartbarkeit. Man kennt ihre grundsätzliche Möglichkeit (Analogie: man weiß, Seerosen können sich bis zur Unschiffbarkeit ausdehnen), man will auch darauf achten, dass sie nicht eintritt (Analogie: man schaut gelegentlich auf den Teich, was denn die Seerosen so machen) - aber schließlich wird man von ihr überrollt (Analogie: wenn man die Seerosen als signifikante Fläche wahrnimmt, dann ist es schon zu spät).

 

image 

Das ist ja auch in anderen Bereichen ein oft beobachtetes Verhalten. Vorsorge in der Gesundheit oder im Umgang mit der Umwelt ist schwer "zu verkaufen", weil in beiden Bereichen vieles lange gut geht. Aber wenn es dann schlecht wird, dann oft sehr schnell. Dann kippt das Biotop um, dann ist der Herzinfarkt da - auch wenn kurz vorher noch alles ziemlich in Ordnung schien.

In der Softwareentwicklung liegen die negativen Effekte von mangelnder Vorsorge und Voraussicht jedoch nicht so weit in der Zukunft. Hier reden wir nicht über Jahrzehnte, sondern über wenige Jahre oder gar nur Monate. Da sollte es doch möglich sein, über die Schwierigkeit von uns Menschen, mit exponenziellen Entwicklungen klarzukommen, einfach mal hinwegzuspringen. Im Vergleich zum Weltklima oder der Globalisierung ist doch ein Softwareprojekt eine simple Angelegenheit. Da sollte es uns leicht fallen, Wartbarkeit nicht so auf die leichte Schulter zu nehmen. Lassen wir uns also nicht länger von Unwartbarkeit überraschen. Wenn wir die Plötzlichkeit lieben, haben wir ja auch immer noch Gruselfilme oder Weihnachten ;-)

Samstag, 20. Juni 2009

Wider die Geißeln zukunftsfähiger Software: Abhängigkeiten und Synchronizität

Was macht Software so schwer zu evolvieren? Abhängigkeiten. Was beschränkt den Nutzen morgiger Prozessorgenerationen für heutige Software: Synchronizität.

Funktionseinheiten, die von anderen abhängig sind, die insofern einen bestimmten Kontext voraussetzen, lassen sich mühsamer weiterentwickeln als solche, die frei und unabhängig sind. Möglichkeiten zur Abhängigkeit gibt es natürlich viele und nicht alle lassen sich immer kappen. Aber das Streben nach immer geringerer Kopplung lohnt sich - wenn Evolvierbarkeit gefragt ist. Wo hingegen zweifelsfrei Effizienz nötig ist, da müssen womöglich Kopplungen eng bleiben oder gar enger werden. Im Zweifelsfall bin ich jedoch der Meinung, dass unsere Software heute eher unter zu engen, als zu losen Kopplungen leidet.

Funktionseinheiten, die synchron arbeiten, arbeiten notwendig auch sequenziell. Von einer steigenden Zahl an Prozessorkernen können sie nicht profitieren. Die nützen ja nur, wenn es auch etwas parallel auszuführen gibt.

Abhängigkeiten und Synchronizität stehen uns also im Weg bei unserer Reise in eine glücklichere Softwarezukunft. Was tun? Ich beschreibe mal ein paar Gedanken anhand eines Beispiels. Hier etwas synchroner und abhängiger Code:

class MyBusinessLogik : IBusinessLogik
{
    IDatenquelle dq;
    IDatensenke ds;
    IValidator v;

    public BusinessLogik(IDatenquelle dq, IDatensenke ds, IValidator v)
    {
        this.dq = dq;
        this.ds = ds;
        this.v = v;
    }

    public int Aktualisiere(Parameter p)
    {
        v.Validiere(p);
        Datencontainer dc = this.datenquelle.LadeDaten(p.Query);
        int n = AktualisiereDaten(dc, p.Request);
        this.datensenke.SpeichereDaten(dc);
        return n;
    }

   private int AktualisiereDaten(Datencontainer dc, Aktualisierungsanfrage req) { ... }
}

Statische und dynamische Abhängigkeiten

Der Code ist statisch und dynamisch abhängig von anderen Funktionseinheiten:

image

Diese statischen Abhängigkeiten der Businesslogik sind statisch in der Businesslogik-Implementation verdrahtet. Das ist ein Punkt, den es deutlich herauszustellen gilt. Ausdruck der statischen Abhängigkeiten sind die Felder der Klasse MyBusinessLogik und die Aufrufe von Methoden auf deren Instanzen.

Die dynamischen Abhängigkeiten hingegen, sind nicht in der Businesslogik-Implementation zu finden. Sie kennt nur abstrakte Dienstleister wie IDatenSenke oder IValidator. Die zur Laufzeit relevanten Implementationen, werden ihre hingegen über den Ctor injiziert.

In puncto Abhängigkeiten hat also schon eine gewisse Separation of Concerns (SoC) stattgefunden: die laufzeitrelevante Bindung übernimmt eine andere Codeeinheit.

Vollständig ist die SoC allerdings nicht. Denn - wie gesagt - die BusinessLogik hat neben ihrer funktionalen Aufgabe auch noch eine nicht-funktionale: die statische Bindung. Ihr dienen die Felder und die Aktualisiere()-Methode. Ihre funktionale Aufgabe erfüllt AktualisiereDaten().

Während der Code also durch Injektion konkreter Abhängigkeiten gegenüber der früheren Praktik evolvierbarer geworden ist, weil er nicht mehr an Implementationen gebunden ist. So ist die Kopplung noch nicht wirklich lose. Die statischen Abhängigkeiten halten ihn starr. Das schränkt seine Evolvierbarkeit ein.

Synchronizität und Sequenzialität

Dass die BusinessLogik synchron und sequenziell ist, liegt auf der Hand. Ohne Hilfsmittel kann sie in C# nicht definiert werden. Ein Aufrufer von Aktualisiere() muss also auf das Ergebnis warten. Und während Aktualisiere() läuft, kann ein Prozessor nichts anderes tun; andere Aufgaben, andere Programme müssen auch warten. (Preemptives Multitasking lasse ich hier außen vor. Damit kann zwar doch quasi-parallel weiteres geschehen, aber jede zusätzliche Aufgabe verlangsamt alle schon laufenden.)

Auf innerhalb der BusinessLogik gibt es keine Parallelität. Mehrere Prozessorkerne bringen hier überhaupt keinen Nutzen. Es könnte ja sein, dass Laden, Verarbeiten und Speichern der Daten im Rahmen der Aktualisierung zumindest überlappen dürfen. Während noch Daten geladen werden, kann die Verarbeitung schon beginnen. Und während die Verarbeitung noch läuft, können erste Ergebnisse schon gespeichert werden.

Die synchrone Notation einer Sprache wie C# lässt das jedoch ohne Hilfsmittel nicht zu. Und weil wir letztlich "in C# denken", kommen wir auch nicht so recht auf die Idee, dass es auch anders sein könnte. Die beschränkten Ausdrucksmttel von C# stehen für uns zu sehr im Vordergrund. Das objektorientierte, synchrone Programmierparadigma ist wie ein Korsett um unsere Vorstellungen.

Das schränkt den Nutzen ein, den die Software, zu der die BusinessLogik gehört, aus der zukünftig wachsenden Zahl an Prozessorkernen ziehen kann.

Ausweg Asynchronizität

Ich glaube nun, dass uns ein anderes Paradigma einen Weg aus dieser beschränkten Zukunftsfähigkeit weisen kann. Durch asynchrone Programmstrukturen können wir beide Fliegen mit einer Klappe schlagen. Und das geht so:

1. Abhängigkeiten beseitigen

Im ersten Schritt schütteln wir auch noch die statischen Abhängigkeiten ab. Oder genauer: Wir separieren den Aufbau von Abhängigkeiten komplett von der problemdomänenorientierten Funktionalität. Dazu führe ich mal den Begriff "Prozessdefinition" ein. Ich trenne die Abfolge von Arbeitsschritten ganz klar von den Arbeitsschritten selbst. Alle Arbeitsschritte werden damit frei von Abhängigkeiten:

image

Die BusinessLogik v2 enthält jetzt nur noch die Funktionalität von AktualisiereDaten()! Aufgabe der Prozessdefinition ist es nun, die Arbeitsschritte ohne Abhängigkeiten in eine nützliche Reihenfolge zu bringen. Dass sie selbst viele Abhängigkeiten enthält, ist nicht schlimm. Sie ist im Vergleich zu den Arbeitsschritten trivial.

Dynamische und statische Abhängigkeiten stehen damit auf derselben Stufe: sie sind separierte Concerns.

image

Die Konsequenz solcher Beseitigung von Abhängigkeiten ist, dass Funktionalität nicht mehr geschachtelt ist. Funktionalität der Problemdomäne wie die BusinessLogik oder auch Infrastrukturfunktionalität wie eine Datenquelle sind Blätter im Abhängigkeitsbaum. Ergebnisse reichen sie also nicht tiefer hinunter in einem Aufrufbaum. Unter ihnen gibt es ja keine Ebene mehr. Stattdessen sind Ergebnisse immer Rückgabewerte in irgendeiner Form. Nur wer die Funktionalität aufruf bzw. sie zusammengesteckt hat, weiß ja, was weiter mit Ergebnisse geschehen soll. Die Wiederverwendbarkeit ist damit gestiegen.

2. Vom Callstack zum Fluss

Wenn nun alle Funktionalitäten ohne Abhängigkeiten sind, dann sind wir plötzlich sehr frei, was ihre Verschaltung angeht. Müssen wir sie denn wirklich noch synchron "ineinanderstecken"?

Hier aber zunächst ein erster Schritt. Ich nehme die refaktorisierten Funktionalitäten 1:1 und füge sie zu einem Prozess zusammen. Eine Funktion scheint mir da der passende Ausdruck für einen Prozess. In den geht etwas hinein und am Ende kommt etwas heraus.

class Prozessdefinition
{
    public Func<Parameter, int> Prozess { get; private set; }

    public Prozessdefinition(IDatenquelle dq, IDatensenke ds, IValidator v, IBusinessLogikV2 blv2)
    {
        this.Prozess = new Func<Parameter, int>(p =>
           {
               v.Validiere(p);
               Datencontainer dc = dc.LadeDaten(p.Query);
               int n = blv2.AktualisiereDaten(dc, p.Request);
               ds.SpeichereDaten(dc);
               return n;
           });
    }
}

Soweit das synchrone Programmierparadigma. Um weiter zu kommen, ist nun eine Richtungsänderung nötig. Asynchrones Denken und codieren ist nötig. Dafür zunächst eine etwas andere Darstellung der Funktionseinheiten:

image

Die Funktionseinheiten haben immer noch keine Abhängigkeiten untereinander. Aber es ist ihnen nun deutlich anzusehen, was reingeht und was rauskommt. Eine implementationsunabhängige Darstellung im Sinne von EVA (Eingabe-Verarbeitung-Ausgabe).

Das bisherige Abhängigkeitsdiagramm war nicht so detailliert. Darin war nur zu sehen, ob eine Funktionalität von einer anderen abhängig ist. Indem nun die Funktionalitäten aber darstellen, wie man von ihnen abhängig sein kann, ist Klarheit gewonnen. (Dass ich Ausgaben bei Validator und Datensenke eingeführt habe, ist hier vernachlässigbar. Sie machen den späteren asynchronen Prozess etwas einfacher.)

Die Prozessdefinition ist nun nicht mehr in einer Black Box eingeschlossen, sondern kann im Grunde das trivial gewordene Abhängigkeitsdiagramm ersetzen:

image

Diese Grafik zeigt nicht nur die grundsätzlichen Zusammenhänge der Funktionsbausteine, sondern auch den Zweck, zu dem sie zusammenhängen: die Abarbeitungen eines Prozesses. Und bitte im Hinterkopf behalten: Die Funktionsbausteine haben keine statischen Abhängigkeiten mehr. Ihre dynamische Zusammenarbeit im Prozess sieht man ihnen selbst nicht an. Die ist Sache des Prozesses.

Jetzt der Trick: Wenn der Prozess erstmal so dargestellt ist und eben nicht sofort als Code wie in der obigen ersten Prozessdefinition, dann... ja, dann ist es leicht, von einer synchronen Kopplung abzusehen. Warum sollte ich dies:

image

übersetzen in jenes:

if (v.Validiere(p))
{
    Datencontainer dc = dq.LadeDaten(p.Query);
    ...

Das Prozessdiagramm ist aber nicht zu verwechseln mit einem simplen Flowchart, das auch ein Kind des synchronen Programmierparadigmas ist. Es ist allgemeiner als Fluss zu verstehen: Die Funktionseinheiten sind verbunden zu einem Fluss, auf dem Daten zwischen ihnen als Verarbeitungsstationen fließen.

3. Asynchrone Flüsse

Die Darstellung eines Prozesses bietet nun die Chance, das Paradigma zu wechseln. Die Frage ist nur, wie kann solch bisher synchroner, sequenzieller Code in asynchronen, sequenziellen oder parallelen Code gewandelt werden?

Der Schlüssel liegt in expliziten Verbindungsstücken!

Bei der üblichen synchronen Programmierung sind die Funktionsbausteine quasi aneinandergeschweißt. Die ursprüngliche BusinessLogik war untrennbar verbunden mit einem Validator usw. Sie bildeten eine Einheit - auch wenn die konkreten Abhängigkeiten erst zur Laufzeit dynamisch eingespritzt wurden.

Das ist zwar effizient - aber eben auch inflexibel. Und es ist synchron. Damit steht solche Verschweißung der Evolvierbarkeit im Wege.

Ganz anders das Bild, wenn die Funktionsbausteine nicht verschweißt, sondern verschraubt sind. Mit expliziten Verbindungsstücken - wieder eine Separation of Concerns - können Beziehungen viel flexibler aufgebaut werden.

Ein Mittel dafür sind die Ports der Microsoft Concurrency Coordination Runtime (CCR). Mit ihnen ließe sich ein Prozess ganz anders beschreiben. Aus den bisherigen synchronen Methoden

interface IValidator
{
    bool Validiere(Parameter p);
}

interface IDatenquelle
{
    Datencontainer LadeDaten(string query);
}

könnten solche werden:

void Validiere(Parameter p, Port<bool> output) {...}

void LadeDaten(string query, Port<Datencontainer> output) {...}

Ich habe sie aus zwei Gründen nicht in eine Klasse oder ein Interface eingetragen: Zum einen möchte ich an dieser Stelle keine bestimmte Lösung jenseits explizit verbundener Funktionsbausteine vorschlagen. Auch die CCR Ports sind nur eine mögliche Variante für explizite "Schraubverbindungen". Zum anderen sollen die beiden freigestellten Routinen unterstreichen, dass Funktionale Programmierung - also die Konzentration auf Funktionen statt Klassen - einen Beitrag leisten kann, um Software zukunftsfähig zu machen.

Die Übersetzung einer bisher synchronen Methode könnte mit Ports also geradlinig sein: Eingabeparameter bleiben, Rückgabewerte werden ersetzt durch einen Output-Port. Der ist mit der nächsten Verarbeitungsstation verbunden. Zu jeder Methode gehört also ein Port für die Eingabe. Für die obige Methode Validiere() könnte das in vereinfachter und roher Form so aussehen:

Port<bool> pOutput = ...;

var pValidiere = new Port<Parameter>();
pValidiere.ReceiveSequentially(p => Validiere(p, pOutput));

Der Port für die Ausgabe, der gehört dann zum nächsten Arbeitsschritt im Prozessfluss.

Die Methode ReceiveSequentially() ist eine Extension Method für Ports, die ich mir ausgedacht habe. Sie bindet einen Eventhandler so an einen Port, dass immer nur ein Element zur Zeit verarbeitet wird. Das sichert eine sequenzielle, allerdings asynchrone Verarbeitung durch die Stationen in einem Prozessfluss zu. Bei Bedarf können Stationen aber natürlich auch parallel verarbeiten. Ihre vielen Ergebnisse müssen dann nur auch wieder eingesammelt werden. Map-Reduce ist dafür ein berühmtes Beispiel.

Viel wichtiger als Parallelität ist jedoch die Asynchronizität. Dadurch, dass Arbeitsschritte jetzt explizit über puffernde Ports gekoppelt sind, geben sie immer wieder ihre Prozessorressource (Thread auf einem Kern) frei. Das skaliert wunderbar. Es ist kooperatives Multitasking, das alle Kerne oder potenziell auch viele Maschinen überspannt.

Damit ist das eingangs beschriebene Ziel im Grunde erreicht: Die Abhängigkeiten sind minimiert, der Umgang mit ihnen ist herausfaktorisiert. Und die Asynchronizität ist eingeführt. Damit ist der Code zukunftsfähiger geworden:

  • Viele Flüsse können in dieser Weise gut skalierbar abgehandelt werden und nutzen dabei alle Prozessorkerne. (Und falls es mal nicht viele Flüsse durch die Last auf einem Rechner zu bearbeiten geben sollte, dann zeige ich in einem späteren Blog-Posting, wie so ein unterforderter Rechner anderen seine Leistung anbieten kann.)
  • Abhängigkeitsfreie Funktionseinheiten lassen sich viel einfacher weiterentwickeln und neu kombinieren.
Technische Umsetzung mit Notationsschwierigkeiten

Jetzt aber noch kurz konkret zur Umsetzung des obigen Prozesses:

  • Es kommen Parameter an, die in mehreren sequenziellen Schritten durch einen Prozess laufen sollen.
  • Am Anfang werden die Parameter validiert. Nur wenn die Validation erfolgreich ist, werden sie zum Laden von Daten weitergeschickt. Ein Fork-Station spaltet die Daten dafür auf: sie werden gleichzeitig zur Validation und zu zwei Joins weitergeleitet.
  • Der erste Join führt das Ergebnis der Validation und die Query in den Parametern zusammen und leitet die Query weiter an die Datenbeschaffung - aber natürlich nur, wenn die Validation erfolgreich war. Validation und Datenbeschaffung laufen also nicht parallel, sondern immer noch sequenziell.
  • Nach der Datenbeschaffung führt ein weiterer Join deren Ergebnis (Datencontainer) und die Parameter zusammen und leitet beide weiter an die eigentliche Geschäftslogik.
  • Die Geschäftslogik verändert die Daten und reicht sie weiter zum Speichern. Gleichzeitig erzeugt sie ein Ergebnis (z.B. Anzahl veränderter Datensätze), das jedoch nur am Ende aus dem Prozess herauskommt, wenn auch die Datenspeicherung erfolgreich war.

Dieses Szenarion mit Ports zu bauen, ist nicht schwierig. Es ist nur etwas umständlich. Eine wirklich gute textuelle Notation oder ein Fluent Interface, dass gerade diese Fork-Join-Kombinationen plastisch macht, ist mir noch nicht eingefallen.

Ohne Fork-Join oder auch Scatter-Gather oder Select/Choice und andere Muster, bei denen mehrere Ports beteiligt sind, wäre es einfach. Mit Pipes könnte ein vereinfachter Fluss ohne Fork-Join so aussehen:

Validator | Datenquelle | BusinessLogikv2 | Datensenke

Jede Station würde ihre Ergebnisse einfach nur weiterschieben an die nächste. Allerdings müsste z.B. der Validator alle Parameter weitergeben und nicht nur sein boolean-Resultat, weil ja nachfolgende Stationen davon mehr oder weniger für ihre Arbeit brauchen.

Etwas realistischer ließe sich so ein Fluss auch mit einem Fluent Interface beschreiben, z.B.

new Stage<Parameter>(Validiere)
    .Stage<Parameter, Tupel<Datencontainer, Parameter>>(LadeDaten)
    .Stage<Tupel<Datencontainer, Parameter>, Tupel<Datencontainer, int>>(Aktualisieren)
    .Stage<Tupel<Datencontainer, int>, int>(SpeichereDaten);

Dabei stehen die generischen Typparameter von Stage<TIn, TOut> für den Typ des Input- und des Output-Ports. Als Verarbeitungsschritt wird dann Action<TIn, TOut> erwartet.

Die Schwierigkeit der Notation - nicht der Technologie! - beginnt jedoch, wenn Flüsse sich teilen und wieder zusammenfließen.

Die Fork am Anfang des obigen Prozesses ließe sich so formulieren:

var fork = new Fork<Parameter, Parameter, string, Parameter>(
    (i, o0, o1, o2) => {oo.Post(i); o1.Post(i.Query); o2.Post(i);});

Der erste Typparameter definiert den Input-Typ, die weiteren die Typen für die Output-Ports, d.h. die Input-Ports der nachfolgenden Schritte. Die Aufgabe des Lambda-Funktion ist also die Aufspaltung oder Verteilung des Input auf die Outputs.

Die einzelnen Verarbeitungsschritte sind ebenfalls für sich genommen leicht zu formulieren:

var val = new Stage<Parameter, bool>(Validiere);
var laden = new Stage<string, Datencontainer>(LadeDaten);
var akt = new Stage<Tupel<Datencontainer, Parameter>,
                                          Datencontainer, int>(Aktualisieren);
var speichern = new Stage<Tupel<Datencontainer, int>, int>(SpeichereDaten);

Und wie die Joins formulieren, die Zusammenführungen von mehreren Flussarmen?

var joinFürLaden = new Join<bool, string, Datencontainer>((i0, i1, o0) => oo.Post(i1));

var joinFürLogik = new Join<Datencontainer, Parameter, Tupel<Datencontainer, Parameter>>(
    (i0, i1, oo) => oo.Post(new Tupel<Datencontainer, Parameter>(i0, i1)));

var joinFürEnde = new Join<bool, int, int>((i0, i1, oo) => oo.Post(i1));

Zum Schluss noch die Prozessschritte "zusammenstöpseln":

fork.Output[0] = val;
fork.Output[1] = joinFürLaden.Input[1];
fork.Output[2] = joinFürLogik.Input[1];

val.Output = joinFürLaden.Input[0];

joinFürLaden.Output = laden;

laden.Output = joinFürLogik.Input[0];

joinFürLogik.Output = akt;

akt.Output[0] = speichern;
akt.Output[1] = joinFürEnde.Input[1];

speichern.Output = joinFürEnde.Input[0];

Der Input geht dann in den Prozess bei fork.Input hinein und das Ergebnis kommt bei joinFürEnde.Output heraus. Das ist technologisch nicht kompliziert - aber die vorstehende Formulierung ist sicher nicht so gut zu lesen wie die synchrone Variante ganz am Anfang.

Was tun? Hier ist wahrscheinlich eine DSL angezeigt. Eine textuelle könnte schon ein wenig Erleichterung bringen; am Ende geht es aber wohl nicht ohne Diagramme. Ja, das wäre doch mal was: Eine grafische DSL mit einem hübschen Designer, die aus solchen Prozessdiagrammen eine Assembly produziert, die wir nur noch mit Prozessschritten parametrisieren müssen, wenn das nicht schon der Designer getan hat, weil wir ihm Referenzen auf Prozessschritte übergeben haben.

Ist das dann nicht aber die Microsoft Workflow Foundation neu erfunden? Nein. Die Prozesse, von denen ich hier geschrieben habe, sind leichtgewichtiger. Sind sollen nicht lange laufen. WF scheint mir da Overkill.

Und vielleicht... findet sich ja doch auch noch eine textuelle Beschreibung z.B. in Form eines Fluent Interface? Das würde mir sehr gefallen.

Aber all dessen ungeachtet ist mir an dieser Stelle wichtig gezeigt zu haben (oder zumindest laut gedacht zu haben), dass asynchrone explizite Kopplung mit soetwas wie CCR Ports hilft, Software zukunftsfähig in zweierlei Hinsicht zu machen. Wer asynchrone Prozesse denkt, der geht anders mit Abhängigkeiten um und macht Software fit für Mehrkernprozessoren. Da müssen wir gar nicht erst auf Axum & Co warten. Das geht hier und heute.

Dienstag, 16. Juni 2009

Tagesschäft und Lernen vereint - School of .NET

image Neulich haben Stefan Lieser und ich noch in unseren Blogs drüber diskutiert. Jetzt ist sie schon Realität: die School of .NET. Wir fanden das "gebrainstormte" Konzept so überzeugend, dass wir uns gleich hingesetzt und ein Curriculum ausgearbeitet haben.

Wer sich für berufsbegleitendes Lernen interessiert, wer nicht ein oder gar mehrmals 5 Tage am Stück aus dem Betrieb raus kann, um den Clean Code Developer "Kickstart" zu bekommen, wer schon lange sein .NET-Know-How festigen und ausbauen wollte, ohne das Projekt länger zu verlassen, dem eröffnet jetzt die School of .NET die Möglichkeit, das Angenehme mit dem Nützlichen zu verbinden. (Was hier angenehm und was nützlich ist, das Lernen oder das Tagesgeschäft, das überlasse ich jedem selbst zur Bewertung ;-)

Hier die Beschreibung des ersten "Semesters", dass Stefan und ich anbieten: die Ausbildung zum .NET-Entwickler für lokalen, synchronen und sequenziell entwickelten Code, den Synchronous Developer. Es findet simultan in Köln und Hamburg statt. Regionalität ist Trumpf!

Montag, 15. Juni 2009

Last und Lust der Kommiteesoftware

Gerade habe ich das Originalpaper "How Do Commitees Invent" von Mel Conway gelesen, dem Conway´s Law entstammt. Da ist mir unerwartet ein Schauer kalt den Rücken runtergelaufen bei dieser Aussage:

"To the extent that an organization is not completely flexible in its communication structure, that organization will stamp out an image of itself in every design it produces. The larger an organization is, the less flexibility it has and the more pronounced is the phenomenon."

image Nicht, dass ich Conway´s Law nicht schon vorher gekannt hätte. Aber manchmal vergrößert sich die Tragweite von Wissen durch neue Formulierungen oder einen anderen Blickwinkel einfach nochmal. Vielleicht liegt es auch daran, dass ich mich gerade Technologien abseits der Microsoft-Schiene oder überhaupt des Mainstreams annähere wie Erlang oder CouchDB. Jedenfalls habe ich den Absatz oben innerlich quasi simultan so gelesen:

"To the extent that Microsoft is not completely flexible in its communication structure, Microsoft will stamp out an image of itself in every design it produces. The larger Microsoft is, the less flexibility it has and the more pronounced is the phenomenon."

Und dann habe ich an den Entity Framework, an Oslo, an Visual Studio, an Windows und an andere Microsoft Produkte/Technologien gedacht. Microsoft hat sich seit 1990 und Visual Basic 1.0 sehr verändert. Für mich, der ich vor einigen Jahren noch viel mit Microsoft Deutschland zu tun hatte, war insbesondere ein Änderungsschub um 2002/3 zu spüren.

Wasfüreine Organisation ist Microsoft also heute? Inwiefern ist Microsoft "not completely flexible in its communication structure"? Das hat natürlich nur wenig damit zu tun, ob Microsoft-Mitarbeiter Email oder Blogs benutzen. Es geht vielmehr um die organisationsinternen Kommunikationswege und Weisungsbefugnisse.

Dieselbe Frage lässt sich natürlich auch Oracle oder IBM oder SAP stellen. Aber dort kenne ich mich nicht so mit den Produkten aus.

Sind Microsoft-Produkte heute also vielleicht schon quasi notwendig mehr Last als Lust? Sind sie womöglich heute schon weniger der Lösung eines Problems als der inneren Struktur von Microsoft angemessen? Hat Microsoft eine Größe und damit Kommunikationsstrukturen erreicht, die sozusagen notwendig zu Overengineering führen?

image Ich tendiere da mal zu einem vorsichtigen "Hm... es scheint immer mehr der Fall zu sein..." Das bedeutet nicht, dass nun alle Technologien über diesen Kamm geschoren werden müssten. Es geht nur um die Tendenz sowie eine daraus abzuleitende Vorsicht. Auch ist Microsoft inzwischen so groß, dass es immer wieder "Taschen der Widerstandes" geben kann und gibt, die nicht in die allgemeinen Kommunikationsstrukturen 100% eingebunden sind und insofern auch andere Systeme bauen können. Die Gruppe um die Concurrency Coordination Runtime fällt mir da ein oder das F# Team.

Doch sobald diese Taschen sich öffnen wollen, kollidieren sie natürlich irgendwann mit der "großen Organisation". Und dann kommt es darauf an... Was macht die mit diesen aus ihrer Sicht eigentlich anarchischen Systemen?

Technisch gesehen, läuft Microsoft qua seiner Größe und Organisation also langsam (oder immer schneller?) in den Morast. Die interessanten Sachen passieren dann irgendwann einfach dort nicht mehr. (Ja, ich weiß, wieviel Forschungsgelder Microsoft ausgibt. Dennoch ist es schwierig für eine solch große Organisation, den Geist der Anfangsjahre zu bewahren. Das ist lange, sehr lange recht gut gegangen - aber die Controller und damit die formale Kommunikation sind in unserer Wirtschaft ein Naturgesetz. Sie lassen sich hinauszögern, aber nicht vermeiden.)

Beweis #1: Das Internet. Microsoft hat es verschlafen und nur mühsam aufholen können. Beweis #2: O/R Mapping. Hier hat Microsoft lange geschlafen bzw. nichts zustande gebracht und ringt immer noch um ein vernünftiges Produkt. Beweis #3: IDE. Visual Studio kann einiges, aber die Qualität in puncto Flexibilität, wie sie heute so wichtig ist, die Eclipse bietet, hat VS nicht. Beweis #4: Microsoft Office. Ohne Zweifel hat Microsoft hier einen Standard geschaffen. Aber Innovation ist etwas anderes. Die spannenden Sachen wie realtime Kollaboration und neue Kommunikationsformen passieren z.B. bei Google.

Was den Technikern eine Last ist, mag anderen aber natürlich eine Lust sein. Controller lieben solche Moloche wie Microsoft. Hatte Microsoft lange Jahre mit dem Image einer "Bastelbude" zu kämpfen, die nicht "enterprise ready" ist, so gibt es diesen Zweifel in seiner grundlegenden Ausprägung nicht mehr. Microsoft ist hoffähig. Wer sich für Microsoft entscheidet - von BizTalk Server bis Entity Framework -, der wird nicht gefeuert, wenn es nicht klappt. Ob die Entscheidung allerdings in technischer Hinsicht die beste ist, hat damit nichts zu tun. Microsofts schiere Größe ist der scheinbare Garant für einen Mindesterfolg.

Nach Conway´s Law also mal meine These wie hier illustriert:

image

Eine gewisse Zeit lang nimmt die Qualität der Technologien durch Wachstum zu. Aber ab einer gewissen Größe sinkt sie eben - sogar unter das Ausgangsniveau. Mit "Qualität"  meine ich natürlich sozusagen die "overall user experience" von der Funktionalität über die Dokumentation bis zum Service.

Wünschen tue ich mir das für Microsoft oder IBM oder Google oder sonst einen Technologieanbieter nicht. Meine Befürchtung ist nur, dass es eben unvermeidlich ist. Deshalb sollten wir auf der Hut sein und gerade als Techniker immer unser Radar kreisen lassen auf der Suche nach Innovationen, die uns wirklich voran bringen. Das Entity Framework - sorry to say - gehört nicht dazu. Und ob Oslo "es reißen wird", halte ich auch noch nicht für ausgemacht.

Also: Wachsam bleiben! Links und rechts andere Technologien und Paradigmen anschauen. Nicht auf Microsoft warten. Im eigenen Haus nicht den Controllern die Technologieentscheidungen überlassen. Nur so erhalten wir uns die Lust an den Technologien, statt fatalistisch "Kommiteesoftware" hinterherzulaufen.

Freitag, 12. Juni 2009

Anforderungsaikido - Den Kunden mit gnadenlosen Requirements zur Agilität motivieren

Auf dem Architecture.NET Open Space neulich in Düsseldorf bin ich in einem Gespräch über einen interessanten Gedanken gestolpert. Thema war das Übliche:

Gemeinhin ist es ja so, dass der Kunde eine Anforderung stellt, das Team versucht sie umzusetzen und der Kunde dann irgendwie nicht ganz zufrieden ist.

Das ist für uns quasi normal, wenn auch unbefriedigend. Wir glauben nicht mehr daran, dass der Kunde genau weiß oder sagen kann, was er will. Also leben wir mit Experimenten, Angeboten, Glaskugelguckerei - und damit YAGNI.

Für den Kunden ist das aber keinesfalls normal. Der glaubt ja allermeistens, dass er genau weiß, was er will. Und er glaubt, dass er nach all den Sitzungen über Anforderungsdokumenten es auch richtig rübergebracht hat. So ist es kein Wunder, wenn er am Ende beim Test verwundert oder gar ärgerlich ist, nicht vorzufinden, was er gewollt hat.

Das mag objektiv ungerecht sein - subjektiv ist es aber so. Laien haben meist entweder den Eindruck, alles ausreichend spezifiziert zu haben, oder den Anspruch, dass die Softwareentwicklung in ihrer Weisheit die Lücken schon selbstständig schließen wird.

Wir wir alle wissen, ist das nicht so und funktioniert auch nicht.

Jetzt der interessante Gedanke: Damit wir nicht länger während der Implementierung rätseln, YAGNI vermeiden und der Kunde glücklicher wird, könnten wir es ja zur Abwechslung mal mit "gnadenlosen Requirements" versuchen. Gnadenlos nenne ich sie, weil es darum geht, immer genauer und genauer und genauer nachzufragen. Quasi bis in die Absurdität hinein.

"Lieber Kunde, willst du es so oder anders? Wenn so, willst du es dann genau so oder lieber so? Wirklich? Echt? Garantiert? Überleg nochmal. Oder möchttest du es doch vielleicht lieber leicht abgewandelt so?"

Das klingt nicht nur hier nervig in seiner Allgemeinheit. Auch in einem Kundengespräch ist das nervig. Dabei geht es allerdings nicht darum, wirklich das absolut letztendgültig "Richtige" aus dem Kunden herauszuholen. Nein, nein! Im Gegenteil! Auch wenn die konkreten Fragen sich natürlich auf seine Problemdomäne bzw. die vorgestellte Lösung beziehen, soll der Kunde nicht wirklich auf die immer detaillierter werdenden Frage eine präzise Antwort haben.

Der Kunde soll vielmehr selbst spüren, dass er keine (!) Antwort hat. Ja, ganz genau: Er soll an den Rand seiner Selbstsicherheit geführt werden. Es soll ihm durch immer weitergehende Entscheidungen, die tatsächlich nur er treffen könnte - aber eben nicht kann, weil er auch die Antwort nicht weiß -, gezeigt werden, dass er selbst (noch) nicht genau weiß, was er haben will.

Das Ziel der Anforderungsanalyse ist mithin nicht mehr, die Anforderungen genau zu erheben, sondern nur genügend gute Anforderungen zu bekommen, um loslegen zu können. Und darüber hinaus soll Kunde wie Team klar sein, dass eben viel im Unklaren ist und auch nicht klarer sein kann. Es geht halt nicht anders.

Damit ist dann der Kunde idealerweise weichgekocht für ein iteratives Vorgehen. Er kann dann auch gefühlsmäßig nachvollziehen, dass ihm nicht mit einer "Big Bang Lösung" gedient ist. Er ist dann selbst froh, sich seinen eigenen Vorstellungen schrittweise, iterativ anzunähern.

Gnadenlos ist solche Anforderungserhebung, weil sie den Kunden echt beim Wort nimmt. Er hat ja erstens den Anspruch, seine Anforderungen zu kennen, und zweitens den Glauben, dass die sich dann auch geradlinig und klar planbar umsetzen lassen.

Nun, dann nehmen wir ihn eben mit gnadenlosen Requirements ernst. Dann soll er mal die "Hosen runterlassen" und wirklich, wirklich detailliert jeden Fliegenschiss bestimmen. Er wird dann schon merken, dass er damit nicht weit kommt. Wichtig ist dafür natürlich, quasi nie mit den vom Kunden formulierten Anforderungen zufrieden zu sein. Er muss aufgeben beim Spezifizieren. Er muss sagen: "Ok, ihr habt gewonnen. Ich weiß es wirklich nicht genau. Versucht es also mal und zeigt mir das Ergebnis möglichst schnell. Aber dann lasst uns auch in kleinen Schritten vorgehen."

Akzeptiert sind gnadenlose Requirements erst, wenn der Kunde auf der Matte liegt und abklopft. Dann (!) können wir mit ihm ernsthaft sprechen. Dann machen heben wir ihn auf und versichern ihm, dass das alles gar nicht so schlimm ist. Es gibt einen Ausweg und der heißt agiles Vorgehen.

Ja, den Gedanken fand ich schon interessant. Mit gnadenlosen Requirements den Kunden mit den eigenen Waffen "agilitätsreif zu schießen" ;-) Oder etwas weniger brutal ausgedrückt: Anforderungsaikido. Die Energie des Kunden aufnehmen, weiterführen und ihn damit aus seiner Balancezone zu ziehen. Dann fällt er in unseren agilen Arme ;-) Ja, das klingt etwas netter, glaub ich.

Mittwoch, 10. Juni 2009

Einsteigen, bitte - Level 1 der School of .NET

Stefan Lieser hat nun schon eine erste Skizze zu einem Curriculum für unsere School of .NET geliefert. Damit stimme ich überein:

Es geht um die Grundlagen der Objektorientierung (in C# oder auch VB): Erst wenn diese Grundlagen jenseits von "OOP hat mit Klassen zu tun" klar sind, ist zu erwarten, dass neuere Konzepte wie dynamische Programmiersprachen, funktionale Programmierung, Workflows, Aspektorientierung  in ihrer Andersartigkeit allen Vor/Nachteilen gewürdigt werden können.

Der einzelne Entwickler ist im Fokus, um auch in einer Umgebung, wo nicht alle Entwickler auf demselben Stand sind, schnellstmöglich Fortschritte am Arbeitsplatz erzielt werden können. Persönliche Grundfitness steht am Anfang, bevor es an echte Teamarbeit geht. Die CCD-Bausteine der ersten Graden - allen voran konsequentes automatisiertes Testen - geben hier den Takt an. Ziel ist - so könnte man sagen - eine Konditionierung im Hinblick auf Testautomatisierung: in der School of .NET wird es von der ersten Übung an keinen Code geben, der nicht mit einem Testrahmen ausgestattet ist.

Im Sinne eines Menschenbildes, das von schrittweiser Entwicklung des Bewusstseins zu immer höheren Ebenen ausgeht - nicht nur im Allgemeinen, sondern auch in Bezug auf Fachkompetenzen - steht am Anfang die Auseinandersetzung mit Code auf den kleinsten physischen Abstraktionsebenen: Methode, Klasse, Assembly und ansatzweise Komponente. Synchrone, 1- oder 2-Tier Software muss gemeistert werden, um fit zu sein für die nächste Stufe.

Hinzufügen möchte ich für Level 1 der School of .NET allerdings noch...

  • Grundlagen des .NET Framework wie z.B. Streams, Exceptions, Instrumetierung, Linq, Extension Methods, Iteratoren, UserControls und mehr,
  • Grundlagend des O/R Mappings, weil Datenbankzugriffe immer noch für viele Projekte ein Thema sind, das unnötig viel Aufwand macht

Damit haben wir, glaube ich, ein ordentliches Curriculum beieinander. Mancher mag sich dabei allerdings fragen, warum das eine oder andere Thema nicht darin auftaucht. Was ist mit WinForms oder ASP.NET MVC oder Silverlight? Was ist mit Security? Warum kein ADO.NET?

Die School of .NET hat nicht zum Ziel, Technologietraining zu sein. Über die technologischen Feinheiten von WinForms oder Security oder ADO.NET wird allerorten berichtet. Dazu kann man sich auch ein Spezialtraining suchen oder die Literatur studieren.

Mit dem Curriculum der School of .NET möchten wir uns vielmehr auf das konzentrieren, was zum einen bisher weniger Raum einnimmt in der Ausbildung oder was sich schlecht in Büchern vermitteln lässt. Das sind Grundlagen, Zusammenhänge, Konzepte und Prozesse. Beispiele:

  • Bei Streams und Exceptions geht es uns nicht so sehr um die APIs des .NET Framework, sondern um die Konzepte. Ein streambasierter Umgang mit Daten ist etwas anderes als ein blockorientierter oder ein objektorientierter. Exceptions sind leicht zu werden und zu fangen; darüber müssen wir auch nicht reden. Aber wo einsetzen, welche Alternativen gibt es für die Meldung von (unvorhergesehenen) Zuständen: das ist konzeptionell interessant, das hat Auswirkungen auf den Anwendungsentwurf.
  • WinForms und GDI+ Feinheiten sind nicht ohne. Aber darüber lässt sich allerorten lesen, das kann man leicht selbst ausprobieren. Der konzeptionelle Umgang mit UserControls jedoch, ihr Platz im Entwurf, das ist etwas anderes. Hier gibt es Nachholbedarf.
  • Auch O/R Mapping ist für uns kein Technologie- sondern ein Konzeptthema. Die School of .NET wird kein Produkttraining durchführen. O/R Mapping soll vielmehr im Sinne von Patterns zu Bewusstsein gebracht werden.

Wer also spezielle Technologien trainieren will, der soll in ein Technologietraining gehen. Wer Hype hören will, der soll eine "Das ist alles neu in VS 20xx und .NET y.z"-Veranstaltung besuchen. Die School of .NET widmet sich - soweit es bei ihrem Plattformfokus geht - "Überzeitlichem". Mit dem Curriculum wenden wir uns an die, die einen Einstieg in die Plattform ernst meinen bzw. endlich ihre im Tagesgeschäft gewonnenen Erfahrungen auf soliden Untergrund stellen wollen.

Ein- und Umsteiger sind die primäre Zielgruppe für Level 1 der School of .NET. Für die Übergangsphasen zwischen Projekten und bei Aufnahme einer Tätigkeit ist das Level 1 gedacht. Berufsbegleitung bedeutet Entlastung des Betriebes bei der Ausbildung und Ergänzung anderer Ausbildungen im Sinne einer plattformspezifischen Konkretisierung und Vertiefung.

Und was kommt danach? Nach Level 1 kommt Level 2. Klar :-) Da geht es dann um:

  • Fortgeschrittene Objektorientierung z.B. mit AOP und dynamischen Sprachen
  • Echte Komponentenorientierung und Softwarearchitektur
  • Asynchrone Programmierung - lokal und verteilt, also N-Tier Szenarien
  • Die höheren Grade des CCD-Wertesystems mit Bausteinen wie automatisierter Produktion, iterativem Vorgehen oder statischer Codeanalyse

Level 2 richtet sich an den Entwickler im Team. Denn nur Teams können Software entwickeln, die intern auch als Team organisiert ist. Organisation und Produkt können nur co-evoluieren. Die Gestaltung von Level 2 ist insofern eine Konsequenz aus Conway´s Law. Asynchronizität und gar Verteilung können wir nur in den Blick nehmen, wenn wir uns auch organisatorisch mit autonomen Entitäten, den Entwicklern im Team auseinandersetzen. Aber dazu später mal mehr...

Jetzt werden wir erstmal School of .NET Level 1 in ein Angebot gießen. Soll die Innovation mal mit einem ersten Schritt beginnen...

Asynchronizität und Verteilung üben - Szenarien für verteilte Anwendungen

Wer was Neues lernen will, tut das am besten zunächst mit Übungen. Chirurgen lernen neue Techniken erst an toten und/oder nicht menschlichen Lebewesen, Piloten lernen im Simulator. Und ich will den neuen Application Space ausprobieren oder allgemeiner asynchrone und verteilte Architekturen üben. Was sind aber Übungsaufgaben, an denen ich mich versuchen kann? Einen asynchronen und verteilten Service aufzusetzen ist ja trivial. Von dem Service dann auch noch Notifikationen zu bekommen oder Pub/Sub einzurichten, das ist auch trivial. Jeweils für sich genommen sind diese Dinge einfach - aber wie füge ich diese Bausteine zu etwas Größerem, Realistisch(er)em zusammen? Erst in einem umfassenderen Szenarion, das nicht von der Technik ausgeht, sondern von "Kundenanforderungen" kann ich auch feststellen, was einer Technologie wie dem Application Space noch fehlen mag (oder wo sie besonders geeignet ist).

Hier möchte ich nun einige Szenarien zusammentragen, die mir als Übungen für Verteilung und Asynchronizität erscheinen. Sie sind mehr oder weniger komplex, aber immer irgendwie "zum Anfassen". Jedes bietet für die zu übende oder evaluierende Technologie eine andere Herausforderung. Ich werde sie mit dem Application Space implementieren, wer mag, kann aber natürlich WCF pur oder mit Azure oder Jabber pur oder MassTransit oder NServiceBus oder Rhino Service Bus oder MSMQ pur oder TCP Sockets pur oder noch ganz andere Technologien damit ausprobieren. Ganz im Sinne der School of .NET Diskussion sehe ich diese Szenarien auch als Chancen für ganzheitliches Lernen. Clean Code Development, Komponentenorientierung, .NET Framework Grundlagen, TDD... all das und mehr kann man auch einfließen lassen.

Szenario 1: Stammdatenverwaltung

Aller Anfang sollte einfach und typisch sein. Deshalb ist mein erstes Szenario eines, mit dem viele Entwickler immer wieder konfrontiert werden: die Stammdatenverwaltung oder "forms over data". Ein Anwender verwaltet mit seinem Client Daten in einer Datenbank mit den üblichen CRUD-Funktionen: Create, Read, Update, Delete. Zusätzlich kann er einen serverseitigen Datenimport anstoßen.

Ob sich für dieses Szenario eine Verteilung überhaupt lohnt, sei einmal dahingestellt. Allemal, wenn aus anderen Gründen eine Anwendung verteilt werden soll, muss auch die Stammdatenverwaltung auf eine solche Architektur abgebildet werden.

Um das Datenmodell einfach zu halten, reicht es aus, wenn das Szenario sich nur um Personen mit ihrer Adresse dreht.

Datenmodell:

  • Person(Nachname, Vorname, Straße, PLZ, Ort, Land, Tel, Soundex)

Featureliste:

  • Der Anwender kann nach Personen suchen; der Server liefert eine Liste von passenden Personen zurück.
  • Der Anwender kann eine gefundene Person bearbeiten und speichern.
  • Der Anwender kann eine neue Person anlegen.
  • Der Anwender kann eine Person löschen.
  • Der Anwender kann gefundene Adressen in eine CSV-Datei exportieren. Der Export kann clientseitig erfolgen.
  • Der Anwender kann den Import von Personen aus einer CSV-Datei veranlassen. Dazu muss er dem Server mitteilen, in welcher Datei die Daten liegen. Der Server importiert, meldet zwischendurch den Fortschritt und liefert am Ende ein Importresultat.
  • Dublettenprüfung: Jede Person soll nur einmal in der Datenbank stehen. Mehrere Sätze mit denselben Daten sind zu vermeiden, um bei Mailings nicht mehrere Briefe an dieselbe Person zu senden. Um den Vergleich von Personen zu vereinfachen, können sie mit einem Soundex-Wert ausgestattet werden. Wannimmer eine Person gespeichert werden soll (nach Bearbeitung, nach Neuanlage, beim Import) und schon mit einem anderen Datensatz in der Datenbank vertreten ist, wird die Operation verweigert und der Anwender informiert. Die Dublettenprüfung kann in Schritten implementiert werden:
    • Dubletten bei Neuanlage prüfen
    • Dubletten beim Import prüfen
    • Dubletten nach Bearbeitung prüfen

Klingt doch einfach, oder? Hat aber natürlich seine Tücken, denn es gilt ja, diese Funktionalität asynchron und verteilt zu realisieren. Wie kommunizieren Client und Server im Sinne einer solchen Stammdatenverwaltung miteinander?

image

Herausforderungen:

  • Wie werden asynchrone Operationen wie Speichern oder Import im UI repräsentiert?
  • Wie meldet der Server den Fortschritt beim Im/Export an den Client (Notifikationen)?

Szenario 2: Referentenfeedback (Heckle Service)

Christian Weyer hat ein schon älteres Szenario in seinem dotnetpro-Artikel "Schnuppern an Azure" (5/2009)mit den aktuellen Technologien neu implementiert. In der dotnetpro 7/2009 greife ich das auf und realisiere es mit dem Application Space.

Die Idee ist einfach: Zuschauer eines Vortrags auf einer Konferenz sollen dem Referenten live Feedback geben können. Sie sollen sozusagen elektronisch zwischenrufen können (engl. to heckle). Dazu hat jeder Teilnehmer einen Client, mit dem er kurze Textnachrichten an den Referenten senden kann, der sie in einem eigenen Frontend auflaufen sieht.

Datenmodell:

  • Nachricht(Absendername, Nachrichtentext, Eingangszeitstempel) - Jede Nachricht gehört natürlich zu einem Referenten. Ob das allerdings in der Nachricht vermerkt werden muss, soll hier nicht festgelegt werden.

Featureliste:

  • Teilnehmer senden Zwischenrufe an den Referenten.
  • Teilnehmer sehen sich die Liste der letzten n Zwischenrufe an.
  • Der Referent bekommt jeden Zwischenruf automatisch angezeigt.
  • Falls der Referent sein Frontend - aus welchen Gründen auch immer - neu startet, bekommt er die Liste aller bisher eingegangenen Zwischenrufe angezeigt.
  • Der Referent identifiziert sich irgendwie, so dass die Teilnehmer ihm und keinem anderen ihre Zwischenrufe senden. Die Teilnehmer müssen einen Referenten also beim Zwischenrufen adressieren. Potenziell kann die Heckle-Anwendung ja gleichzeitig in vielen Vorträgen zum Einsatz kommen.
  • Der Veranstalter der Vorträge kann die Zwischenruflisten aller Referenten jederzeit einsehen.

image

Herausforderungen:

  • Wie nehmen Teilnehmer mit dem Referenten Kontakt auf? Direkt, indem sie seinen Rechner adressieren oder indirekt via eines Discovery-Servers?
  • Wo werden die Nachrichten vorgehalten, damit vor allem Teilnehmer und Veranstalter sich jederzeit einen Überblick verschaffen können?
  • Wie wird insb. der Referent automatisch über neue Nachrichten informiert?

Szenario 3: Tic Tac Toe

Es ist zwar kein typisches Geschäftsanwendungsszenario, aber es macht Spaß: ein Spiel realisieren. Bei Tic Tac Toe (TTT) sind die Regeln simpel, so dass man sich auf die verteilte Implementation konzentrieren kann.

Zwei Spieler spielen gegeneinander auf einem TTT-Brett. Jeder sitzt an seinem PC und sieht den gemeinsamen Spielstand.

Datenmodell:

  • Spielfeld mit 3x3 Spielfeldern in den Zuständen O, X und leer. Zusätzlich sollte das Spielfeld noch einen Spielzustand haben wie Spiel begonnen, Spiel beendet, Gewinner ist Spieler 1, Gewinner ist Spieler 2.

Featureliste:

  • Ein Spieler bietet sich zum Spiel an.
  • Ein Spieler nimmt Kontakt mit einem anderen auf und sie beginnen eine Partie.
  • Spieler machen Züge.
  • Ob und welcher Spieler gewinnt, wird automatisch festgestellt.
  • Ein Spieler beendet eine Partie vorzeitig.

Dies ist während des Spiels natürlich ein Peer-to-Peer-Szenario. Ob intern die Rollen aber auch gleich sind oder nicht vielleicht doch ein Spieler ein Partienserver ist, hängt von der Implementation ab.

image

Herausforderungen:

  • Wie nehmen die Spieler Kontakt miteinander auf?
  • Wo wird der Partienzustand gehalten?
  • Wie erfahren die Spieler über den nächsten Zug?
  • Wie wird den Spielern das Spielende mitgeteilt?

Szenario 4: Starbucks

Gregor Hohpe hat in einem Blogbeitrag deutlich gemacht, wie wenig praktikabel die bisher so beliebten 2-Phase-Commit-Transaktionen in der realen Welt, d.h. in asynchronen (und verteilten) Szenarien sind. MassTransit und Rhino Service Bus haben das aufgenommen und versucht, mit ihren Mitteln das Szenario abzubilden. Es ist einfach eine schöne Fingerübung für jeden, der in die verteilte und asynchrone Programmierung einsteigen will.

Bei Starbucks kommen Kunde, Kassierer und Barista zusammen. Der Kunde bestellt ein Getränk, der Kassierer nennt den Preis und nimmt das Geld entgegen. Währenddessen bereitet der Barista schon das Getränk zu und serviert es, wenn die Zahlung geklappt hat.

Datenmodell:

  • Bestellung(Getränkeart, Bechergröße, Menge)
  • Zahlungsaufforderung(Gesamtpreis einer Bestellung)
  • Bezahlung(Betrag, Zahlmittel) - Zahlmittel könnten Barzahlung oder Kreditkarte sein

Featureliste:

  • Kunde bestellt ein Getränk beim Kassierer. Variation: Kunde bestellt mehrere und verschiedene Getränke beim Kassierer.
  • Kassierer nennt den Gesamtpreis
  • Kunden bezahlt
  • Kassierer nimmt Bezahlung entgegen und prüft den Betrag. Wenn ok, dann schließt er die Bestellung ab.
  • Barista bereitet bestellte Getränke vor.
  • Wenn Bezahlung abgeschlossen, stellt der Barista die Getränke zur Abholung bereit.

Der Kunde kann hier als interaktiver Client realisiert werden. Kassierer und Barista hingegen sind automatische Dienste. Um die reale Welt nachzustellen, können ihre Funktionen über Pausen (Thread.Sleep()) eine wahrnehmbare Dauer bekommen.

image

Herausforderungen:

  • Wie nehmen die Beteiligten Kontakt miteinander auf?
  • Wie wird der Dialog zwischen Kunde und Kassierer geführt?
  • Wie erfährt der Kunde über das fertiggestellte Getränk?
  • Was passiert mit einem schon zubereiteten Getränk, wenn die Zahlung nicht erfolgreich ist?

Szenario 5: Arbeitsteilung

Das MSDN Magazine Juni/2009 beschreibt in "A Peer-To-Peer Work Processing App With WCF" ein Szenario, dass sich auch zur Übung zu realisieren lohnt. Mehrere sog. Worker stehen da bereit, um Aufträge von sog. Usern anzunehmen. Der Artikel nutzt zur Arbeitsverteilung das P2P-Protokoll von WCF - aber man kann es auch anders machen.

Datenmodell:

  • Arbeitsauftrag(Id, Dauer)

Featureliste:

  • User vergeben Aufträge in einen Pool von Workern hinein.
  • Worker übernehmen einen oder mehrere Arbeitsaufträge.
  • Worker können dem Pool beitreten oder ihn verlassen.
  • Ob ein Worker Aufträge annimmt hängt von seiner Last ab. Die gesamte Auftragslast soll natürlich möglichst gleichmäßig auf die Worker verteilt werden.
  • Wie die Arbeit "so läuft" können sog. Spectators beobachten; sie dienen der Instrumentierung des Systems.

In dieser Featureliste fehlen einige Aspekte, die der Artikel beschreibt, z.B. "Work Item Backup" oder "Work Sharing". Ich habe sie nicht aufgenommen, weil sie mir abhängig scheinen vom gewählten Lösungsweg (hier: P2P-Kommunikation). Meine Szenarien sollen aber keine Lösungswege oder Technologien nahelegen (z.B. Einsatz eines Busses oder Sagas oder P2P-Kommunikation).

Die schlanken Aufträge habe ich ebenfalls deshalb übernommen. Es geht ja nicht um eine konkrete Problemdomäne. So müssen die Aufträge nur eine Dauer haben, um Lastverteilung beobachten zu können.

image

Herausforderungen:

  • Wie wird die Auftragslast möglichst gleichmäßig (oder "gerecht") auf die Worker verteilt?
  • Wie werden weitere Worker möglichst unmittelbar in die Auftragsbearbeitung einbezogen? Oder allgemeiner: Wie kann der Worker-Pool "atmen"?
  • Wie werden User über Auftragsergebnisse (oder gar Fortschritte) informiert?
  • Wie kann ein Spectator die Arbeit(sverteilung) beobachten?
  • Müssen Aufträge nach Vergabe noch "gesichert" werden können, falls Worker noch nicht zu ihrer Verarbeitung gekommen sind?
  • Wie können die Worker möglichst ortsunabhängig verfügbar gemacht werden?

Montag, 8. Juni 2009

School of .NET - Stakeholderverträgliches Lernen

Stefan Lieser hat ihn nun entworfen: einen Rahmen für neues Präsenzlernen. In 3 Blogeinträgen haben wir laut (oder besser: sichtbar und öffentlich) darüber nachgedacht, wie Lernen in Zeiten von Web 2.0 und einer wieder mal geplatzten Wunschblase aussehen könnte. Das übliche Kochrezept - nimm ein Thema, biete 2, 3, 4, 5 Tage Kurs an, fertig - schien uns nicht mehr so unzweifelhaft zeitgemäß. Es setzt einfach den Willen voraus, bei knapper Personaldecke wie heute üblich auf Entwickler über längere Zeit zu verzichten. Die Kompaktheit des Lernen mag zwar auch ihre Vorteile haben - doch die wahrzunehmen fühlen sich nicht alle, die es wollen, in der Lage. Also liegt der Gedanke nahe, Lernen mehr an die Bedürfnisse der Lernenden anzupassen.

Aufbauend auf dem Rahmen von Stefan konkretisiere ich deshalb mal eine School of .NET:

  • Semesterlänge: 3 Monate
  • 1 Tag Unterricht pro Woche
  • Semesterbeginn mit einer Blockveranstaltung von 2 Tagen zur Gruppenbildung - genannt Semester.Ctor ;-)
  • Semesterende mit einer Blockveranstaltung als intensiver Abschluss mit Retrospektive - genannt ~Semester oder Semester.Finalizer ;-)

Das sind dann zusammen 14 Unterrichtstage mit 11 Wochen zwischen den 12 Terminen. Zu den Präsenztagen kommen also noch ausgedehnte Gelegenheiten für Hausaufgaben. Die finde ich so wichtig wie Stefan, denn damit kann sich das Wissen setzen, das man in den Präsenztagen erwirbt. Im normalen Blockunterricht wird zuviel gestopft - und die Fragen kommen erst hinterher, wenn alle wieder auseinandergelaufen sind.

Wird der Unterricht aber über 3 Monate verteilt, kann das neue Wissen schrittweise schon in die Praxis überführt werden und wirft Fragen auf, die gleich am nächsten Unterrichtstag gestellt und diskutiert werden können. Die School of .NET ist damit viel praxisnäher als jedes übliche Training. Sie bietet sogar quasi während des Semesters Zugriff auf Consultants (die "Lehrer"), denn die "Schüler" können ja jederzeit Problemstellungen aus ihren Betrieben mitbringen.

Die Hausaufgaben involvieren die Teilnehmer auch viel mehr als jede Übung, die innerhalb des Unterrichts gemacht wird. Dann sind die nämlich auf sich allein gestellt. Insofern ist jedes berufsbegleitende Lernen letztlich tiefergehend als ein Blockunterricht.

Aber nicht nur für die Teilnehmer hat die School of .NET Vorteile. Auch Kunde und vor allem Chef können nur davon profitieren. Als Stakeholder wollen sie ja eigentlich beides: einen kompetenten Entwickler, der schon alles kann, und einen, der morgen auch noch mehr kann, also ständig lernt. Berufsbegleitend ist das möglich. 1 Tag in der Woche lernen die Entwickler und 4 bringen sie ihre Kompetenz im Projekt ein. Sie werden schlauer ohne störend lange abwesend zu sein. Zudem können sie - wie oben schon erwähnt - in den Unterricht Probleme aus dem Projekt einbringen. Auch der Unterricht bringt ihnen also etwas für das Projekt und sie lernen von anderen etwas über deren tägliche Probleme.

Lernt der Entwickler was, freut sich der Stakeholder ;-)

Ja, ich denke, ein solches Lernangebot werden Stefan und ich mal schneidern: School of .NET oder kurz SoN. Und dann bieten wir das in Hamburg und Köln mit regionalem Einzugsgebiet an. Da kann dann keiner, der lernwillig ist, mehr Nein sagen ;-) Der Unterrichtsort ist erreichbar und der Unterricht nimmt nicht zuviel Raum im Tagesgeschäft ein.

Solche Lerngelegenheit ist dann besonders interessant für alle, die um- oder aufsteigen. Dann geht es nämlich nicht darum, dass gleich morgen irgendwas gekonnt werden muss. Entwickler, die auf .NET umsteigen oder eine Brownfield-Anwendung uptodate bringen sollen, haben meist einen weiteren Zeithorizont. Da ist es klar, dass sie erstmal Erfahrung mit der Plattform sammeln müssen. Dito Neueinsteiger, die aus ihrer Erstausbildung kommen und noch nicht fit mit dem .NET Framework sind. Als Junior Programmer haben sie 1 oder 2 Jahre, um sich einzufuchsen. Da ist berufsbegleitendes Lernen ideal. Und auch wer heute fitte .NET Programmierer, aber am Markt nicht findet, der bekommt mit einer School of .NET eine Hilfestellung. Er kann sich eher auf einen "smart and getting things done" Entwickler einlassen, der noch kein .NET-Spezialist ist.

Ja, ich finde, das ist ein rundes Bild für eine School of .NET. Das machen wir, Stefan. Auch wenn das Format zu jedem Thema passt, fangen wir am besten mit .NET-Grundlagen und CCD an. Tüfeln wir mal ein Curriculum aus...

Donnerstag, 4. Juni 2009

Schulbank statt “Druckbetankung” – Berufsbegleitendes Lernen gerade jetzt

Auf mein Posting mit der Frage, wie Präsenzlernen anders, attraktiver aussehen könnte, hat Stefan Lieser geantwortet – und auch laut nachgedacht. Wir stimmen also in der Analyse überein. Doch was tun? Wohin kann es gehen mit dem Lernen?

Vielleicht lohnt es, einen Begriff, den er ins Spiel gebracht hat, weiter zu verfolgen: Schule. Für Stefan ist es wichtig, Lernen effektiver zu machen durch “Mischung” und durch “Lernen durch Lehren”. Anfänger und Fortgeschrittene lernen zusammen und voneinander. Je nachdem um welche Lerninhalte es geht, sind unterschiedliche Mischungen förderlich. Geht es um Fachkompetenz, dann mögen unterschiedliche Fortschrittsgrade zusammen lernen, geht es um soziale Kompetenz, dann vielleicht unterschiedliche soziale Gruppen oder behinderte Menschen mit gesunden.

Das ist schon mal ein guter Gedanke, finde ich. Die üblichen Kurse legen ja eher Wert auf Homogenität. Und da sie meist vollgepackt sind mit Stoff, wird der Austausch nicht gefördert und auch kein Raum zum Lernen durch Lehren geboten. Dann ist das Training zwar kurz (2-3 Tage) – das freut den Chef. Aber das Ergebnis ist, naja, oft nicht nachhaltig.

Damit haben die üblichen Kurse natürlich immer noch Zweck. Aber der ist womöglich ein anderer als der intendierte. Nützlich sind sie als Impulsgeber. Oder sie helfen zu motivieren. Einen Überblick können sie auch geben. Aber viel mehr ist nicht drin. Am Ende ist der Teilnehmer schnell wieder allein. Und dann oft ratlos. Wie war das doch gleich im Kurs? Da funktionierte es doch noch…

Mit dem Clean Code Developer Camp bemühen wir uns nun schon, das ein wenig besser zu machen. Gerade bei den 2 x 5 Tagen gibt es Raum für´s Lehren durch die Teilnehmer. Und eine Pause von 1-2 Wochen zwischen den Blöcken lässt Zeit zur Reflexion. In den nächsten Block kommen die Teilnehmer dann schon gleich mit Fragen aus der Praxis. Zusätzlich gibt es ein Diskussionsforum nur für die Teilnehmer, in dem sie jederzeit Fragen stellen können. Auch nach dem Kurs. Dort können sie sicher sein, dass man sie versteht, weil die Forenmitglieder durch dieselbe Erfahrung des Camps gegangen sind.

Aber wie gesagt: 5 Tage oder noch länger hören sich für manchen wie eine Drohung an. Den “Kickstart”, den so ein kompaktes Training bietet, kann und will nicht jeder bekommen.

Woher kommt denn überhaupt das “Training en bloc”? Wer will das? Es ist so weit verbreitet, dass es wie “gottgegeben” aussieht.

Einer, der es sich wünscht, ist sicher doch der potenzielle Teilnehmer. Der sucht “Erkenntis sofort”. Am besten alles in 1-2 Tagen. Leider funktioniert das nicht mit allen Themen. Und mit dem Lernen funktioniert es eh nicht so gut. Lernen ist Veränderung. Veränderung braucht Zeit. Doch erstmal zählt der Wunsch, die Nachfrage. Wenn die da ist, dann gibt es eben auch ein Angebot dazu.

Der andere, der sich en bloc Trainings wünscht, ist allerdings auch der Anbieter. Mit einem Blockangebot ist er mit größerer Reichweite attraktiv. Für 1, 2, 3 Tage reisen Teilnehmer auch mal von weiter her an. Das Training wird also mit weniger Mühe voll.

Die Parameterwerte des üblichen Lernens sind also: Zuhören (und ein wenig Übung), Homogenität der Lerngruppe, einmaliger Blockunterricht, Überregionalität.

Wenn ich nun auf der Suche nach neuen, effektiven und attraktiven Lernformen bin, um die nötigen Veränderungen in der Softwareentwicklung in die Betriebe zu bringen, wie wäre es, einfach mal diese Parameterwerte zu negieren? Sozusagen frei nach Nietzsche: Umwertung aller Werte.

Dann würde “modernes Lernen” so aussehen:

  • Viel Übung, viel selbst erarbeiten, viel selbst weitergeben – statt Zuhören;  also Aktivität statt Passivität. Das beschreibt für mich auch die Atmosphäre, von der Boas Enkler in einem Kommentar zu meinem vorherigen Posting spricht: eine Atmosphäre, “in der Fehler gemacht werden dürfen und dennoch jeder danach strebt sich zu verbessern.”
  • Heterogene Lerngruppe, in der die Interaktionen zwischen den Lernenden gefördert werden. Die Lerngruppe homogenisiert sich sozusagen selbst. Das schafft Vertrauen untereinander und erweitert den Horizont. Die Herausforderung an den Unterricht ist dann natürlich, dass er für jeden etwas bietet. Niemandem soll langweilig werden, niemand soll abgehängt werden. Jeder kann in seiner Geschwindigkeit lernen. Dazu sind auch Pufferzeiten nötig, denke ich, in denen Langsamere wieder etwas aufholen können.
  • Abschied vom Blockunterricht. Stattdessen findet der Unterricht in mehreren “Sitzungen” statt. Warum nicht jede Woche 1 Tag Unterricht? Damit wäre über längere Zeit auch die Forderung des CCD-Wertesystems nach 20% Fortbildungszeit erfüllt. Zwischen den Unterrichtstagen wäre dann Pause, so dass weiter mit Hausaufgaben gelernt werden könnte. So ergeben sich auch die eben erwähnten Pufferzeiten ganz natürlich. Zudem ergeben sich in diesen Pausen sicherlich Fragen aus der Praxis an den Lernstoff, so dass der Unterricht viel praxisbezogener sein kann.
  • Wenn Unterricht nicht mehr in Blöcken stattfindet, ist er quasi notwendig auch nicht mehr überregional. Denn wer würde schon über z.B. 5 Wochen jeweils 1 Tag von Berlin nach Frankfurt reisen, um dort an einem Kurs teilzunehmen? Wochenweises Lernen wäre also regional. Das hätte auch den Vorteil, dass sich im Unterricht Menschen treffen, die auch anschließend noch einen Kontakt halten könnten (z.B. durch Begegnungen in User Groups oder bei “Alumni-Veranstaltungen”).

Hm… irgendwie finde ich diese Vision charmant. Schulbank drücken statt “Druckbetankung”. Berufsbegleitendes Lernen. Jede Woche ein Lernhäppchen. Ich glaube, das hätte für alle Beteiligten Vorteile.

Mittwoch, 3. Juni 2009

Lernpräsenz in Zeiten der Krise – aber wie?

Na, schön, wenn alle von der Krise reden, dann frage ich mal, wie denn in Zeiten derselben gelernt werden soll? Oder ist Lernen in der Krise gar kein Thema. Ja, so mag es scheinen. Die Ausbildungsetats vieler Firmen sind dieser Tage sehr, sehr klein geworden. Das ist natürlich komplett kontraproduktiv. Genauso wie Entlassungen.

Die Krise kommt, die Leute gehen, das Wissen bleibt stehen. Hinterher ist man dann dümmer als Unternehmen.

Aber schon recht, so ganz ignorieren kann man die Krise auch nicht. Wie könnte also beides überein gebracht werden: das Arbeiten und das Lernen. Insbesondere, wenn es viel zu tun gibt beim Lernen.

Das Clean Code Developer (www.clean-code-developer.de) Wertesystem mit seinen Bausteinen ist da ein Modellfall, würde ich sagen. Da gibt es eine ganze Menge zu lernen. Nicht nur Technologie, wie es die meisten gewohnt sind, sondern vor allem neue Gewohnheiten, neues Denken stehen bei CCD auf dem Programm.

Eine erste Hilfestellung geben die Grade. Sie teilen die Bausteine in Häppchen ein, die man als Lernpillen besser schlucken kann. Wenn man dann aber mehr will, was dann? Wenn man schneller mit CCD starten will, was dann? Denn der selbstgeführte Weg durch die Bausteine – gerade wenn man ihn allein gehen will oder muss – ist steinig. Bau-steinig sozusagen ;-) Ob man WCF oder einen O/R Mapper richtig einsetzt, dazu bekommt man beim autodidaktischen Lernen recht gut Feedback von der Anwendung. Die funktioniert oder nicht, die ist schnell genug oder nicht.

imageBei CCD ist es aber anders. Das Feedback ist nicht so klar. Der Code gibt nur schlecht Auskunft darüber, ob er änderbar ist. Metriken könnten befragt werden – aber die sind oft recht pauschal. Am Ende kann eigentlich nur Erfahrung Feedback geben. Für die braucht es aber entweder viel Zeit, um sie selbst zu gewinnen – oder einen Partner.

Insofern liegt die Frage nahe, wie es mit einer CCD-Ausbildung aussieht. Kann man schneller zu CCD-Erfahrung und neuen CCD-Gewohnheiten kommen, mit Hilfe von außen. Klar. Das CCD Camp beweist es. Das Feedback der Teilnehmer war und ist immer noch einhellig positiv.

Allerdings: Einige Projekte der Teilnehmer begleite ich und sehe, wie schwer es ist, selbst das, was man im Verlauf von 2 x 5 Tagen gelernt hat, unter Projektdruck einzusetzen. Der Druck presst es schnell raus aus dem Projekt und die Einschätzungen in Bezug auf die angemessene Anwendung der Bausteine ist noch nicht sicher.

So frage ich mich denn, wie kann Lernen noch effektiver sein? Lernen in Präsenz anderer, also nicht rein online, durch Buch und autodidaktisch, ist schon eine gute und wichtige Sache. Die Krise macht Chefs demgegenüber aber sehr sensibel. Solche Lernen in Präsenz führt dazu, dass Mitarbeiter nicht am Arbeitsplatz sind. 2 x 5 Tage scheinen da schnell zuviel. Und das verstehe ich durchaus auch.

Ich meine allerdings, dass die Lösung nicht lauten sollte, auf Lernen in Präsenz zu verzichten. Die Herausforderung muss vielmehr darin liegen, es geeignet aufzubereiten. Erfolge stellen sich nur beim Präsenzlernen wirklich schnell ein. Herausforderung 1: Sie müssen dann nur noch nachhaltiger gemacht werden. Herausforderung 2: Sie müssen in diesen Zeiten ohne massive Abwesenheit vom Arbeitsplatz erreichbar sein. Aber wie?

Inhouse Schulungen halte ich für keine Lösung. Sie holen den Mitarbeiter auch vom Arbeitsplatz weg – aber nicht richtig. Da kann keine wirklich fokussierte Lernatmosphäre entstehen. Die meisten sind mit einem Ohr immer am Telefon.  Außerdem lernt man da nur mit den Kollegen. Der Blick über den Tellerrand ist gering und eingefahrene Verhältnisse behindern womöglich zusätzlich.

Also: Wie denn aber sonst? Ich bin überzeugt, dass es nötig und möglich ist, Lernen in Präsenz besser und attraktiver zu machen. Auch und gerade für all die, die sich in der Krise fühlen.