- Das Konzept der binären Suche (binary search) wird nicht nur in Interviewaufgaben verwendet, sondern kommt auch im echten Entwicklungswerkzeug Git zum Einsatz
- In einer großen Monorepo-Umgebung kann es vorkommen, dass Tests plötzlich fehlschlagen und sich die Ursache allein anhand von Logs nur schwer zurückverfolgen lässt
- Ein Kollege führte eine automatische Suche mit
git bisect durch, indem er einen guten und einen schlechten Commit festlegte, und fand so exakt den problematischen Commit, bei dem der Bug erstmals auftrat
- In jedem Schritt wird ein Skript ausgeführt, das Commits anhand des Testergebnisses automatisch klassifiziert und den ersten fehlerhaften Commit identifiziert
git bisect, das auf dem Prinzip der binären Suche basiert, ist ein leistungsstarkes Werkzeug, um die Ursache von Bugs in großen Codebasen schnell zurückzuverfolgen
Algorithmus und Praxisbeispiel
- Der Algorithmus der binären Suche (binary search) ist mehr als nur eine typische Interviewfrage und bildet auch in echten Debugging-Werkzeugen ein zentrales Prinzip
git bisect ist ein Tool, das binäre Suche verwendet, um den „Commit, der den Bug zuerst eingeführt hat (first bad commit)“, zu finden
- Es funktioniert nach einem ähnlichen Prinzip wie das Leetcode-Problem „First Bad Version“
Problemsituation im realen Arbeitsumfeld
- In Umgebungen mit großen Monorepos entstehen täglich Hunderte bis Tausende von Commits
- Die Ursache eines bestimmten Testfehlers lässt sich nur anhand von Logs nur schwer nachvollziehen
- Auslöser des Fehlers war eine String-Änderung in einer Konfigurationsdatei zum Abrufen des für einen Remote-Aufruf benötigten Tokens, wodurch auf ein anderes Konto verwiesen wurde und der Test fehlschlug
- Diese Änderung bestand zwar den Integrationstest, verursachte in der Praxis jedoch Probleme, und unter den vielen Commits war schwer festzustellen, ab welchem Zeitpunkt dies geschah
Problemlösung mit git bisect
- Ein Kollege aus einem anderen Team identifizierte den problematischen Commit schnell mit dem Befehl
git bisect
- Nach dem Festlegen eines guten Commits (good) und eines schlechten Commits (bad) checkte das Tool automatisch die dazwischenliegenden Commits aus, führte Tests aus und grenzte die Ursache schrittweise ein
- Jeder Testlauf dauerte zwar eine gewisse Zeit, aber am Ende wurde der Commit, der das Problem eingeführt hatte, exakt gefunden
- Nachdem dieser Commit zurückgesetzt worden war, funktionierten alle Tests wieder normal
Ablauf von git bisect
Fazit
git bisect ist ein praktisches Werkzeug, das das Prinzip der binären Suche auf die Erkundung der Code-Historie anwendet
- Selbst in großen Repositories oder bei komplexen Änderungshistorien lässt sich der Zeitpunkt der Bug-Einführung schnell nachverfolgen
- In Kombination mit Testautomatisierung ist so auch in großen Codebasen stabiles Debugging möglich
2 Kommentare
Wegen solcher Probleme verwenden wir TBD (trunk-based development).
Hacker-News-Kommentare
Als ich früher in einer riesigen Codebasis ohne Testabdeckung und mit chaotischen Abstraktionen gearbeitet habe, war
git bisectfast das einzige wirklich brauchbare Werkzeug.Der Code war so komplex, dass es unmöglich war, Bugs logisch zurückzuverfolgen; deshalb war es viel einfacher, herauszufinden, in welchem Commit das Problem entstanden ist.
In einer hochwertigen Codebasis brauchte man bisect dagegen kaum. Man konnte jede Komponente unabhängig testen, und auch die Observability war gut, sodass klar war, wo man nachsehen musste.
git bisectjemals unnötig ist. Es hilft nicht nur dabei, den Bug zu finden, sondern auch zu verstehen, warum er entstanden ist.Wenn die Commit-Messages in einem Projekt aussagekräftig sind, kann man per bisect den Kontext älterer Commits nachvollziehen und diesen Inhalt in den Bugfix-Commit einfließen lassen. Dieser Kreislauf stärkt die Commit-Kultur selbst.
Direktes Nachverfolgen war unmöglich, aber nachdem ich ein bisect-Skript geschrieben und es etwa 30 Minuten laufen lassen hatte, fand es den problematischen Commit exakt.
git bisectwurde ursprünglich eingeführt, um Regressionen im Linux-Kernel zu finden.Selbst in Fällen wie Hardware-Treibern, die sich nicht testen lassen, konnten normale Nutzer den Kernel selbst bisecten und den problematischen Commit eingrenzen.
Früher musste man Entwickler per E-Mail um Hilfe bitten, heute können Nutzer das Problem selbst Schritt für Schritt eingrenzen.
Das ist zum Beispiel nützlich, um den Umfang fehlerhaft verarbeiteter Daten nachzuverfolgen oder um zu beurteilen, ob „das ein Bug oder ein Feature“ ist.
Wenn ein Kunde zum Beispiel mit einer Version von vor 6 Jahren Probleme hat, kann man prüfen, ob ein Upgrade auf eine Version von vor 4 Jahren das Problem bereits löst.
Oder wenn der Code stark refaktoriert wurde, kann man nachvollziehen, ob der Fix beabsichtigt war oder nur zufällig passiert ist.
git bisectist großartig, wenn es gut funktioniert, aber es kann nicht jeden Bug finden.Manche Bugs zeigen beim Einführen noch keine Symptome und treten erst später durch eine andere Änderung zutage.
In solchen Fällen bricht die Annahme von bisect zusammen, dass der Bug zwischen einem guten und einem schlechten Commit genau einmal auftaucht.
Nicht testbare Commits kann man mit
skipüberspringen, aber wenn genau das der problematische Commit ist, wird das Ergebnis uneindeutig.Ich habe vor Kurzem zum ersten Mal ernsthaft
git bisectverwendet, und es war fast wie Magie.Es gab zwei Funktionen mit demselben Namen, und bei einer Code-Formatierungsarbeit wurde der Import der richtigen Funktion entfernt, wodurch das Problem entstand.
Ich hatte den Code mehrfach überprüft, aber bis bisect den problematischen Commit identifiziert hatte, hatte ich keine Ahnung von der Ursache.
Ich weiß normalerweise schon, in welcher Datei oder Funktion der Bug steckt, deshalb nutze ich bisect nicht oft.
Stattdessen verfolge ich mit dem Befehl
git log -L :func_name:path/to/file.cdie Änderungshistorie einer bestimmten Funktion.Dafür ist eine
.gitattributes-Konfiguration nötig..gitattributes-Konfiguration und wollte genauer wissen, was dafür nötig ist.git log -Lschwächer, weil es schwer ist, unter überladenen Funktionen mit demselben Namen eine bestimmte Variante zu verfolgen..gitattributesgibt, kann man alternativ mitgit log -Snach Commits suchen, die einen bestimmten String enthalten.Im Testskript sollte man sich Exit-Code 125 merken.
Wenn sich ein Testergebnis nicht beurteilen lässt, etwa wegen eines Build-Fehlers, überspringt bisect den Commit, wenn 125 zurückgegeben wird.
Ich habe das in einem Blogbeitrag zusammengefasst.
git bisect --first-parentnützlich.So lässt sich schnell herausfinden, „welcher PR den Bug eingeführt hat“, und danach kann man im betreffenden Branch noch einmal detaillierter bisecten.
Bei flaky Tests zeigt bisect besonders seine Stärke.
Wenn man wegen einer Race Condition einen Test Hunderttausende Male ausführen muss, um sicher zu sein, kann man ein bisect-Skript im Hintergrund laufen lassen und das Problem so realistisch lösen.
Kürzlich habe ich in einem mit Svelte gebauten Musikplayer-Projekt (lets-make-sweet-music.com) mit bisect die Ursache eines Bugs gefunden.
Es gab weder Tests noch Fehlerlogs, und durch
dependabot-Updates waren viele Commits hinzugekommen, was die Nachverfolgung erschwerte.Dank bisect fand ich den problematischen Commit, und die Ursache war, dass die von mir ersetzte Datei die Mehrfachbindung von Events nicht implementierte.
Wenn man Commits klein hält, lässt sich die Ursache eines per bisect gefundenen Problems schnell weiter eingrenzen.
Jemand meinte, es sei absurd, dass man für Vorstellungsgespräche Binärsuche lernen müsse, aber
git bisectist ein gutes praktisches Beispiel für dieses Konzept.Man muss sie aber nicht selbst implementieren. In den meisten Sprachen ist sie bereits in der Standardbibliothek enthalten.
Wenn man den mittleren Index mit
(low + high) / 2berechnet, kann ein Overflow entstehen.Das ist die beste Übung, um invariantenbasiertes Denken zu trainieren.
Neben bisect hat Git auch hervorragende Werkzeuge zur Code-Erkundung wie
log -L,log -Sundblame.Ich habe dazu vor einiger Zeit einen Blogbeitrag geschrieben.