Neue Technologien müssen ausgiebig und tiefgründig evaluiert werden, bevor sie in unsere Produktiv-Software Einzug halten und Teil unseres Produktes werden. Wir wollen unsere bekannten Anforderungen prototypisch umsetzen, um schnelle und wertvolle Erkenntnisse über unsere geplante neue Technologie zu erlangen.
Inhaltsangabe
Zeitrahmen für Explorationen
Die Länge und Intensität, in der wir unsere Ziel-Technologie explorieren werden, wird stark variieren und hängt maßgeblich von dem Umfang der zu erwartenden Änderungen ab. Ein kurzer Vergleich:
Austausch einer Library
Die Evaluierung geht hierbei vergleichsweise schnell vonstatten, unter der Annahme, dass besagte Library nur eine Handvoll von Aufgaben in unserem Produktiv-Code übernimmt. Unsere größte Aufgabe ist es die neuen APIs und deren Verhalten besagter Library zu erlernen und bewerten wie kompatibel sich diese zu dem Rest unserer Architektur integrieren lassen.
Einführung eines neuen Frameworks
Neue Frameworks führen in der Regel nicht nur viele neuen Libraries in unserem Projekt ein, sie haben oft auch neue Konzepte und Lösungsansätze im Gepäck. Neue Konzepte bringen neue Chancen für Verbesserungen, dennoch müssen sie verstanden und erlernt werden – was allzu oft zeitaufwendig werden kann.
Neues oder zusätzliches Build-Tooling
Der große Unterschied zu den anderen Maßnahmen ist, dass sich Build-Tools nicht auf unser Endprodukt auswirken (oder zumindest nicht sollten). Nichts desto trotz müssen wir gerade bei Anpassungen unserer Infrastruktur viele Aspekte der täglichen Entwicklungsarbeit in Betracht ziehen. Negative Auswirkungen auf unsere Infrastruktur können erhebliche Produktivitätseinbußen nach sich ziehen und im schlimmsten Fall die Entwicklung zeitweise blockieren und zum Erliegen bringen.
Einführung einer neuen Programmiersprache
Eine neue Programmiersprache bringt implizit viele neue Konzepte, Libraries und eigenes Build-Tooling mit sich. All diese Neuerungen müssen von uns evaluiert werden. Ein solcher Technologie-Wechsel birgt viele Chancen und verspricht oftmals großes Potenzial für positive Auswirkungen. Wir erkaufen sie uns gleichzeitig mit vielen offenen Fragen, die von uns abgearbeitet werden müssen. Je nach Programmiersprache haben wir (und später auch unsere Kollegen) eine steile Lernkurve vor uns.
Fall-Beispiel: Anforderungen prototypisch umsetzen, in einer isolierten Umgebung
Unser Ziel ist es die wichtigsten unserer technischen Anforderungen prototypisch umsetzen zu können, mit unseren angestrebten Neuerungen. Der Aufwand den wir hier treiben müssen hängt wie oben beschrieben stark von unserer konkreten Modernisierungsmaßnahme ab. In jedem Fall empfiehlt es sich die prototypische Umsetzung isoliert und zu Beginn getrennt von unserem Produktiv-Code zu implementieren. Das nächste Kapitel stellt dies an einem typischen Fall-Beispiel “prototypisch” dar.
Durch unsere vorausgehende und ausgiebige interne Recherche bei unseren Stakeholdern kennen wir bereits die größten Schwachpunkte unserer heutigen Umsetzungen. Nun geht es darum diese prototypisch Stück für Stück nachzustellen.
Ein typisches Beispiel aus dem Entwickler-Alltag: Die heutigen Unit-Tests unserer UI-Anteile kosten uns im Rahmen eines automatisierten Build-Prozesses viel Zeit. Sie brauchen einerseits viel Zeit zur Ausführung und laufen nicht immer zuverlässig, man bezeichnet sie deshalb gerne auch als “flaky” Test-Cases. Die betroffenen Entwickler spielen deswegen seit langem lautstark mit dem Gedanken ihre UI-Test dauerhaft abzuschalten, da der tatsächliche Nutzen die dafür investierte Zeit und den damit verbundenen Ärger nicht rechtfertigt.
Bei diese Aussage lohnt es sich genauer hinzuschauen: Die jetzige Lösung “ärgert” die Betroffenen sichtlich, dennoch konnte man sich bisher nicht dazu durchringen die UI-Tests abzuschalten – es besteht also auf jeden Fall Bedarf für diese!
Beginne möglichst einfach
Wir gehen in diesem Szenario davon aus, dass wir durch die Recherche neuer Technologien bereits ein neues Test-Framework als potentiellen Nachfolger entdeckt haben. Nun gilt es die Lösungen miteinander zu vergleichen. Wir beginnen dazu möglichst einfach:
- Wir erstellen 10 einfache Testcases, die wir so auch in unserer Code-Base vorfinden würden. Die Betonung liegt hierbei auf einfach, komplexe Testszenarien betrachten wir zu Beginn bewusst noch nicht!
- Wir führen unsere Tests 10 mal lokal aus und vergleichen die Laufzeiten mit den bereits existierenden Tests und deren lokaler Laufzeit. Wir führen sie dabei bewusst zuerst nur lokal aus, um uns die Zeit für das Setup der nötigen Build-Pipeline zu sparen.
Mit diesem einfachen Setup können wir schnell erkennen, ob unser Ansatz eines der zentralen Probleme lösen kann: Lässt sich die Laufzeit unserer UI-Test drastisch reduzieren? Und drastisch bedeutet in diesem Kontext, dass sie sich signifikant reduzieren lässt! Eine Handvoll Millisekunden Zeitersparnis sind zwar “ganz nett”, werden aber den damit verbundenen Aufwand einer Umstellung nicht rechtfertigen.
Frage dich: Skaliert mein Lösungsansatz?
Nun stellt sich sicherlich dem ein oder anderen die Frage: “Reichen 10 Tests wirklich aus, um abzuschätzen, wie die Lösung für 1000 Test skalieren wird?”.
Die Antwort auf diese Frage ist einfach: “Wir probieren es aus und sehen was passiert!”. Wir befinden uns in einer abgeschlossenen Umgebung und niemand hindert uns daran unsere 10 Test-Cases so lange zu duplizieren, bis wir 1000 von ihnen haben. Wichtig: Uns geht es nicht darum 1000 unterschiedliche Test-Cases zu implementieren. Wir wollen wissen wie lange dauert es 1000 Test-Cases zu durchlaufen!
Und genauso stiefmütterlich behandeln wir das zweite Wehwehchen der heutigen Umsetzung: unsere “flaky” Test-Cases. Wir erfragen hierzu 5 Test-Szenarien die heute leicht reproduzierbar zu unzuverlässigen Ergebnissen führen. Auch diese durchlaufen wir 10 mal mit unserem alten und neuen Test-Framework und vergleichen die Resultate.
Mut zur Lücke beim Technologie-Wechsel: Kein Perfektionismus
Aus obigen Beispiel sollte eines klar werden: Unser Fokus sollte stets zuerst auf den einfachen Fällen liegen, zu denen wir schnell Erkenntnisse gewinnen können. Spezialfälle und komplexere Szenarien können auch zu einem späteren Zeitpunkt betrachtet werden.
Schnelle und wertvolle Erkenntnisse sammeln
Wir müssen gerade zu Beginn nicht jede Anforderung bis ins letzte Detail in unserem Prototypen implementieren. Wichtig ist es, dass wir schnell ein Gefühl dafür bekommen, ob unser Lösungsansatz hält was wir uns von ihm versprochen haben und wo er noch Lücken und Schwächen aufweist.
Nachdem wir unsere wichtigsten Anforderungen prototypisch umsetzen konnten, müssen wir auch für uns erste Entscheidungen treffen:
- Sind die offenbarten Lücken und Schwächen mit abschätzbarem Aufwand und geringem Risiko lösbar?
- Für wie groß erachten wir die Vorteile tatsächlich, nachdem wir uns selbst ein Bild von ihnen in der Umsetzung gemacht haben?
- Macht unsere Umsetzung einen guten Eindruck in Bezug auf ihre Wartbarkeit, Skalierbarkeit und Erweiterbarkeit?
- Wie schwer ist es uns gefallen die Neuerungen zu erlernen und zu verstehen? Wie viel Zeit mussten wir investieren?
Unser Fokus sollte dabei stets vorrangig auf den wichtigsten betroffenen Requirements und deren aktuellen Schwächen liegen. Damit ist nicht gemeint, dass weniger wichtige Requirements nicht berücksichtigt, oder gar “ignoriert” werden sollten.
Wir müssen an dieser Stelle Prioritäten setzen: Kann unsere Lösung wichtige Requirements nicht erfüllen, ist es belanglos, ob sie weniger wichtige technische Anforderungen verbessern könnte. Unser Hauptaugenmerk muss stets auf den für uns wichtigsten Requirements liegen.
Ausblick: Lücken und Schwächen separat betrachten
Offenbarte Lücken und Schwächen unserer Neuerung, die zu Tage getreten sind während wir unsere Anforderungen prototypisch umsetzen durften, müssen wir separat betrachten. Unser Ziel ist es dabei zu bestimmen, welche Auswirkungen diese im Detail auf unser Produkt und dessen Code-Basis haben wird.
Welche Risiken stellen diese für uns dar und wie können wir ihnen begegnen? All dies gilt es in einer separaten Nachbetrachtung zu bewerten.