Follow my new blog

Freitag, 3. Oktober 2014

Regelmäßiges Lernen - Meine Retrospektive

Ende Juni 2014 hatte ich versprochen, ich würde nun auch noch expliziter das Lernen in meine Arbeitszeit einbauen. Zwar besteht mein Job als Berater, Trainer, Autor zu einem großen Teil ohnehin aus Lernen, doch das hat eine andere Qualität als das, was ich meinen Seminarteilnehmern und Kunden nahelege. Mein Job ist Lernen, deren Job ist es nicht.1

Wenn ich Softwareentwickler empfehle, in der Arbeitszeit zu lernen, dann bürde ich ihnen scheinbar eine extra Aufgabe auf. Das war bei meinem Lernen bisher nicht der Fall gewesen. Deshalb wollte ich mein Lernen noch expliziter machen; es sollte auch für mich eine zusätzliche Aufgabe im Tagesgeschäft werden, damit ich einmal fühle, wie es meinen Kunden geht.

Mein Commitment war, dass ich wöchentlich während der Arbeitszeit 2+ Stunden mich auf diese Aufgaben konzentriere:

  • Französisch lernen
  • Bücher zu Sachthemen außerhalb normaler Lernthemen lese
  • Meditiere, d.h. "Ruhe und Fokus lernen"

Und ich hatte versprochen, über meine Erfahrung mit solchem extra Lernen nach drei Monaten zu berichten. Hier nun meine Beobachtungen:

image

Das sind zwei Auszüge aus meinem Lernprotokoll, das ich mit Lift geführt habe.

Sie sehen, lückenlos ist das nicht. Meine erste Beobachtung also: Es ist nicht leicht. Es ist nicht leicht, an jedem Arbeitstag die 15-30 Minuten aufzuwänden. Irgendetwas anderes schien oft dringender und so war das Lernen dann verschoben auf später und dann auf den nächsten Tag.2

Gelegentliche "Planübererfüllung" hat das zum Teil wett gemacht, ist langfristig aber keine erfolgversprechende Strategie, würde ich sagen. Das ist wie mit dem Refactoring: wenn man es länger und länger nicht tut, dann ist der Berg am Ende so groß, dass man es auch nicht nachholen kann.

Außerdem ist der mentale "Umschaltaufwand" gerade beim Französich Lernen für mich teilweise so hoch, dass in der kurzen Zeit die echte Lernaufmerksamkeit dann noch kürzer ist. Das fühlt sich ineffizient an.

Die Lehre, die ich daraus ziehe: Extra Lernen macht in so kleinen Tageshappen nur sehr bedingt Sinn. Besser scheint mir ein wöchentlicher Lernblock von 2+ Stunden.

Manchmal habe ich mir aber auch selbst ein Bein gestellt. Die beste Zeit für Französisch und Sachbuch war der Vormittag - ebenso jedoch auch fürs Schreiben. Welcher Tätigkeit dann den Vorzug geben? Die Entscheidung fiel mir leichter fürs Schreiben.

Die nächste Lehre daher: Der Zeitpunkt für Lernen will gut gewählt sein. Und wenn er einmal gewählt ist, sollte die Entscheidung nicht immer wieder neu getroffen werden müssen. Da geht Kraft verloren. Da zieht Lernen allzu schnell den Kürzeren. Vertagen scheint so einfach, so schmerzfrei. Doch in Wirklichkeit wird nicht vertagt, sondern gestrichen.

Aber auch von solchen Äußerlichkeiten unabhängig fiel mir das Lernen nicht immer leicht. Ich habe Wellen der Motivation gespürt. Manchmal klappte es besser, dann war ich für den nächsten Tag motivierter; manchmal klappe es nicht so gut, dann hatte ich am nächsten Tag nicht soviel Lust, mich weiterem Frust auszusetzen.

Solche ups and downs lassen sich wohl nicht vermeiden. Aber ich denke, man kann sie mildern. Das ist wie beim Fitnesstraining. Die Lust am Morgen (oder Abend) sich aufzumachen, ist unterschiedlich - da hilft es, wenn jemand auf einen wartet.

