Während einer Diskussion erzählte mir einer meiner Kollegen, dass er einige Probleme mit seinem aktuellen Projekt hat, während er versucht, Fehler zu beheben. "Wenn ich einen Fehler behebe, funktioniert etwas anderes nicht mehr an anderer Stelle", sagte er.
Ich begann darüber nachzudenken, wie das passieren könnte, kann es aber nicht herausfinden.
- Ich habe manchmal ähnliche Probleme, wenn ich zu müde / schläfrig bin, um die Arbeit korrekt auszuführen und einen Überblick über den Teil des Codes zu erhalten, an dem ich gearbeitet habe. Hier scheint das Problem einige Tage oder Wochen zu bestehen und hat nichts mit dem Fokus meines Kollegen zu tun.
- Ich kann mir auch vorstellen, dass dieses Problem bei einem sehr großen, schlecht gemanagten Projekt auftritt , bei dem Teamkollegen keine Ahnung haben, wer was tut und welche Auswirkungen auf die Arbeit anderer Menschen sich auf sie auswirken können. Dies ist auch hier nicht der Fall: Es ist ein eher kleines Projekt mit nur einem Entwickler.
- Es kann auch ein Problem mit der alten, schlecht gewarteten und nie dokumentierten Codebasis sein , bei der die einzigen Entwickler, die sich die Konsequenzen einer Änderung wirklich vorstellen können, das Unternehmen vor Jahren verlassen haben. Hier hat das Projekt gerade erst begonnen und der Entwickler verwendet keine Codebasis von irgendjemandem.
Was kann die Ursache für ein solches Problem in einer neuen, kleinen Codebasis sein, die von einem einzelnen Entwickler geschrieben wurde, der sich weiterhin auf seine Arbeit konzentriert ?
Was kann helfen?
- Unit-Tests (gibt es keine)?
- Richtige Architektur (ich bin mir ziemlich sicher, dass die Codebasis überhaupt keine Architektur hat und ohne vorläufige Überlegungen geschrieben wurde), die das gesamte Refactoring erfordert?
- Paar-Programmierung?
- Etwas anderes?
project-management
refactoring
Arseni Mourzenko
quelle
quelle
Antworten:
Es hat nicht viel mit Fokus, Projektgröße, Dokumentation oder anderen Prozessproblemen zu tun. Solche Probleme sind normalerweise auf eine übermäßige Kopplung im Design zurückzuführen, wodurch es sehr schwierig ist, Änderungen zu isolieren.
quelle
Eine der Ursachen kann eine enge Kopplung zwischen den Komponenten Ihrer Software sein: Wenn es keine einfachen, genau definierten Schnittstellen zwischen den Komponenten gibt, kann bereits eine kleine Änderung in einem Teil des Codes unerwartete Nebenwirkungen in anderen Teilen der Software hervorrufen Code.
Als Beispiel habe ich kürzlich an einer Klasse gearbeitet, die eine GUI-Komponente in meiner Anwendung implementiert. Wochenlang wurden neue Fehler gemeldet, ich habe sie behoben, und an anderer Stelle traten neue Fehler auf. Mir wurde klar, dass diese Klasse zu groß geworden war, zu viele Dinge tat und viele Methoden davon abhingen, dass andere Methoden in der richtigen Reihenfolge aufgerufen wurden, um richtig zu funktionieren.
Anstatt die letzten drei Fehler zu beheben, habe ich einiges überarbeitet: Die Komponente wurde in eine Hauptklasse plus MVC-Klassen (drei zusätzliche Klassen) aufgeteilt. Auf diese Weise musste ich den Code in kleinere, einfachere Teile aufteilen und klarere Schnittstellen definieren. Nach dem Refactoring wurden alle Bugs behoben und keine neuen Bugs gemeldet.
quelle
Es ist leicht für einen Fehler, einen anderen zu maskieren. Angenommen, Fehler "A" führt dazu, dass die falsche Funktion aufgerufen wird, um Eingaben zu verarbeiten. Wenn der Fehler "A" behoben ist, wird plötzlich die richtige Funktion aufgerufen, die noch nie getestet wurde.
quelle
Nun, die unmittelbare Ursache ist, dass zwei Fehler richtig oder zumindest nicht offensichtlich falsch sind. Ein Teil des Codes gleicht das fehlerhafte Verhalten des anderen Teils aus. Oder wenn der erste Teil als solcher nicht "falsch" ist, gibt es eine ungeschriebene Vereinbarung zwischen den beiden Teilen, die verletzt wird, wenn der Code geändert wird.
Angenommen, die Funktionen A und B verwenden für eine bestimmte Menge eine auf Null basierende Konvention, sodass sie ordnungsgemäß zusammenarbeiten. C verwendet jedoch eine Konvention. Sie können A "reparieren", um mit C zu arbeiten, und dann ein Problem mit B feststellen.
Das tiefere Problem ist das Fehlen einer unabhängigen Überprüfung der Richtigkeit der einzelnen Teile. Unit-Tests sollen dies ansprechen. Sie dienen auch als Spezifikation der richtigen Eingaben. Zum Beispiel würde eine gute Reihe von Tests deutlich machen, dass die Funktionen A und B eine 0-basierte Eingabe und eine 1-basierte Eingabe erwarten.
Die richtigen Spezifikationen können auch auf andere Weise erstellt werden, von offiziellen Dokumenten bis hin zu guten Kommentaren im Code, je nach den Anforderungen des Projekts. Der Schlüssel ist zu verstehen, was jede Komponente erwartet und was sie verspricht, damit Sie Inkonsistenzen finden können.
Gute Architektur hilft bei dem Problem, den Code zu verstehen, und erleichtert dies. Paarprogrammierung ist hilfreich, um Fehler zu vermeiden oder sie schneller zu finden.
Hoffe das hilft.
quelle
Enge Kopplung, fehlende Tests, dies sind wahrscheinlich die häufigsten Schuldigen. Grundsätzlich geht es nur um schlechte Standards und Vorgehensweisen. Ein weiterer Grund ist der falsche Code, der es schafft, mit korrektem Verhalten für eine Weile Glück zu haben. Betrachten Sie einen
memcpy
Fehler von Linus Torvalds, bei dem das Ändern seiner Implementierung Fehler in Clients aufdeckte (nicht verursachte), diememcpy
an Orten eingesetzt wurden, an denen siememmove
mit überlappenden Quellen und Zielen hätten arbeiten sollen.quelle
Es klingt so, als ob diese "neuen" Bugs keine "neuen" Bugs sind. Sie waren einfach kein Problem, bis der andere Code, der kaputt war, tatsächlich behoben wurde. Mit anderen Worten, Ihr Kollege merkt nicht, dass er die ganze Zeit tatsächlich zwei Bugs hatte. Wenn der Code, der sich nicht als fehlerhaft herausstellt, nicht fehlerhaft war, wäre er auch dann nicht fehlgeschlagen, wenn der andere Teil des Codes tatsächlich repariert worden wäre.
In beiden Fällen kann ein besser automatisiertes Testprogramm hilfreich sein. Es hört sich so an, als müsste Ihr Kollege die aktuelle Codebasis einem Komponententest unterziehen. Bei zukünftigen Regressionstests wird überprüft, ob vorhandener Code weiterhin funktioniert.
quelle
Verbessern Sie die Breite Ihres automatisierten Testprogramms. Führen Sie IMMER alle Tests durch, bevor Sie Codeänderungen vornehmen. Auf diese Weise erkennen Sie die schädlichen Auswirkungen Ihrer Änderungen.
quelle
Ich bin gerade auf dieses Problem gestoßen, als ein Test nicht korrekt war. Der Test überprüfte einen bestimmten Berechtigungsstatus, der korrekt war. Ich habe den Code aktualisiert und den Berechtigungstest ausgeführt. Es funktionierte. Dann habe ich alle Tests durchgeführt. Alle anderen Tests, bei denen die geprüfte Ressource verwendet wurde, sind fehlgeschlagen. Ich habe den Test und die Berechtigungsprüfung korrigiert, aber zuerst gab es ein bisschen Panik.
Es kommt auch zu inkonsistenten Spezifikationen. Dann ist es fast garantiert, dass das Beheben eines Fehlers einen anderen erzeugt (aufregend, wenn dieser bestimmte Teil der Spezifikation erst später im Projekt ausgeführt wird).
quelle
Stellen Sie sich vor, Sie haben ein vollständiges Produkt. Wenn Sie dann etwas Neues hinzufügen, scheint alles in Ordnung zu sein, aber Sie haben etwas anderes kaputt gemacht, was von einem Code abhängt, den Sie ändern, damit das neue Feature funktioniert. Auch wenn Sie keinen Code ändern , sondern nur vorhandene Funktionen erweitern, kann dies zu Problemen führen.
Im Grunde haben Sie sich selbst fast geantwortet:
Lernen Sie einfach, das TDD-Prinzip anzupassen (zumindest für neue Funktionen) und versuchen Sie, alle möglichen Zustände zu testen, die auftreten können.
Pair-Programmierung ist großartig, aber nicht immer "verfügbar" (Zeit, Geld, beides ...). Aber auch Code-Überprüfungen (zum Beispiel von Ihren Kollegen) einmal pro Tag / Woche / eine Reihe von Commits helfen sehr, insbesondere wenn die Überprüfung die Testsuite enthält. (Ich finde es schwierig, keine Fehler in Testsuiten zu schreiben ... manchmal muss ich den Test intern testen (Sanity Check) :)).
quelle
Angenommen, Entwickler A hat Code mit einem Fehler geschrieben. Der Code stimmt nicht genau mit dem überein, was er tun soll, aber etwas anderes. Entwickler B hat Code geschrieben, der darauf beruhte, dass der Code von A genau das tat, was er tun sollte, und der Code von B funktioniert nicht. B untersucht, findet das falsche Verhalten im Code von A und behebt es.
In der Zwischenzeit funktionierte der Code von Entwickler C nur richtig, weil er sich auf das falsche Verhalten von Code A stützte. Der Code von A ist jetzt korrekt. Und der Code von C funktioniert nicht mehr. Das heißt, wenn Sie Code reparieren, müssen Sie sehr sorgfältig prüfen, wer diesen Code verwendet und wie sich ihr Verhalten mit dem festen Code ändert.
Ich hatte eine andere Situation: In einer Situation X hat sich ein Code schlecht verhalten und ein Feature vollständig funktionsunfähig gemacht. Daher habe ich das Fehlverhalten geändert und das Feature funktionsfähig gemacht. Der unglückliche Nebeneffekt war, dass das gesamte Feature in Situation X erhebliche Probleme hatte und überall versagte - dies war niemandem bekannt, da die Situation noch nie zuvor aufgetreten war. Nun, das ist hart.
quelle