Wenn du dir ständig selbst ins Bein schießt, repariere die Waffe
- In Teams gibt es oft Bereiche, in denen rund um ein System regelmäßig Fehler passieren, ohne dass darüber nachgedacht wird, wie sich diese Fehler verringern lassen
- In solchen Fällen ist es wichtig, das System zu verbessern, damit weniger Fehler passieren
- Erfahrung:
- Bei der iOS-Entwicklung mit CoreData sind UI-Updates nur auf dem Main-Thread möglich
- Subscription-Callbacks traten sowohl auf dem Main-Thread als auch auf Background-Threads auf, was häufig Probleme verursachte
- Bestehende Teammitglieder wussten das und gingen gut damit um, aber in Reviews von neuen Teammitgliedern kam das oft zur Sprache
- Wenn gelegentlich ein Fehler passierte, wurde im Crash-Report nachgesehen und
DispatchQueue.main.async ergänzt
- Um das zu lösen, wurde die Subscription-Layer aktualisiert, sodass Subscribers auf dem Main-Thread aufgerufen werden. Hat genau 10 Minuten gedauert.
- Damit wurde eine ganze Klasse von Crashes und etwas mentale Belastung beseitigt
- Jeder hätte das mit ein paar Minuten Nachdenken als offensichtliches Problem erkennen können
- Aber weil es keinen natürlichen Zeitpunkt gibt, solche Probleme zu beheben, bleiben sie seltsam lange bestehen
- Das heißt: Wenn man lange im Team ist, verschwinden solche Probleme leicht im Hintergrund
- Es braucht eine Veränderung der Haltung
- Man muss sich gelegentlich daran erinnern, dass man sich selbst und dem Team das Leben leichter machen kann, wenn solche Probleme auftauchen
Die Balance zwischen Qualität und Geschwindigkeit finden
- Zwischen Umsetzungsgeschwindigkeit und Vertrauen in die Korrektheit gibt es immer einen Trade-off
- Man sollte sich fragen, wie akzeptabel es in der aktuellen Situation ist, Bugs auszuliefern
- Wenn die Antwort darauf die eigene Arbeitsweise nicht beeinflusst, ist man zu starr
- Im ersten Job, bei Datenverarbeitungsprojekten, gab es ein gutes System, um Daten rückwirkend erneut zu verarbeiten
- Die Auswirkungen von ausgelieferten Bugs waren sehr gering, und in so einer Umgebung kann man sich in gewissem Maß auf Guardrails verlassen und schneller arbeiten
- 100% Test-Coverage oder umfangreiche QA-Prozesse bremsen die Entwicklungsgeschwindigkeit dann nur aus
- Im zweiten Job war das anders: ein Produkt für zig Millionen Nutzer, mit hochwertigen Finanzdaten und personenbezogenen Informationen, sodass Bugs verheerend waren
- Selbst kleine Bugs erforderten eine Postmortem-Analyse
- Features wurden sehr langsam ausgeliefert, aber ich glaube, in diesem Jahr gab es 0 Bugs
- In den meisten Fällen befindet man sich nicht in einer Situation wie bei der zweiten Firma
- Wenn Bugs nicht katastrophal sind, etwa bei 99% der Web-Apps, ist es besser, schnell auszuliefern und Bugs schnell zu beheben
- So kommt man weiter, als wenn man Zeit darauf verwendet, von Anfang an perfekte Features zu veröffentlichen
Zeit ins Schärfen der Säge zu investieren, ist fast immer wertvoll
- Es ist wichtig, mit den eigenen Tools gut umgehen zu können
- Man sollte schnell Code schreiben, die wichtigsten Shortcuts kennen und sich gut mit Betriebssystem und Shell auskennen
- Dinge wie Umbenennen, Zu Typdefinition springen und Referenzen finden wird man sehr oft machen
- Man sollte alle wichtigen Editor-Shortcuts kennen und sicher und schnell tippen können
- Auch Browser-Entwicklertools effektiv zu nutzen, ist wichtig
- Gute Tool-Auswahl und souveräner Umgang damit sind ein großer Vorteil
- Eines der größten Green Flags bei neuen Engineers ist Interesse an der Tool-Auswahl und am kompetenten Einsatz dieser Tools
Wenn du die Schwierigkeit nicht einfach erklären kannst, ist es wahrscheinlich akzidentelle Komplexität, und dann lohnt es sich, das Problem zu lösen
- Mein Lieblingsmanager hatte die Angewohnheit, immer weiter nachzuhaken, wenn ich sagte, dass etwas schwer umzusetzen sei
- Oft lautete seine Antwort ungefähr: „Ist das nicht letztlich nur etwas an X zu senden, wenn Y passiert?“ oder „Ist das nicht wie Z, das wir vor ein paar Monaten gemacht haben?“
- Das war Widerspruch auf sehr hohem Abstraktionsniveau, nicht auf der Ebene der konkreten Funktionen und Klassen, die ich erklären wollte
- Die übliche Sichtweise ist, dass solche Vereinfachungen durch Manager einfach nur nervig sind
- Überraschenderweise wurde mir aber in einem hohen Anteil der Fälle klar, dass der Großteil der Komplexität, die ich erklärte, akzidentelle Komplexität war, wenn der Manager weiterfragte
- Und indem ich diese zuerst löste, ließ sich das Problem tatsächlich so trivial machen, wie der Manager es beschrieben hatte
- So ein Ansatz macht spätere Änderungen meist einfacher
Versuche, Bugs eine Ebene tiefer zu lösen
- Statt Bugs nur oberflächlich zu beheben, ist es wichtig, die eigentliche Ursache zu finden und zu lösen
- Wenn es auf einem Dashboard eine React-Komponente gibt, die mit einem
User-Objekt aus dem Status des aktuell eingeloggten Nutzers arbeitet
- Dann meldet Sentry vielleicht einen Bug-Report, dass
user beim Rendern null war
- Man könnte schnell
if (!user) return null hinzufügen oder
- etwas tiefer graben und feststellen, dass die Logout-Funktion zwei getrennte State-Updates ausführt
- zuerst wird der Nutzer auf
null gesetzt und danach auf die Startseite weitergeleitet
- Wenn man die Reihenfolge der beiden vertauscht, wird keine Komponente diesen Bug je wieder erleben
- Denn innerhalb des Dashboards ist das User-Objekt niemals
null
- Wenn man weiter nur die erste Art von Bugfixes macht, endet man im Chaos, aber
wenn man weiter die zweite Art von Bugfixes macht, bekommt man ein sauberes System und ein tiefes Verständnis für Invarianten
Unterschätze nicht den Wert davon, für Bug-Untersuchungen in die Historie zu graben
- Ich war ziemlich gut darin, seltsame Probleme mit typischen Tools wie
println und dem Debugger zu analysieren
- Deshalb habe ich nicht oft in git geschaut, um die Historie eines Bugs zu verstehen, aber bei manchen Bugs ist das sehr wichtig
- Vor Kurzem schien auf einem Server dauerhaft Memory zu leaken, sodass er durch OOM beendet und neu gestartet wurde
- Alle plausiblen Ursachen waren ausgeschlossen, und lokal ließ sich das Problem nicht reproduzieren
- Es fühlte sich an, als würde man mit verbundenen Augen Dartpfeile werfen
- Ein Blick in die Commit-Historie zeigte, dass es nach dem Hinzufügen von Play-Store-Zahlungsunterstützung begonnen hatte
- Weil das nur ein paar HTTP-Requests waren, hätte ich dort in einer Million Jahren nicht gesucht
- Es stellte sich heraus, dass nach dem Ablauf des ersten Access Tokens eine Endlosschleife beim Holen des Access Tokens entstand
- Jede Anfrage fügte dem Speicher vielleicht nur etwa 1 kB hinzu, aber wenn auf mehreren Threads alle 10 ms neu versucht wird, summiert sich das schnell
- Normalerweise hätte so etwas einen Stack Overflow verursacht, aber weil in Rust asynchrone Rekursion verwendet wurde, trat kein Stack Overflow auf
- Darauf wäre ich nie gekommen, aber als ich den konkreten Code ansah, der offensichtlich das Problem verursachte, kam mir plötzlich die Theorie
- Es gibt keine feste Regel dafür, wann man so vorgehen sollte
- Es ist intuitionsbasiert; eine andere Art von „Hm?“ bei einem Bug-Report löst diese Art Untersuchung aus
- Diese Intuition kann sich mit der Zeit entwickeln, aber es reicht schon zu wissen, dass dieser Ansatz manchmal äußerst wertvoll ist
- Wenn das Problem dazu passt, probiere
git bisect aus
- Wenn du einen Commit hast, von dem du weißt, dass er fehlerhaft ist, und einen, von dem du weißt, dass er gut ist
Schlechter Code gibt Feedback, perfekter Code nicht. Irr dich lieber in Richtung schlechten Code
- Wirklich furchtbaren Code zu schreiben, ist sehr leicht
- Aber es ist auch sehr leicht, Code zu schreiben, der absolut allen Best Practices folgt
- Dann braucht man Unit-, Integrations-, Fuzz- und Mutationstests für alles, und das Startup geht vorher das Geld aus
- Ein großer Teil der Programmierung besteht darin, eine Balance zu finden
- Wenn man sich eher in Richtung schnell geschriebenem Code irrt...
- gerät man gelegentlich wegen schlechter Technical Debt in Schwierigkeiten
- man lernt, dass man „für Datenverarbeitung wirklich gute Tests hinzufügen muss“
- weil es später oft unmöglich ist, das noch zu korrigieren
- man lernt auch, dass man „über das Tabellendesign wirklich gründlich nachdenken muss“
- weil Änderungen ohne Downtime sehr schwer sein können
- Wenn man sich eher in Richtung perfektem Code irrt...
- bekommt man überhaupt kein Feedback
- alles dauert grundsätzlich länger
- man weiß nicht, wo man seine Zeit sinnvoll investiert und wo man sie verschwendet
- Feedback-Mechanismen sind essenziell fürs Lernen, aber dann bekommt man keine
- Zur Klarstellung, was mit „schlechtem“ Code gemeint ist
- Gemeint ist nicht: „Ich konnte mich nicht an die Syntax zum Erzeugen einer HashMap erinnern und habe deshalb die innere Schleife zweimal benutzt“
- Gemeint ist eher so etwas wie:
- Statt die Datenerfassung neu zu schreiben, um bestimmte Zustände unmöglich zu machen, fügt man an ein paar zentralen Stellen ein paar Assertions für Invarianten hinzu
- Das Servermodell ist exakt dasselbe wie das zu schreibende DTO, also serialisiert man es einfach. Das DTO kann man später bei Bedarf noch schreiben, statt jetzt allen Boilerplate-Code aufzusetzen
- Diese Komponenten sind trivial und selbst bei Bugs kein großes Problem, also überspringt man das Schreiben von Tests
Mach das Debugging einfacher
- Über die Jahre habe ich viele kleine Tricks gelernt, um Software leichter debuggbar zu machen
- Wenn man sich nicht darum bemüht, Debugging einfacher zu machen, wird man mit zunehmender Komplexität der Software enorm viel Zeit damit verbringen, jedes einzelne Problem zu debuggen
- Man bekommt Angst davor, Änderungen vorzunehmen. Es könnte eine Woche dauern, nur ein paar neue Bugs zu verstehen
- Achte darauf, wie viel deiner Debugging-Zeit für Setup, Reproduktion und Aufräumen danach draufgeht
- Wenn es mehr als 50% sind, solltest du einen Weg finden, es einfacher zu machen, auch wenn es diesmal etwas länger dauert
- Unter sonst gleichen Bedingungen sollten Bugfixes mit der Zeit leichter werden
Wenn du im Team arbeitest, stell immer Fragen
- Es gibt ein Spektrum von „alles selbst herausfinden wollen“ bis „Kollegen mit trivialen Fragen nerven“
- Ich glaube, die meisten Menschen am Anfang ihrer Laufbahn sind zu stark in Richtung des ersten Extrems verschoben
- Es gibt fast immer Leute um dich herum, die länger in der Codebase gearbeitet haben, Technologie X viel besser kennen, das Produkt besser verstehen oder einfach erfahrenere Engineers sind
- In den ersten sechs Monaten an einem neuen Ort vergeudet man oft mehr als eine Stunde damit, etwas herauszufinden, obwohl man die Antwort in ein paar Minuten bekommen könnte
- Stell Fragen. Nur dann ist es für jemanden lästig, wenn offensichtlich ist, dass du die Antwort in wenigen Minuten selbst hättest finden können
Der Deployment-Zyklus ist extrem wichtig. Man sollte sorgfältig darüber nachdenken, wie man schnell und häufig deployen kann
- Startups haben nur begrenzte Runway, und Projekte haben Deadlines
- Wenn man kündigt und sich selbstständig macht, reichen die Ersparnisse vielleicht nur für ein paar Monate
- Idealerweise steigt die Projektgeschwindigkeit im Lauf der Zeit mit Zinseszins-Effekt, sodass Features schneller ausgeliefert werden, als man es sich vorstellen kann
- Um schnell zu deployen, braucht es viele Dinge
- ein System, das nicht anfällig für Bugs ist
- schnelle Turnaround-Zeiten zwischen Teams
- die Bereitschaft, 10% eines neuen Features wegzuschneiden, die 50% der Engineering-Zeit kosten würden, und die Einsicht, genau diese Teile zu erkennen
- konsistente und wiederverwendbare Patterns, die sich für neue Screens/Features/Endpoints kombinieren lassen
- schnelles und einfaches Deployment
- Prozesse, die nicht bremsen (flaky Tests, langsame CI, pingelige Linter, langsame PR-Reviews, JIRA, das wie eine Religion behandelt wird, usw.)
- und Millionen weitere Dinge
- Langsames Ausliefern sollte genauso ein Postmortem auslösen wie das Lahmlegen von Production
- Unsere Branche funktioniert zwar nicht so, aber das heißt nicht, dass man nicht persönlich schnelles Deployment als Nordstern verfolgen kann
6 Kommentare
„Sich in den Fuß schießen“ = bedeutet das, dass man sich selbst ins Verderben bringt?
Wenn ein Problem durch fehlerhaften Code entsteht (eine kaputte Waffe), heißt das: Repariere die Waffe, statt dir in den Fuß zu schießen.
Das ist schockierend, als hätte jemand einfach meinen Gedankeninhalt eins zu eins herausgeholt, krass..
Hat sich sehr gut gelesen!!
Ich habe den Artikel aufmerksam gelesen.
Ich bin zwar kein Entwickler, aber ich konnte vieles davon gut nachvollziehen.