Ich habe (wieder einmal) übers Lernen gelernt: Lernen macht mehr Spaß und funktioniert verlässlicher, wenn man es nicht allein machen muss. Das Mindeste ist ein Accountability Partner, d.h. eine Person, der man direkt in die Augen blicken und gegenüber Rechenschaft ablegen muss. Eine Person, die den Lerner zieht. Die ihn an seinen Vorsatz erinnert, ihn fordert (aber auch durchaus fördert).

Damit meine ich nicht unbedingt einen Lehrer, aber der kann es natürlich auch sein.

Ein Mitlerner ist genauso gut. Oder einfach nur jemand, der eben zieht und sonst nichts.

Lernen in Teams sollte deshalb nicht Sache der Einzelnen sein, sondern der Gruppe. Gemeinsam Lernzeit einrichten, gemeinsam lernen. Das hilft ungemein.

Unterschätzt habe ich am Ende allerdings vor allem einen Aspekt: Relevanz. Nach einer initialen Phase hoher Motivation bin ich beim Französisch Lernen in den Morast gekommen. Es ging nur zäh voran. Nicht, weil es besonders schwierig gewesen wäre. Es lag vielmehr an einer fehlenden Bedeutung des Französischen für mein sonstiges Leben.

Ich mag Französisch. Ich würde es gern lesen und auch sprechen können. Doch am Ende ist das nicht jeden Tag wieder genug Antrieb gewesen, um dauerhaft dabei ins Lernen zu kommen. Anders wäre es wahrscheinlich gewesen, hätte ich schon mehr Vokabular drauf gehabt und mit dem Lesen von spannenderer Lektüre anfangen können. So aber war dieses Thema sehr abstrakt.

Das bedeutet: Lernen funktioniert umso besser, je relevanter es für das sonstige Leben (hier: Arbeitsalltag) ist. Je kleiner der Sprung vom Lernen ins Tagesgeschäft, desto besser. Das muss nicht bedeuten, dass man alles anwenden kann oder Großartiges leisten muss. Aber immer wieder sollte im Arbeitsalltag etwas ein Stückchen leichter fallen, besser werden.

Das habe ich bei der Lernaufgabe "Sachbuch lesen" positiv gemerkt. Dort habe häufiger Impulse für die Arbeit bekommen.

Diese Retrospektive zu meinem Lern-Commitment mag ernüchternd klingen. Es hat nicht so einfach geklappt, wie gedacht. Auch die Öffentlichkeit, in die ich mich mit dem Commitment begeben hatte, hat daran nicht viel geändert.

Doch ich sehe es positiv: Immerhin habe ich dabei etwas gelernt. Auf der Meta-Ebene. Für mich selbst, für meine Kunden oder für Sie braucht es neben der Erkenntnis, dass Lernen wichtig ist, und dem guten Willen einfach noch ein paar Rahmenbedindungen:

  1. Wenn es um echten Lernstoff geht, dann besser jede Woche eine längere Lernzeit einplanen (2+ Stunden).3
  2. Einen Lernpartner oder zumindest einen Accountability Partner suchen.
  3. Immer wieder probieren, den Lernstoff im Alltag anzuwenden. Sozusagen inkrementelles Lernen à la Agilität, d.h. es entsteht sofort Nutzen.

Das mögen jetzt keine weltbewegenden Einsichten sein. Irgendwie klingt das doch selbstverständlich. Und doch ist es nochmal etwas anderes, diese Einsichten durch Erfahrung gewonnen zu haben.

Angesichts der Wichtigkeit des Lernens und auch des Lernens während der Arbeitszeit, in sich immer wieder etwas anderes dazwischen drängt, ist jeder Gewinn an Klarheit jedoch ein Fortschritt, finde ich. Mit dem falschen Fuß loszugehen, sich Illusionen hinzugeben, enttäuscht nur. Besser ist es, von Anfang an ein realistisches Rahmenwerk aufzubauen.

In diesem Sinn ist auch die CCD School gedacht. Sie bietet nicht nur Input fürs Lernen, sondern auch eine Form der Begleitung, der Accountability Partnerschaft. Offline geht das im Monatsrhythmus, online auch häufiger.

Zu guter Letzt aber doch noch ein Trost: Ich habe auch festgestellt, dass sich manches verselbstständigt. Ich mag mich schließlich nicht so regelmäßig und dauerhaft zur Meditation hingesetzt haben, wie geplant - doch das, was ich damit erreichen wollte, hat sich auf anderem Wege in mein Leben eingeschlichen. Die Fokussierung und das Konzentrieren auf "Innen" finden inzwischen "einfach so" immer wieder statt.

