Auf diese Frage bin ich neulich bei einem Aufenthalt im Marriott Hotel in Zürich gestoßen. Dort fand ich nämlich dieses Angebot vor:
Dienstag, 7. Mai 2013
Mehr verdienen durch Experiment
Auf diese Frage bin ich neulich bei einem Aufenthalt im Marriott Hotel in Zürich gestoßen. Dort fand ich nämlich dieses Angebot vor:
Sonntag, 28. April 2013
Software fraktal - Funktionale Abhängigkeit entschärfen
Operationen
Operationenhierarchien
Problem #A: Grenzenloses Wachstum
Problem #B: Umfängliche Unit Tests auf jeder Ebene
Problem #C: Attrappen auf jeder Ebene
Integration
Trennung von Integration und Operation
Bereitstellungsaufwand wäre zu treiben (Problem #1) und es gäbe topologische Kopplung (Problem #2). Aber das grenzenlose Wachstum (Problem #3) sähe anders aus. Integrationsfunktionen mit vielen LOC wären viel, viel besser zu lesen als Operationen von gleicher Länge. Entstünden lange Integrationsfunktionen aber überhaupt? Nein. Die Möglichkeit zum grenzenlosen Wachstum gibt es zwar, doch da Integrationsfunktionen so übersichtlich sind, werden sie viel schneller refaktorisiert.
Wenn Sie in Erste_Seite_aufblättern() zwei Verantwortlichkeiten vermischt sehen (Widerspruch gegen SRP) oder meinen, da sei nicht alles auf dem selben Abstraktionsniveau (Widerspruch gegen SLA), dann klappen Sie einfach einen Teil weg in eine andere Integrationsfunktion:
Seite Erste_Seite_laden() {
var dateiname = Dateiname_von_Kommandozeile_holen();
Dadurch steigt zwar die Tiefe der Funktionshierarchie, doch das macht nichts. Jede Ebene für sich ist ja leicht verständlich. Sie brauchen nicht mal ein Werkzeug, das Ihnen Abhängigkeitsbäume zeichnet, da die Ihnen ja deutlich vor Augen stehen und nicht in "Logikrauschen" verborgen sind.
Auch die syntaktische (Problem #4) und semantische Kopplung (Problem #5) verlieren an Gewicht. Da Integrationen selbst keine Logik enthalten, haben Änderungen in Syntax oder Semantik keinen Einfluss auf die Integration selbst. Syntaktische Änderungen können durch Typinferenz womöglich verschluckt werden. Und wenn nicht, dann hilft wahrscheinlich ein kleiner Einschub zwischen den aufzurufenden Funktionen wie hier gezeigt.
Integrationen sind zwar grundsätzlich gerade an die Semantik der Funktionen gekoppelt, von denen sie abhängen. Sie sollen ja aus ganz konkreten Teilverantwortlichkeiten eine neue Summenverantwortlichkeit herstellen. Doch diese Kopplung ist loser, weil die nicht an eigener Logik hängt.
Soweit die Milderung der Probleme #1..#5 funktionaler Abhängigkeit durch das IOSP. Aber was ist mit den Problemen #A..#C der Operationenhierarchien?
ad Problem #A: Einzelne Integrationen können zwar wachsen, doch tendenziell tun sie das viel weniger als Operationen (s.o.).
Integrationshierarchien können auch wachsen und tun das durch Refaktorisierungen womöglich sogar mehr als Operationshierarchien. Doch das ist nicht schlimm. Jede Ebene ist einfach zu verstehen. Logik ist nicht vertikal und horizontal verschmiert. Sie steckt allein in den Blättern, den Operationen.
ad Problem #B: Integrationen haben von Hause aus eine sehr niedrige zyklomatische Komplexität. Es gibt nur wenige Pfade durch sie. Die kann man sehr einfach testen. Oft reicht ein einziger Test. Oder Sie lassen eine Integration auch mal ganz ohne Test.
Das meine ich ernst. Da Integrationen so leicht zu verstehen sind, kann ihre Korrektheit auch mal nur durch einen Code Review geprüft werden. Die Korrektheit besteht ja nur darin, dass eine Integration von den richtigen Funktionen abhängt und zweitens diese Funktionen in passender Weise für den Zweck der Integration "verdrahtet" sind. Ob das der Fall ist, ergibt sich oft durch Augenschein.
Allerdings sollten Sie automatisierte Tests der Integrationen auf oberster Ebene haben. Die prüfen den Gesamtzusammenhang. Solange der korrekt ist, sind auch darunter liegende Integrationen korrekt.
Und Sie decken natürlich Operationen mit automatisierten Tests ordentlich ab. Dort spielt die Musik der Logik, das ist nicht einfach zu verstehen, also müssen Tests helfen.
Mein Gefühl ist, dass das Testen von Funktionshierarchien, die dem IOSP folgen, deutlich einfacher ist. Allemal, da die Operationen ja nicht mehr voneinander abhängig sind. Sie sind durch das Principle of Mutual Oblivion (PoMO) entkoppelt.
ad Problem #C: Nicht zuletzt ist das Testen einfacher, weil der Bedarf an Einsätzen eines Mock Frameworks deutlich sinkt. Ich zum Beispiel benutze schon lange gar keinen mehr.
Attrappen sind hier und da noch nötig. Doch dann schnitze ich sie mir kurz für den konkreten Fall selbst. Wissen über einen Mock Framework vorzuhalten oder gar deren Entwicklung zu verfolgen, wäre viel umständlicher.
Warum sind Attrappen viel seltener nötig? Weil die vielen Tests von Operationen keine mehr brauchen. Sie sind ja in Bezug auf die Domäne ohne funktionale Abhängigkeit. Und weil Tests von Integrationen seltener sind.
Skalieren mit dem IOSP
Dass Sie Ihre Software nur mit einer Hierarchie von Integrationen realisieren können, an denen unten wie Weihnachtsbaumkugeln Operationen baumeln, glaube ich auch nicht. Aber ich denke, dies sollte die Grundstruktur sein:Operationen sind die Black Boxes an der Basis funktionaler Abhängigkeitshierarchie. Nur dort sollte sich Logik befinden. Hier gilt es ausführlich zu testen. Diese "Büchsen" wollen Sie so selten wie möglich öffnen. Besser ist es, auf einer Integrationsebene darüber simple Veränderungen anzubringen und neue "Büchsen" unten hinzuzufügen.
Für die ganze Struktur gilt das IOSP, für jeden Knoten darin das PoMO.
Wenn diese Grundstruktur jedoch wachsen soll, dann reicht es nicht, an der Basis in die Breite zu gehen und bei der Integration in die Höhe. Ich denke, dann muss Schachtelung dazukommen.
Black Boxes sind nur aus einem bestimmten Blickwinkel oder aus einer gewissen Entfernung undurchsichtig und quasi von beliebiger Struktur. Wenn eine Operation wächst, dann sollten Sie das ebenfalls nach dem IOSP tun.
Fazit
- Teile Funktionen klar in Operationen und Integrationen:
- Halte Funktionen, die Logik enthalten, frei von funktionalen Abhängigkeiten (Operationen).
- Sammle funktionale Abhängigkeiten in Funktionen ohne Logik (Integration).
Endnoten
[3] Wenn Sie bei solcher Übung auf Schwierigkeiten stoßen, Fallunterscheidungen in Operationen auszulagern, ohne die funktional abhängig zu machen, dann ist das selbstverständlich. Aber vertrauen Sie mir: auch das ist ein lösbares technisches Problem.
An dieser Stelle will ich nicht näher darauf eingehen. Doch als Stichwort sei Continuation Passing Style (CPS) genannt.
Donnerstag, 25. April 2013
Selbstorganisation persönlich definiert
Soziale Systeme
Selbstorganisation
- Soll mit Java oder .NET entwickelt werden? Wenn eine Entwicklergruppe darüber selbst entscheiden darf, dann ist sie in Bezug auf die Entwicklungsplattform selbstorganisiert.
- Soll agil vorgegangen werden? Wenn eine Entwicklergruppe darüber selbst entscheiden darf, dann ist sie in Bezug auf das Vorgehensmodell selbstorganisiert.
- Soll Peter der ScrumMaster sein und Klaus die CI einrichten? Wenn eine Entwicklergruppe darüber selbst entscheiden darf, dann ist sie in Bezug auf die Rollenbesetzung selbstorganisiert?
- Soll das Daily Standup von 10 Minuten auf 15 Minuten ausgedehnt werden? Wenn eine Entwicklergruppe darüber selbst entscheiden darf, dann ist sie in Bezug auf das Lernen im Rahmen ihres Vorgehensmodells selbstorganisiert.
- Soll jeden Dienstag von 15-19h eine gemeinschaftliche "Lernzeit" in der CCD School stattfinden, um die Weiterentwicklung in Bezug auf Clean Code und andere Aspekte nachhaltig zu betreiben? Wenn eine Entwicklergruppe darüber selbst entscheiden darf, dann ist sie in Bezug auf ihre Zeiteinteilung und das Budget selbstorganisiert.
- Arbeitsinhalt
- Arbeitsort
- Arbeitszeit
- Arbeitsmittel
- Rollenverteilung
Fazit
Endnoten
Mittwoch, 24. April 2013
Raus aus der funktionalen Abhängigkeit
Wir sollten einfach keine funktionalen Abhängigkeiten mehr eingehen.
Funktion c() wie Client sollte sich nicht mehr an eine Funktion s() wie Service binden. Denn wenn sie das tut, wenn sie nicht mehr delegiert, dann kann sie sich endlich selbstbestimmt auf ihre einzige Domänenverantwortlichkeit im Sinne des Single Responsibility Principle (SRP) konzentrieren.
Technisch bieten Funktionen dafür die besten Voraussetzungen. Die Funktion
int Add(int a, int b) { return a+b; }
macht ja schon nur ihr Ding. Sie definiert sich komplett selbst. Ihre syntaktische Spezifikation besteht nur aus einer Signatur:
Func<int,int,int>
Die könnte man mit einer Beschreibung der gewünschten Semantik ("Die Funktion soll zwei Zahlen addieren.") zur Implementation in ein fernes Land geben. Die Entwickler dort müssten nichts weiter berücksichtigen.
Anders ist das bei einer Funktion wie dieser:
string ReportGenerieren(string dateiname) {
string report = "...";
...
int[] daten = LadeDaten(dateiname);
foreach(int d in daten) {
...
report += ...;
...
}
...
return report;
}
Deren Spezifikation besteht nicht nur aus:
- Func<string,string>
- "Funktion, die den Report wie folgt generiert: ..."
- Func<string,int[]>
- "Funktion, die die soundso Daten in folgender Weise als int-Array liefert: ..."
Aufbrechen funktionaler Abhängigkeiten
Also, wie kommen wir raus aus funktionaler Abhängigkeit? Wir müssen zunächst genauer hinschauen. Funktionale Abhängigkeit besteht aus drei Phasen:- Vorbereitung
- Aufruf
- Nachbereitung
int c(int a) {
// vorbereitung
int x = ... a ...;
// aufruf
int y = s(x);
// nachbereitung
int z = ... y ...;
return ... z ...;
}
Jede abhängige Funktion kann man mindestens einmal in diese Phasen zerlegen. Bei mehreren funktionalen Abhängigkeiten können sich Phasen natürlich auch überlagern. Dann mag eine Nachbereitung die Vorbereitung eines weiteren Aufrufs sein.
Sind diese Phasen aber erkannt, kann man jede in eine eigene Funktion verpacken:
int c_vorbereitung(int a) {
return ... a ...;
}
int c_nachbereitung(int y) {
int z = ... y ...;
return ... z ...;
}
Die bisherige abhängige Funktion wird dann zu einer Folge von unabhängigen Funktionen. Die können so zum bisherigen Ganzen zusammengeschaltet werden:
return c_nachbereitung(s(c_vorbereitung(42)));
Das ist jedoch schlecht lesbar, wenn auch vielleicht schön knapp formuliert. Besser finde ich es daher wie folgt:
int x = c_vorbereitung(42);
int y = s(x);
return c_nachbereitung(y);
Oder moderner könnte es in F# auch so aussehen:
42 |> c_vorbereitung |> s |> c_nachbereitung
Da ist doch sonnenklar, wie die Verantwortlichkeit, für die das ursprüngliche c() stand, erfüllt wird, oder? Das kann man lesen wie einen Text: von links nach rechts und von oben nach unten.
Bitte lassen Sie es sich auf der Zunge zergehen: Keine der Funktionen hat nun noch eine funktionale Abhängigkeit mehr. [1]
c_vorbereitung() ruft keine Funktion auf und weiß nichts von s() und c_nachbereitung(). Dito für s() und c_nachbereitung(). Keine Funktion weiß von einer anderen. Das nenne ich "gegenseitige Nichtbeachtung" (engl. mutual oblivion). Und wenn man sich so bemüht zu codieren, dann folgt man dem Principle of Mutual Oblivion (PoMO).
Funktionen sind ein Mittel, um Funktionseinheiten so zu beschreiben, dass sich nicht um Vorher oder Nachher scheren. Es gibt aber auch andere Mittel. Methoden können mit Continuations versehen werden oder Klassen können mit Events ausgestattet werden.
Egal wie das Mittel aber aussieht, die Funktionseinheit beschreibt immer nur sich selbst. Ihr Interface/ihre Signatur ist rein selbstbestimmt. Bei Funktionen fällt das gar nicht so sehr auf. Deren Ergebnisse sind sozusagen anonym. Anders sieht das aus, wenn Continuations ins Spiel kommen:
void c_vorbereitung(int a, Func<int,int> ???) {
???(... a ...);
}
Wie sollte die Continuation ??? heißen? Sollte sie vielleicht s genannt werden? Nein! Dann würde c_vorbereitung() wieder eine semantische Abhängigkeit eingehen. Die Prozedur würde eine bestimmte Dienstleistung erwarten, die ihr übergeben wird.
??? muss vielmehr rein auf die Prozedur bezogen sein. Die Continuation könnte neutral result oder continueWith betitelt sein. Oder sie könnte sich auf den produzierten Wert beziehen:
void c_vorbereitung(int a, Func<int,int> on_x) {
on_x(... a ...);
}
Einen Vorteil hat eine Continuation gegenüber einem Funktionsergebnis sogar: damit können beliebig viele Ergebnisse zurückgeliefert werden, ohne eine Datenstruktur aufbauen zu müssen.
Vorteile der Vermeidung funktionaler Abhängigkeiten
ad Problem #1: Es gibt keine Abhängigkeit, also muss auch keine Bereitstellung erfolgen. Die Frage nach statischer oder dynamischer Abhängigkeit stellt sich nicht einmal. Um c_vorbereitung() oder c_nachbereitung() zu testen, sind keine Attrappen nötig. Von Inversion of Control (IoC) oder Dependency Injection (DI) ganz zu schweigen.
ad Problem #2: Es gibt keine Abhängigkeit, also gibt es auch keine topologische Kopplung. Ob s() auf dem einen oder auf dem anderen Interface definiert ist, interessiert c_vorbereitung() und c_nachbereitung() nicht.
ad Problem #3: Das Wachstum von Funktionen wird ganz natürlich begrenzt. Wann immer eine Funktion eine Dienstleistung benötigt, muss sie beendet werden. Sie bereitet sozusagen nur vor. Für die Nachbereitung ist eine weitere Funktion zuständig. Überlegen Sie, wie lang Funktionen dann noch werden, wenn sie nur noch von einer Delegation bis zur nächsten reichen dürfen. Ich würde sagen, dass es kaum mehr als 10-20 Zeilen sein können, die Sie guten Herzens ohne Verletzung eines anderen Prinzips anhäufen können.
ad Problem #4: Ohne funktionale Abhängigkeit gibt es zumindest keine direkte syntaktische Abhängigkeit. Wenn sich die Form des Output von s() ändert, muss c_nachbereitung() nicht zwangsläufig nachgeführt werden. Es könnte auch eine Transformation zwischen s() und c_nachbereitung() eingeschoben werden:
...
Tuple
int y1 = t.Item1;
return c_nachbereitung(y1);
Das mag nicht immer möglich sein, doch zumindest sinkt das Risiko, dass sich Änderungen an s() ausbreiten.
Dito für eine Änderung der Form der Parameter von s(). Auch hier könnte eine Transformation zwischen c_vorbereitung() und s() die Veränderung abpuffern.
ad Problem #5: Semantische Änderungen, also logische Kopplungen, haben am ehesten weiterhin eine Auswirkung auf Vorbereitung bzw. Nachbereitung. Doch auch hier ist Pufferung möglich, z.B.
Vorher:
int[] sortierteDaten = s(...);
return c_nachbereitung(sortierteDaten);
Nachher:
int[] unsortierteDaten = s(...);
int[] sortierteDaten = unsortierteDaten.Sort();
return c_nachbereitung(sortierteDaten);
Fazit
Das mag hier und da (zunächst) mühsam sein - doch der Gewinn ist groß. Und wenn man ein bisschen Übung darin hat, dann kann man quasi nicht mehr anders denken. Glauben Sie mir :-) Ich mache das jetzt seit vier Jahren so und es geht inzwischen mühelos.
Sollten funktionale Abhängigkeiten immer aufgelöst werden? Nein. Das wäre Quatsch. Man würde auch in einen infiniten Regress hineinlaufen. Die Frage ist also, wann auflösen und wann nicht? Doch davon ein anderes Mal.
Für heute ist mir nur wichtig, eine Lösung der aufgeworfenen Probleme aufgezeigt zu haben. Die sind aber natürlich gegen andere Werte abzuwägen. Evolvierbarkeit als eine Form von Flexibilität hat ja immer den Gegenspieler Effizienz.
Endnoten
Sonntag, 21. April 2013
Warnung vor der funktionalen Abhängigkeit
Funktionale Abhängigkeit
- eine Funktion c() wie Client eine Funktion s() wie Service aufruft und
- c() selbst die Parameter für s() durch Berechnungen vorbereitet und
- c() selbst das Ergebnis von s() selbst in Berechnungen weiterverarbeitet.
Problem #1: Bereitstellungsaufwand
Problem #2: Topologische Kopplung
Problem #3: Grenzenloses Wachstum
Problem #4: Syntaktische Kopplung
Problem #5: Semantische Kopplung
Fazit
Mittwoch, 3. April 2013
Clean Code Development anders lernen
Ich hatte einen Traum. Den erfülle ich mir nun. Zusammen mit Stefan Lieser eröffne ich die Clean Code Developer School (CCDS):
Mein Traum war schon vor Jahren, Softwareentwicklung entspannter und nachhaltiger für die Teilnehmer zu unterrichten. Zunächst hatten Stefan und ich das mit den CCD Camps versucht, deren Trainings bis zu 14 Tage gedauert haben.
Da hat uns dann aber die Realität schnell eingeholt. So lange mögen Unternehmen ihre Mitarbeiter nicht “von der Leine lassen”. Auch die Aufteilung in kleinere Blöcke mit Pausen dazwischen hat die Attraktivität solch soliden, ausführlichen Lernens nicht erhöht.
Die Konsequenz: Inzwischen sind sind runter auf die üblichen 2, 3 oder maximal 5 Tage Trainingseinheiten. Wir versuchen, die Clean Code Developer Inhalte so zu schneiden, dass Teilnehmer in so kurzer Zeit wirklich etwas mitnehmen.
Leider zeigt sich, dass schon 5 Tage am Stück zu lernen, eine starke Belastung für die Aufmerksamkeitsspanne ist. Ab Tag 4 ist deutlich zu spüren, wie die Luft bei den Teilnehmern ausgeht. Der Lernstoff muss einfach mal sacken, statt unter weiterem begraben zu werden.
Doch die übliche Ökonomie von Trainings drängt darauf, soviel wie möglich in kurze Trainings zu stecken.
Darauf habe ich jetzt aber keine Lust mehr. Und Stefan Lieser auch nicht.
Wir sehen einfach, dass oft das Geld für die üblichen Trainings rausgeschmissen ist. Zuviel Lernstoff in zu kurzer Zeit führt dazu, dass am Ende nichts richtig geübt wurde. Aber vor allem fallen die Teilnehmer nach dem Training in ein Loch. Zurück am Arbeitsplatz fehlt ihnen die Zeit und die Anleitung, um den Stoff weiter zu üben und dann auf ihre Situation zu transferieren.
Das ist für uns als engagierte Trainer frustrierend zu sehen.
Deshalb machen wir es jetzt anders.
Einstweilen geben wir unsere üblichen Trainings zum Beispiel im Rahmen der Clean Code Developer Akademie noch nicht auf. Aber zumindest setzen wir einen Fuß in eine neue Richtung mit der Clean Code Developer School.
Dort findet Lernen nämlich nicht in tagelange Kursen ab und an im Jahr statt, sondern jede Woche in Blöcken zu 4 Stunden.
Wir meinen das mit dem “regelmäßig lernen” ernst. Ohne geht es einfach nicht. Nur findet es am Arbeitsplatz nicht statt. Deshalb machen wir mal ein ganz anderes, quasi ein unwiderstehliches Angebot. Wir kommen mit der CCDS den Lerninteressierten im doppelten Sinn so weit entgegen, dass kontinuierliches Lernen machbar wird.
Natürlich sind wir uns bewusst, dass ein solches Angebot schlechter skaliert als übliche Trainings. Ob und wie es sich für uns rechnen wird, müssen wir sehen. Aber egal. Wir starten das Experiment, weil wir das Richtige im Sinne eines nachhaltigen Lernens für die Teilnehmer tun wollen.
Details zum Was, Wie, Wo, Wann finden Sie auf der Seite der Clean Code Developer School. Hier will ich nicht mehr verraten ;-)
Falls Sie in der Nähe eines Schulungsortes der CCDS sein sollten, schauen Sie doch zumindest mal für einen Schnupperunterricht rein.
Wenn ich Sie für diese Idee begeistern konnte, dann folgen Sie der CCDS doch ab jetzt über ihren RSS-Feed oder bei Twitter: @ccd_school.
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.




