In diesem Artikel geht es darum, das Konzept “Fortschritt durch inkrementelles Arbeiten” zu untersuchen und zu optimieren.
Definitionen
Fortschritt
Fortschritt ist abgeleitet, vom Fortschritt auf einem Weg. Der Weg hat oft einen Anfang (ist aber nicht immer relevant) aber immer ein Ende - also ein Ziel. Fortschritt machen bedeutet, dass man sich näher in Richtung Ziel bewegt.
In der Realität, genügt diese Metapher jedoch nicht. Oft ist das Ziel nicht nur durch einen einzigen Weg erreichbar - wenn überhaupt. Ausserdem bezieht sich Fortschritt in der Realität auch nicht auf nur eine Grösse, sondern eine Vielzahl von Variablen. Aus diesem Grund, kann man in viele verschiedene (oft mehr als 3) Fortschritt machen und sogar in manchen das Ziel erreichen, ohne aber wirklich je vollends das Ziel erreicht zu haben. Somit kann es auch passieren, dass man sich in bestimmterweise vom Ziel entfernet, aber trotzdem im Schnitt näher kommt und Fortschritt macht.
Zuguterletzt ist nicht immer eindeutig ob man überhaupt Fortschritt macht, weil das Ziel und die Umgebung nicht konstant sind und ausserdem über sehr komplexe Zusammenhänge miteinander verbunden sind. Quasi wie ein Wanderer bei Nebel in einer sich ständig dehnend und stauchenden Gummi-Welt.
Inkrementell
Inkrementell bedeutet, dass etwas in mehreren Schüben erfolgt. Ausserdem impliziert das Wort auch, dass eine Grösse in kleinen Schritten erhöht oder verändert wird. Inkrementell legt ausserdem nahe, dass die Grösse ansteigt. Synonym für “Inkrementell”, kann auch das Wort “iterativ” verwendet werden (z.B Iteration, iterieren, etc.).
Inkrementeller Fortschritt
Inkrementeller Fortschritt bedeutet in diesem Falle also, in kleinen aber kontinuierlichen Schritten einem Ziel näher zu kommen.
Zwei Arten von Iterationen
Inkrementeller Fortschritt kann auf jegliches Ziel oder Arbeit angewandt werden. Dabei spielt es keine Rolle ob es sich um ein Projekt, eine einfache Arbeit, um ein persönliches Hobbyprojekt oder das Produkt einer Firma handelt.
Vertikale Iteration
Vertikale Iteration bedeutet, dass man sich auf einen Parameter fokussiert und diesen in mehreren Durchläufen schrittweise erhöht. Also zum Beispiel, die Performance eines Webservers schrittweise steigern. Oder schrittweise die Features einer privaten Hostingplattform erhöhen (Azure, Kubernetes, Ingress, Certs, Logging, etc.).
Horizontale Iteration
Beim horizontalen Inkrementieren, wird der Fokus nicht auf einen einzelnen Parameter, sondern auf einen übergreifenden Parameter gelegt, welcher alle sub-Parameter zusammenfasst. Eine Iteration besteht hier darin dass bei allen sub-Parameter gleichzeitig ein Inkrement hervorgerufen wird.
Vertikal vs. Horizontal
Horizontales iterieren bietet gegenüber der vertikalen Variante einige Vorteile. Horizontales iterieren garantiert, dass auch Fortschritt beim übergeordneten Ziel (ausgedrückt durch den sub-Paramter) gemacht wird. Dadurch, dass gleichzeitig verschiedene Unterbereiche inkrementiert werden müssen, ist der Wert um welcher pro Unterbereich inkrementiert wird proportional kleiner, als bei der vertikalen Iteration.
sub_schritt = schritt / Anz_Unterbereiche
Der gesamte Fortschritt des übergreifenden Paramters sollte jedoch fast gleich weit sein wie bei der vertikalen Iteration. Fast, weil durch Task-Switching ein bestimmter Overhead bestehen kann.
Eine grosse Gefahr beim vertikalen iterieren ist premature optimization.
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming. - Donald Knuth, ~1960
Dabei wird zu früh auf Sachen Wert gelegt, welche im momentanen Zeitpunkt völlig belanglos sind und selbst für die Zukunft grosse Unsicherheit besteht ob sich dies je ändern wird (Stichwort: You aren’t going to need it - YAGNI). Dieses Problem wird durch das horizontale iterieren dadurch gelöst, indem bei jedem Teilschritt eine Verbesserung erreicht werden muss und der gesamt Fokus immer auf dem übergeordneten Paramter liegt.
Ein Beispiel wäre 5uperDev und Kubernetes. Beim Aufbau von 5uperDev und den Housingestimator habe mich stark auf die inkrementelle Arbeitsweise konzentriert. So konnte ich mich in kurzer Zeit in viele neue Technologien einarbeiten (Azure, ASP.NET core / Blazor, AI, GRPC, Kubernetes). Das ganze Projekt ist bis auf den letzten Punkt stark gemäss der vertikalen Iterationsmethode verlaufen. Ich habe Basis-Funktionen aufgebaut und bin dann sofort weiter zur nächsten Unteraufgabe. Als ich jedoch Kubernetes aufgesetzt habe, wurde ich von einem Strom an neuen Lösungen und Technologien mitgerissen, wodurch die Plattform Schritt für Schritt komplizierter und aufwendiger wurde (multiple nodes, secrets, certificates, CI/CD variables). Dabei habe ich auch viel Zeit mit dem recherchieren von weiteren Technologien verloren (Ingress, Traefik, Istio, etc.). Beim Teil Kubernetes bin ich also in die vertikale Iterationsmethode abgerutscht.
Die vertikale Iterationsmethode hat jedoch noch eine weitere Gefahr. Für bestimmte Aufgaben gibt es keinen definierten Endzustand. Wenn man zum Beispiel eine Infrastruktur aufbaut, dann muss diese später auch unterhalten werden. Dies bedeutet, dass wenn man unkontrolliert vertikal iteriert, dann verrennt man sich in eine Infrastruktur, welche so viel Zeit und Ressourcen für den Unterhalt einnimmt, dass man gar keine Zeit für anderes mehr hat. Somit würde das überliegende Ziel nicht mehr erreicht.
Umsetzung der horizontalen Iterationsmethode
Horizontale Iteration ist also das Aufteilen eines grossen Schrittes in mehre Unteraufaben, um anschliessend alle gleichzeitig anpacken und jede nur einen kleinen Schritt weiter zu bewegen. In der Theorie tönt dies sehr gut. Allerdings funktionieren Menschen in der Praxis anders.
Bestimmte Aufgaben (oder sogar die meisten) lassen sich schlecht in kleinere Schritte / Teile unterteilen. Der einzige Weg diese Aufgaben trotzdem zu unterteilen, ist irgendwo Abstriche zu machen. Abstriche heisst, dass etwas nicht so schön, nicht so gut, nicht so performant, nicht so billig oder nicht so sicher ist wie es sein könnte. Es heisst auch, dass “Best Practices” nicht angewandt werden können, einfach weil die Schrittweite dafür nicht ausreicht - der Aufwand zu gross wäre.
Dies bedeutet, um Aufgaben trotzdem bewältigen zu können muss gehackt und getrickst werden. Das Resultat wird schludrig und unschön. Aber erstens gibt es ein Resultat und zweitens ist es besser als das vorherige.
Das Problem dabei ist, dass wir Menschen nicht gerne schlechte Arbeit machen. Wir wollen auf unsere Arbeit stolz sein und von anderen gelobt werden. Schludrige und schlechte Qualität färbt auf den Urheber ab und lässt ihn in schlechtem Licht erscheinen (naiv, Anfänger, Unwissender, Idiot, etc.). Der mentale Wiederstand ist riesig dadurch ist vertikales iterieren extrem verlockend und unser Gehirn / Gewissen wendet hier hoft die Salami-Taktik an.
Es geht also darum zu verhindern, dass man Stück für Stück in die veritkale Iteration abrutscht. Dazu muss jedoch zuerst definiert werden, wie gross der Unterschritt sein soll. Respektive ab wann wir quasi die Teilaufgabe zu gut zu lösen versuchen und in premature Optimization abdriften.
Schrittgrösse / Iterationsperiode
Generell gilt bei inkrementellem Fortschritt, dass die Zeit welche es benötigt für eine Iteration möglichst klein sein sollte - respektive begrenzt ist. Bei der vertikalen Itertation ist die Grenze der Periode nur von untergeordneten Wichtigkeit. Denn auch wenn man sehr lange für eine Iteration benötigt, bewegt man sich immer noch auf ein vertikales Ziel zu.
Bei der horizontalen Iteration ist dies jedoch gerade einer der kritischen Kriterien. Wie gesagt, wird die Schrittgrösse der Teilaufgaben durch die Anzahl Teilaufgaben und die übergeordnete Schrittgrösse definiert.
Das bedeutet, dass die wichtigsten Grössen beim horizontalen inkrementellen Fortschritt die Anzahl Teilaufgaben und die gesamte Schrittgrösse ist. Beide Parameter müssen gut im Auge behalten werden, denn sie geben die untergeordnete Schrittgrösse vor. Die untergeordnete Schrittgrösse ist nichts anders, als die Zeit und Ressourcen, welche für einen Unterschritt / Teilaufgabe zur Verfügung stehen.
Um erfolgreich horizontal iterieren zu können ist es eine zwingende Bedingung, dass die zeitlichen und ressourcenbasierten Vorgaben strikte eingehalten werden. Dies bedeutet, dass Pläne nicht überschritten werden dürfen. Jede Überschreitung bedeutet entweder einen Kompromiss in einer anderen Teilaufgabe oder eine langsamere Iteration im gesamten übergeordneten Prozess! Vor allem eine langsamere Iteraton im übergeordneten Prozess führt direkt zu weiteren gravierenden Nachteilen wie:
- keine Flexibilität → Trägheit
- kein Feedback → Blindflug
- Ressourcen Bankrott → Scheitern des Haupt-Ziels
Eine Iteration
Die begrenzte und fixe Schrittgrösse, welche die zur Verfügung stehenden Ressourcen und Zeit definiert beeinflusst, wie eine Teilaufgabe abgearbeitet werden muss.
Wenn die obersten Gebote sind:
- Es muss inkrementiert werden == es muss Fortschritt / Mehrwert erzeugt werden
- Es muss innerhalb des Zeit & Ressourcen Rahmens geblieben werden
Dann heisst dies, dass an anderen Variablen der Lösungsfindung geschraubt werden muss, bis diese beiden Gebote erfüllt werden. Generell besitzt der Lösungsfindungsprozess die folgenden Variablen (wichtigste zuerst): - Vollständigkeit - Abdecken der Spezifikation
- Konsistenz - Gleichmässigkeit über mehrere Systeme z.B horizontale Konsistenz in einem Projekt
- Komplexität - Je komplexer, desto verflochtener ist ein System und kann dadurch einen Output grösser als die Summe von Einzelteilen generieren. Dadurch theoretisch effizienter und effektiver.
- Korrektheit - Saubere Arbeit, Qualität, Legal
Wenn also an den Parameter gedreht werden muss, sollte dies in folgender Reihenfolge geschehen:
- Korrektheit
- Komplexität
- Konsistenz
- Vollständigkeit
Somit ist Vollständigkeit das wichtigste Gut. Natürlich kann keiner der 4 Parameter in nur einer Iteration erfüllt werden und dies sollte auch nicht angestrebt werden. Sondern, die Einhaltung der Ressourcen Grenzen muss an oberster Priorität stehen. Denn nur dann kann das gesamte System erfolgreich sein. Ein erfolgreiches System garantiert weitere Iterationen und somit Ressourcen um weiter an den 4 Parametern zu arbeiten.
Justieren der Parameter
Wenn also die 4 Parameter (Korrektheit, Komplexität, Konsistenz und Vollständigkeit) justiert werden sollen, stellt sich die Frage wie das andere Ende des Spektrums aussieht:
Maximal | Minimal |
---|---|
Vollständigkeit | Unvollständig, Halbpatzig, “Kaputt” |
Konsistenz | Sonderfall, Extra-Wurst, Unterschied, Diskreptanz |
Komplexität | Simpel, Trivial, Ineffizient, Ineffektiv |
Korrektheit | Illegal, Schlampig, Fahrlässig |
Somit bedeutet “an den Parametern schrauben”, dass man sich mehr in Minimal bewegt. Wir haben also zwei Extrempositionen “minimal” und “maximal”.
Ideale Werte und deren Implikationen
Der ideale Zustand für jeden Parameter ist unten aufgelistet.
- Korrektheit - Nichts, mit Nichts kann auch nichts falsch gemacht werden.
- Komplexität - Unendlicher Random Process, jede Zahl ist einzigartig, somit stellen alle Zahlen eine Information dar, welche grösser ist als die Summe - der Information einzelner Zahlen addiert.
- Konsistenz - Interface wo (x)x enthalten, Konsistenz zu allem, da alles enthalten ist.
- Vollständigkeit - Eine Implementation wo (x)x implementiert ist.
Nun stellt sich die Frage was denn diese idealen Werte bedeuten.
Korrektheit
Ideale Korrektheit kann erreicht werden, wenn eine Teilaufgabe aus “Nichts” besteht. Das heisst es muss gar nichts gemacht werden. Denn so können keine Regeln verletzt werden, alles ist legal und man folgt allen “Best Practices” - weil es gar keine gibt. Zudem ist die Qualtät so hoch, dass niemals Mängel oder Probleme auftreten können.
Die ist auch der Grund wieso die Korrektheit als erstes vernachlässigt werden muss. Korrektheit bringt alleine für sich keine Funktion und dadurch keinen Fortschritt zum übergeordneten Ziel.
Schön geschriebener Code ist nicht verkaufbar, wenn er nicht auch andere Parameter erfüllt. Ein super sicher eingerichteter Server hilft nicht, wenn es keine Webseite zum hosten gibt.
Komplexität
Ideale Komplexität ist ein unendlicher random Prozess, weil jede Zahl eine eigene Instanz darstellt, welche einzigartig ist und Information darstellt. Zusammen ergeben alle Zahlen einen Informationswert, welcher höher ist als die Summe der Information der einzelnen Zahlen. Dies entsteht dadurch, dass die Position und der Wert jeder Zahl die Gesamtinformation beeinflusst. Jede Instanz ist unersetzlich, perfekt effizient und effektiv.
Komplexität muss als nächstes vernachlässigt werden, da der ideale komplexe Zustand nie erreicht wird (unendlich) und somit unendlich Ressourcen erfordert. Ideale Effizienz und Effektivität sind nicht gefordert, sondern nur das erreichen des Ziels. Deswegen sind simplere Lösungen immer vorzuziehen.
Bedenke auch: Overfitting vs Underfitting in Machine Learning
Konsistenz
Ein Inteface wo das ganze Universum enthalten ist, ist immer konsistent zu allem.
Der Grund wieso Konsistenz vernachlässigt werden muss, ist weil durch die ideale Konsistenz eine Umsetzung unmöglich wird. Eine Lösung soll Fortschritt zum Gesamtsystem bringen und dabei so Konsistent wie möglich sein. Ideale Konsistenz würde jedoch bedeuten, dass gar nicht der Fortschritt zur Lösung im Vordergrund steht.
Vollständigkeit
Die Ideale Vollständigkeit ist eine Implementation wo alles erdenklich mögliche im Universum implementiert ist.
Dies ist eigentlich kein Problem, da durch das iterative Vorgehen eben genau das Universum in Einzelteile aufteteilt wird, welche dann iterativ abgearbeitet werden können. Deswegen sollte die Vollständigkeit nur dann komprimiert werden, wenn es gar nicht anders geht.
Interessanterweise ist es genau die Vollständigkeit, welche traditionell im Projektmanagement komprimiert wird…
Vorgehensweise - Unkown unknows
Das Konzept die 4 Parameter zu justieren, um die zwei Gebote der horizontalen Iteration einzuhalten ist schön und gut. Allerdings besteht weiterhin das Problem, dass wir zwar die theoretischen Grenzen der 4 Paramteter nun kennen (Komplex vs. Simpel, Korrekt vs. Illegal, etc) jedoch ist die Bedeutung dieser abstrakten Begriffe für den Aufgaben Kontext noch unklar. In der Praxis ist das Konzept deswegen so nicht anwendbar.
Die einzige Möglichkeit die abstrakten Begriffe für einen Aufgaben Kontext und Zeitpunkt zu definieren, besteht darin eines der beiden Extreme zu wählen und eine Lösung in dieser Richtung zu suchen. Sobald eine Lösung gefunden wurde, können dann die 4 Parameter justiert werden.
Da wir weiterhin unser Gebot “innerhalb des Zeit & Ressourcen Rahmens bleiben” erfüllen müssen folgt, dass wir die Minimal Strategie anstreben müssen. Denn mit der Maximal Strategie wäre praktisch garantiert, dass wir die beiden Gebote nicht erfüllen könnten.
Sobald eine Minimal Lösung gefunden wurde, können die verbleibenden Ressourcen dazu verwendet werden, die 4 Parameter soweit wie möglich in Richtung Maximal Lösung zu bewegen.
Durch diese Strategie entstehen jedoch zwei Probleme. Erstens, ist es die menschliche Natur, dass wir immer automatisch optimieren und Stolz auf unsere Arbeit sein wollen. Dies führt dazu, dass man oft in die Slippery-Slope oder die Salami-Taktik Falle fällt und langsam Richtung Maximal Lösung rutscht.
Zweitens, müssen durch die Justierung der 4 Parameter möglicherweise Kompromisse eingegangen werden, um kurzfristig die Gebote einzuhalten, jedoch langfristig zu einem Problem werden können (z.B fehlende Korrektheit). Deswegen muss der Justier Prozess (also die Hacks, Illegalen Prozesse, Ineffizienz, etc.) an die Pendenzen Liste des übergeordneten Ziels weitergeleitet werden - quasi als To Do Liste dokumentiert werden. Nur so kann langfristig verhindert werden, dass Technical Dept und andere Risiken unter Kontrolle gehalten werden können.
Dadurch, dass das Delta zur abgeschätzten Maximal Lösung aufgeschrieben wird, ist auch das erste Problem entschärft. So steht man quasi nicht als Unwissender, Unprofessioneller oder Idiot da und man kann sich rechtfertigen.
Somit ist der Ablauf:
- Minimal Lösung umsetzen
- Falls Ressourcen vorhanden: Loop in dem schrittweise in Richtung Maximal Solution gegangen wird.
- Aufschreiben der Differenz zwischen der erzielten Lösung und der abgeschätzten Maximal Lösung.
Die Maximal Lösung ist abgeschätzt, da diese in der Praxis nie erreicht werden kann und wir deswegen auch nicht wissen wo sie liegt. Deswegen eine abgeschätzte Maximal Lösung, basierend auf unserem momentanen Wissenstand.
Eine Liste mit verbotenen Ausdrücken (Out of Scope) kann dabei helfen die abstrakten Ausdrücke der 4 Parameter genauer zu definieren.
Verbotene Ausdrücke
Sich wirklich auf die Minimal Lösung zu beschränken ist sehr schwierig. Deswegen hilft es, wenn für die Minimal Lösung auch eine Art Spezifikation respektive eine inverse Spezifikation geschrieben wird. Dabei handelt es sich um eine Liste von “verbotenen Ausdrücken”. Die Ausdrücke und Technologien auf der List dürfen für die Minimal-Lösung unter keinen Umständen verwendet werden.
Oft, weiss man nicht wie die Minimal Lösung aussieht, aber man weiss wenigstens was sicher nicht der einfachste Weg ist. Die Liste der verbotenen Ausdrücke kann anschliessend auch für die Justierung und zum Schluss sogar für die Erfassung des Deltas zur abgeschätzten Maximal Lösung verwendet werden.
Zusammenfassung
Dieser Artikel erlärt, was inkrementeller Fortschritt ist und unterscheidet zwischen zwei Arten von iterativer Arbeitsweise. Vertikale Iteration hat die Nachteile, dass nur ein Parameter optimiert werden kann und man sich schnell in premature Optimization verrennt. Bei der horizontaler Iteration (besser) wird eine grosse Aufgabe in Teilaufgaben unterteilt. Iteriert wird jedoch über die grosse Aufgabe und somit über alle Teilaufgaben gleichzeitig. Der Iterationschritt der grossen Aufgabe und die Anzahl Teilaufgaben geben den Iterationschritt der Teilaufgaben vor.
Wir haben erkannt, dass um horizontale Iteration erfolgreich zu praktizieren, 2 Gebote und 4 Parameter fundamental sind. Die 4 Parameter dienen als Justierknöpfe um zu garantieren, dass die zwei Gebote eingehalten werden können. Durch die 4 Parameter Vollständigkeit, Konsistenz, Komplexität, Korrektheit teilen wir eine Lösung in unterschiedliche Aspekte. Der Wertebereich jedes Parameters ist durch eine Minimal und eine Maximal Grenze definiert.
Die 4 Parameter sind jedoch nur abstakte Begriffe mit keinem Bezug zu einer Aufgabe. Deswegen muss, um die Parameter justieren zu können immer zuerst die Minimal Lösung umgesetzt werden. Dies gibt einen Startpunkt für die Justage vor. Anschliessend soll solange Ressourcen vorhanden die Parameter Richtung Maximal Lösung optimiert werden. Sobald die Ressourcen der momentanen Teil-Iteration aufgebraucht sind, soll ein Delta der momentanen Parameterwerte und der abgeschätzten Maximal Parameterwerte berechnet werden. Mit diesem Delta wird anschliessend eine ToDo Liste für die Planung der nächsten Teil-Iteration gefüllt.
Um die Suche einer Minimal Lösung zu vereinfachen und menschliche Schwächen (z.B Stolz) auszugleichen, ist eine Liste mit verbotenen Ausdrücken hilfreich.
Offene Punkte / Ideen / Fragen
- Wie genau erfolgt die weitergabe der Todo Liste an die Haupt Iteration?
- Planung der Haupt-Iteration?
- Wie werden die Parameter genau kommuniziert?
- Evtl. weitere Möglichkeiten eine Minimal Lösung so schnell wie möglich zu finden?