Manchmal wird aus extra Lernen also neue Gewohnheit. Dann macht das Thema keine Last mehr, sondern ist Alltag, gar Lust.


  1. Ob das wirklich eine günstige Sichtweise ist, die Arbeit von Softwareentwickler als nicht (oder nur wenig) lernend anzusehen, lasse ich hier einmal dahingestellt.

  2. Dazu kam, dass ich nicht jeden Tag meine Arbeitszeit einteilen konnte, wie ich wollte, weil ich in Trainings- und Beratungen beim Kunden war.

  3. Anders ist es mit Gewohnheiten oder einzelnen Handlungen. Meditation lässt sich nicht auf einmal pro Woche konzentrieren. Genauso wenig eine tägliche Reflexion, wie sie der Rote Grad des Clean Code Development empfiehlt.

Donnerstag, 2. Oktober 2014

Responsibilities zählen

Das Single Responsibility Principle (SRP) ist eine Säule sauberer Softwareentwicklung für hohe Wandelbarkeit. Nicht umsonst macht es auch den Anfang bei den SOLID Prinzipien, würde ich sagen.

Aber: Wie finden Sie denn heraus, wieviele Responsibilities (Verantwortlichkeiten) eine Methode oder Klasse hat? "Naja, das sieht man halt", scheint mir ein zu schwammiges Kriterium für ein so zentrales Prinzip.

Was ist eine Verantwortlichkeit?

Bevor es mit dem Zählen losgehen kann, muss natürlich klar sein, was da überhaupt entdeckt und gezählt wird. Was ist eine Verantwortlichkeit?

Die Literatur spricht von "only one reason to change". Das finde ich leider nicht wirklich hilfreich. Denn um welche Gründe geht es, worauf beziehen die sich?

Ich versuche es daher einmal so:

Jede Methode soll nur Anweisungen enthalten, die Anforderungen eines Aspekts erfüllen.

Diese Definition ist knackiger, erfordert jedoch die Klärung zweier Begriffe:

  • Aspekt: Ein Aspekt ist eine Menge von zusammengehörigen Merkmalen, die sich unabhängig von anderen Merkmalen ändern können. Man könnte einen Aspekt auch eine Dimension nennen. Beispiele für Aspekte in der realen Welt sind z.B. Frisur, Kleidung, Bildung. Sie können die Merkmale Ihrer Frisur (Haarfarbe, Haarlänge, Schnitt) unabhängig von Merkmalen Ihrer Kleidung (Stoff, Jahreszeitlichkeit, Stil) oder Bildung (Dauer, Inhalt, Ort) ändern.
  • Anweisung: Anweisungen sind Sprachkonstrukte, die definieren, was eine Software tun soll. Sie fallen für mich in zwei Kategorien: Logik und Integration.
    • Logik: Mit Logik bezeichne ich die essenziellen Anweisung von Programmiersprachen, die der Erfüllung von funktionalen wie qualitativen Anforderungen dienen. Das sind Operatoren, die Daten transformieren, Kontrollstrukturen (z.B. if, while), die den Verarbeitungsfortgang steuern, und Hardwarezugriffe (vermittels API-Aufrufen). Logik beschreibt Algorithmen.
    • Integration: Integration bindet mehrere Methoden zu einem Datenfluss zusammen.

Ich denke, jetzt wird auch verständlicher, was "only one reason to change" bedeutet: Methoden sollen nur geändert werden müssen, wenn sich an einem Anforderungsaspekt etwas verändert hat.

Methoden sind die kleinsten Container von Programmiersprachen. Darüber liegen für mich in wachsender Größe Klassen, Bibliothekn, Komponenten und µServices. Für die muss das SRP natürlich auch gelten. Also lautet es ganz allgemein:

Jeder Container soll nur Anweisungen enthalten, die Anforderungen eines Aspekts erfüllen.

So zumindest das Ideal. In der Praxis kann und muss sogar davon temporär oder in überschaubarem Maße abgewichten werden. Ziel sollte jedoch ein einziger Aspekt pro Container sein.

Außerdem ist zu bedenken, dass Aspekte in Hierarchien existieren. Zum Aspekt der Kleidung mögen die Merkmale Stoff, Jahreszeitlichkeit und Stil gehören. Nur zum Stoff z.B. gehören dann jedoch weitere Sub-Aspekte wie Farbe, Material, Haptik. Kleidung kann aus grobem roten Leinen oder feiner grüner Seide oder rauer gelber Wolle oder feinem gelbem Leinen usw. bestehen.

Ein Container auf höherer Abstraktionsebene (z.B. Klasse) kann dann für einen Dachaspekt stehen, der Container von Sub-Aspekten (z.B. Funktionen) zusammenfasst.

Härtegrade

Für mich gibt es Aspekte in unterschiedlichen Härtegraden. Hart sind Aspekte, die man an der Form erkennt. Man muss die Anforderungen nicht verstehen, die Anweisungen erfüllen, sondern nur die Programmiersprache/Plattform.

Beispiele hierfür sind die Anweisungsaspekte Logik und Integration sowie der Aspekt Datenstruktur.

Innerhalb der Logik können dann jedoch weitere verschiedene Hardwarezugriffe unterschieden werden, z.B. Tastatureingabe, Bildschirmausgabe, Dateisystemzugriff, Datenbankzugriff. Und sogar den Zugriff auf den Heap würde ich dazurechnen, also den Umgang mit Hauptspeicher. Dazu ist zwar kein spezieller API nötig, doch Zugriffe aus globale Daten (statische Felder oder Felder von Objektinstanzen) sind klar erkennbar.

Weich hingegen sind Aspekte, bei denen man verstehen muss, worum es geht. Es geht um das Was, wohingegen harte Aspekte das Wie betreffen.

Schon die Unterscheidung zwischen lesender und schreibender Logik setzt Interpretation voraus. Es ist daher kein Wunder, dass bei weichen Aspekten schnell Diskussionen entstehen. Der eine empfindet Lesen und Schreiben als Merkmale des selben Aspekts, der andere empfindet sie als getrennt - was sich dann jeweils in unterschiedlicher Aufteilung in Container ausdrückt.

Auch hier wieder eine Hierarchie: Lesen und Schreiben sind z.B. Sub-Aspekte von Objektpersistenz (zu der auch z.B. Serialisierung gehört). Und Objektpersistenz ist ein weicher Aspekt von z.B. Personalisierung. Die wiederum ein Aspekt des Anforderungsaspektes Usability ist - welche zur Anforderungskategorie Qualität gehört.

Und was folgt aus der Unterscheidung zwischen harten und weichen Aspekten? Halten Sie sich so lange wie möglich bei der Strukturierung von Code an harte Aspekte. Darüber gibt es viel weniger Diskussion. Das macht Ihre Codierung schneller, das macht Reviews konfliktfreier.

Am Ende jedoch können Sie natürlich den weichen Aspekten nicht ausweichen. Üben Sie also immer wieder Ihre Sensibilität in der Unterscheidung und Zusammenfassung von weichen, inhaltlichen Merkmalen.

Apropos Üben...

Angewandte Aspekterkennung

Zum Abschluss ein Codebeispiel, an dem ich Ihnen die Identifikation von Aspekten praktisch demonstrieren möchte. Ich entnehme es dem Buch "Head First C#".

image

Das Szenario? Versuchen Sie es doch einmal dem Code zu entnehmen. Wie Sie feststellen werden, ist das jedoch schwierig. Weil er nicht integriert und kein Titel sichtbar ist. Die Methode Main() enthält ausschließlich Logik. Und die hat ihrer Natur nach denkbar wenig Dokumentationsqualität. Reine Logik muss immer entziffert werden. Hobbyarchäologen sind klar im Vorteil ;-)

Aber ich verrate Ihnen das Szenario: Es handelt sich um eine Anwendung zur Darstellung von Dateiinhalten in hexadezimaler Form.

Hier nun der von mir mit Aspekten kommentierte Quellcode:

image

Das Kapitel im Buch heißt "Dateien lesen und schreiben" - aber wie das geht, muss der Leser sich mühsam im ganzen Code zusammensuchen. Nicht nur wie das Problem ganz allgemein gelöst wird, wie der Prozess aussieht, der das gewünschte Verhalten herstellt, ist also unklar. Es wird auch dem technologisch Interessierten schwer gemacht, sich zu informieren.

Das ist Bullshit. Das ist dirty code par excellance. Niemandem ist mit soetwas gedient. Und das in deinem Lehrbuch...! Erschreckend.

Ich habe vier Aspekte identifiziert, die über den Code verstreut und auch noch geschachtelt sind. Das ist das Gegenteil von Entkopplung.

Die Domäne ist die Formatierung von Bytes in hexadezimale und ASCII Darstellung. Aber weder ist diese Domäne als ein für sich stehendes Stück Logik herausgearbeitet, noch die anderen Aspekte.

Aber ich will Goethes "Besser machen, nicht nur tadeln, soll den rechten Meister adeln" folgen und Ihnen nicht vorenthalten, wie ich meine, das der Code aussehen sollte.

In der reinen Logik unterscheidet sich meine Lösung nur unwesentlich von der im Buch. Doch ich habe die Logik anders (bzw. überhaupt) in Container verpackt. Zunächst nur in Funktionen:

image

Das ist ein erster Schritt. In Main() ist der Prozess nun deutlich sichtbar. Außerdem ist klar, wo welche APIs benutzt werden. Wer sich zum Thema "Dateien lesen und schreiben" informieren will, schaut einfach bei Check_if_file_exists() und Read_blocks_from_file() rein; den Rest kann man dann ignorieren.

Wem das nun jedoch zu wenig objektorientiert ist, wer gern noch eine deutlichere Zusammenfassung der Subaspekte sehen möchte, der findet hier eine Lösung mit Klassen:

image

Zum Beispiel bündelt die Klasse FileSystemProvider die Methoden, in denen sich Logik befindet, die den API System.IO benutzt.

Die Aspekttrennug ist damit deutlicher. Der Preis dafür ist etwas Rauschen: in Main() müssen nun Klassen instanziert werden und Methodenaufrufe haben Objektnamen als Präfix.

Überhaupt haben meine Lösungen doppelt oder gar mehr als doppelt so viele LOC (lines of code). Ist das gut? Sollte Code nicht immer so kurz und knapp wie möglich sein? Trägt Knappheit nicht zur Lesbarkeit bei?

Klar, wenige Zeilen Code lassen sich leichter überschauen, sozusagen physisch. Aber inhaltlich ist das nicht unbedingt der Fall, nämlich wenn es sich um reine Logik handelt. Das war ja das Problem des ursprünglichen Codes. 40-50 LOC, also rund eine Bildschirmseite, das war nicht viel - und doch war es nur schwer verständlich.

Da bezahle ich gern den Preis von etwas Rauschen und mehr LOC, wenn ich dafür Verständlichkeit bekomme. Und die ist nun vorhanden, würde ich sagen.

Der Einstieg ins Programm ist sonnenklar. Er beschreibt lesbar, wie das Verhalten "Dateiinhalt als Hex Dump anzeigen" hergestellt wird:

image

Main() hat nun eine einzige, harte Verantwortlichkeit: Integration.

Und jede Klasse hat wiederum nur eine einzige Verantwortlichkeit, z.B. FilesystemProvider:

image

Sie kapselt die Nutzung des System.IO API. Dieser Aspekt zerfällt jedoch in zwei weiche: Prüfen, ob eine Datei existiert, und blockweises Lesen der Bytes aus einer Datei.1

Fazit

Das Single Responsibility Principle ist zentral für saubere Softwareentwicklung. Um es anwenden zu können, muss man allerdings wissen, was denn eine Responsibility eigentlich ist.

Mit der Definition, die ich gegeben habe, fällt es Ihnen hoffentlich leichter, die Verteilung von Responsibilities in Ihrem Code zu überschauen - und sie dann mit Refaktorisierung zu entzerren.


  1. Ok, ich gebe zu, ein kleinwenig unsauber ist der Code noch. Denn sowohl in Check_if_file_exists() wie in Get_filename() nutze ich zwei APIs. Zum einen den eigentlichen, um den es dort geht (Dateisystem- bzw. Kommandozeilenzugriff), zum anderen jedoch auch den für die Ausgabe auf der Konsole zur Fehlermeldung. Konsequenterweise müsste ich die Fehlermeldung in eine eigene Methode verpacken und z.B. in den ConsoleProvider verschieben. Eigentlich - denn hier lasse ich das mal so stehen. Es ist eine vergleichsweise kleine Sünde. Und wer will schon Perfektion? :-) Wenigstens bin ich mir der “Schmutzrückstände” bewusst.