Was ist der beste Weg, um eine große Datei umzugestalten?

41

Ich arbeite derzeit an einem größeren Projekt, das leider einige Dateien enthält, in denen die Softwarequalitätsrichtlinien nicht immer befolgt wurden. Dies umfasst große Dateien (zum Lesen von 2000-4000 Zeilen), die eindeutig mehrere unterschiedliche Funktionen enthalten.

Jetzt möchte ich diese großen Dateien in mehrere kleine umgestalten. Das Problem ist, dass, da sie so groß sind, mehrere Personen (ich eingeschlossen) in verschiedenen Zweigen an diesen Dateien arbeiten. Ich kann mich also nicht wirklich vom Entwickeln und Umgestalten trennen, da es schwierig wird, diese Umgestaltungen mit den Änderungen anderer Leute zu kombinieren.

Wir könnten natürlich verlangen, dass alle Dateien zusammengeführt werden, um sie zu entwickeln, "einzufrieren" (dh niemandem mehr zu erlauben, sie zu bearbeiten), umzugestalten und dann "aufzufrieren". Aber das ist auch nicht wirklich gut, da dies bedeuten würde, dass jeder seine Arbeit an diesen Dateien grundsätzlich einstellen müsste, bis das Refactoring abgeschlossen ist.

Gibt es also eine Möglichkeit, umzugestalten, nicht zu lange aufzuhören oder die Feature-Zweige wieder zusammenzuführen, um sie zu entwickeln?

Hoff
quelle
6
Ich denke, das hängt auch von der verwendeten Programmiersprache ab.
Robert Andrzejuk
8
Ich mag "kleine inkrementelle" Checkins. Diese Vorgehensweise minimiert Zusammenführungskonflikte für alle, es sei denn, jemand hält seine Kopie des Repos nicht frisch.
Matt Raffel
5
Wie sehen Ihre Tests aus? Wenn Sie einen großen (und wahrscheinlich wichtigen!) Teil des Codes überarbeiten möchten, stellen Sie sicher, dass sich Ihre Testsuite in einem wirklich guten Zustand befindet, bevor Sie überarbeiten. Dadurch wird es viel einfacher, sicherzustellen, dass Sie in den kleineren Dateien richtig liegen.
CorsiKa
1
Es gibt zahlreiche Ansätze, die Sie mit diesem Ansatz verfolgen können. Der beste Ansatz hängt von Ihrer Situation ab.
Stephen
3
Ich habe mich dem Projekt angeschlossen, bei dem die größte Datei 10.000 Zeilen lang ist und unter anderem eine Klasse enthält, die selbst 6.000 Zeilen lang ist und die jeder nicht anfassen kann. Was ich meine ist, dass Ihre Frage großartig ist. Wir haben sogar einen Witz erfunden, dass diese eine Klasse ein guter Grund ist, das Scrollrad in unseren Mäusen zu entsperren.
ElmoVanKielmo

Antworten:

41

Sie haben richtig verstanden, dass dies weniger ein technisches als ein soziales Problem ist: Wenn Sie übermäßige Zusammenführungskonflikte vermeiden möchten, muss das Team so zusammenarbeiten, dass diese Konflikte vermieden werden.

Dies ist Teil eines größeren Problems mit Git, da das Verzweigen sehr einfach ist, das Zusammenführen jedoch noch viel Aufwand verursachen kann. Entwicklungsteams neigen dazu, viele Zweige zu starten, und sind dann überrascht, dass das Zusammenführen schwierig ist, möglicherweise weil sie versuchen, den Git Flow zu emulieren, ohne dessen Kontext zu verstehen.

Die allgemeine Regel für schnelle und einfache Zusammenführungen besteht darin, zu verhindern, dass sich große Unterschiede ansammeln, insbesondere, dass Feature-Zweige sehr kurzlebig sein sollten (Stunden oder Tage, nicht Monate). Ein Entwicklungsteam, das in der Lage ist, Änderungen schnell zu integrieren, kann weniger Zusammenführungskonflikte feststellen. Wenn ein Code noch nicht produktionsbereit ist, kann er möglicherweise integriert, aber über ein Feature-Flag deaktiviert werden. Sobald der Code in Ihre Hauptniederlassung integriert wurde, ist er für die Art von Refactoring verfügbar, die Sie durchführen möchten.

Das könnte zu viel für Ihr unmittelbares Problem sein. Möglicherweise ist es jedoch möglich, Kollegen aufzufordern, ihre Änderungen, die sich auf diese Datei auswirken, bis zum Ende der Woche zusammenzuführen, damit Sie das Refactoring durchführen können. Wenn sie länger warten, müssen sie sich selbst mit den Zusammenführungskonflikten befassen. Das ist nicht unmöglich, es ist nur vermeidbare Arbeit.

Möglicherweise möchten Sie auch verhindern, dass große Teile des abhängigen Codes beschädigt werden, und nur API-kompatible Änderungen vornehmen. Wenn Sie beispielsweise einige Funktionen in ein separates Modul extrahieren möchten:

  1. Extrahieren Sie die Funktionalität in ein separates Modul.
  2. Ändern Sie die alten Funktionen, um ihre Aufrufe an die neue API weiterzuleiten.
  3. Im Laufe der Zeit portabhängiger Code zur neuen API.
  4. Schließlich können Sie die alten Funktionen löschen.
  5. (Wiederholen Sie dies für den nächsten Funktionsumfang.)

Dieser mehrstufige Prozess kann viele Zusammenführungskonflikte vermeiden. Insbesondere kommt es nur dann zu Konflikten, wenn jemand anderes die von Ihnen extrahierten Funktionen ebenfalls ändert. Die Kosten dieses Ansatzes liegen darin, dass er viel langsamer ist als das gleichzeitige Ändern von allem und dass Sie vorübergehend über zwei doppelte APIs verfügen. Das ist nicht so schlimm, bis etwas Dringendes dieses Refactoring unterbricht, die Vervielfältigung vergessen oder vorrangig wird und Sie am Ende eine Menge technischer Schulden haben.

Letztendlich müssen Sie sich für jede Lösung mit Ihrem Team abstimmen.

amon
quelle
1
@Laiv Das ist leider alles ein äußerst allgemeiner Rat, aber einige Ideen aus dem agilen Raum wie Continuous Integration haben eindeutig ihre Vorzüge. Teams, die zusammenarbeiten (und ihre Arbeit häufig integrieren), haben es leichter, umfangreiche übergreifende Änderungen vorzunehmen, als Teams, die nur nebeneinander arbeiten. Hier geht es nicht unbedingt um den SDLC im Allgemeinen, sondern um die Zusammenarbeit innerhalb des Teams. Einige Ansätze machen eine Zusammenarbeit praktikabler (siehe Open / Closed Principle, Microservices), aber das OP-Team ist noch nicht da.
amon
22
Ich würde nicht so weit gehen zu sagen, dass ein Feature-Zweig eine kurze Lebensdauer haben muss - nur, dass er nicht für längere Zeit von seinem übergeordneten Zweig abweichen sollte. Das regelmäßige Zusammenführen von Änderungen aus dem übergeordneten Zweig in den Feature-Zweig funktioniert in den Fällen, in denen der Feature-Zweig länger bestehen bleiben muss. Trotzdem ist es eine gute Idee, Feature-Zweige nicht länger als nötig zu behalten.
Dan Lyons
1
@Laiv Nach meiner Erfahrung ist es sinnvoll, ein Post-Refactoring-Design im Voraus mit dem Team zu besprechen. In der Regel ist es jedoch am einfachsten, wenn eine einzelne Person Änderungen am Code vornimmt. Andernfalls kehren Sie zu dem Problem zurück, dass Sie Dinge zusammenführen müssen. Die 4k-Linien klingen viel, aber es ist wirklich nicht für gezielte Refactorings wie Extract-Class . (Ich würde das Refactoring-Buch von Martin Fowler hier so schwer fassen, wenn ich es gelesen hätte.) Aber 4-k- Zeilen sind viel nur für ungezielte Refactorings wie „Mal sehen, wie ich das verbessern kann“.
amon
1
@DanLyons Grundsätzlich haben Sie recht: Das kann einen Teil des Aufwands für das Zusammenführen aufteilen. In der Praxis hängt das Zusammenführen von Git stark vom letzten gemeinsamen Ahnen-Commit der zusammengeführten Zweige ab. Durch das Zusammenführen von master → feature erhalten wir keinen neuen gemeinsamen Vorfahren für master, durch das Zusammenführen von feature → master jedoch. Bei wiederholten Zusammenführungen von Master- und Features kann es vorkommen, dass wir dieselben Konflikte immer wieder lösen müssen (siehe git rerere, um dies zu automatisieren). Rebasing ist hier strikt überlegen, da die Spitze des Meisters der neue gemeinsame Vorfahr wird, aber das Umschreiben der Geschichte hat andere Probleme.
amon
1
Die Antwort ist OK für mich, bis auf das Gerede über Git, das es zu einfach macht, sich zu verzweigen, und daher verzweigt sich devs zu oft. Ich erinnere mich gut an die Zeiten von SVN und sogar CVS, in denen das Verzweigen so schwer (oder zumindest umständlich) war, dass die Leute es mit all den damit verbundenen Problemen im Allgemeinen vermieden haben. In git ist es nichts anderes , ein verteiltes System zu sein und viele Zweige zu haben, als überhaupt viele getrennte Repositorys (dh auf jedem Entwickler). Die Lösung liegt an einer anderen Stelle. Einfach zu verzweigen ist nicht das Problem. (Und ja, ich sehe, dass das nur eine Seite ist ... aber immer noch).
AnoE
30

Führen Sie das Refactoring in kleineren Schritten durch. Angenommen, Ihre große Datei hat den Namen Foo:

  1. Fügen Sie eine neue leere Datei hinzu Barund übergeben Sie sie an "trunk".

  2. Suchen Sie einen kleinen Teil des Codes, in Fooden verschoben werden kann Bar. Wenden Sie den Umzug an, aktualisieren Sie vom Trunk aus, erstellen und testen Sie den Code und übergeben Sie ihn an "trunk".

  3. Wiederholen Sie Schritt 2 bis Foound Barhaben Sie die gleiche Größe (oder welche Größe Sie bevorzugen)

Auf diese Weise erhalten Ihre Teammitglieder das nächste Mal, wenn sie ihre Zweige aus dem Stamm aktualisieren, Ihre Änderungen in "kleinen Portionen" und können sie einzeln zusammenführen. Dies ist viel einfacher, als eine vollständige Aufteilung in einem Schritt zusammenführen zu müssen. Dasselbe gilt, wenn in Schritt 2 ein Zusammenführungskonflikt auftritt, weil ein anderer Trunk zwischenzeitlich aktualisiert wurde.

Dadurch werden Zusammenführungskonflikte oder die Notwendigkeit, sie manuell zu lösen, nicht beseitigt, aber jeder Konflikt wird auf einen kleinen Codebereich beschränkt, der viel besser zu handhaben ist.

Und natürlich - das Refactoring im Team kommunizieren. Informieren Sie Ihre Kollegen darüber, was Sie tun, damit sie wissen, warum sie mit Zusammenführungskonflikten für die jeweilige Datei rechnen müssen.

Doc Brown
quelle
2
Dies ist besonders nützlich, wenn die rerereOption gits aktiviert ist
D. Ben Knoble,
@ D.BenKnoble: danke für den zusatz. Ich muss zugeben, ich bin kein Git-Experte (aber das beschriebene Problem ist nicht spezifisch für Git. Es gilt für jedes VCS, das Verzweigungen zulässt, und meine Antwort sollte für die meisten dieser Systeme passen.)
Doc Brown
Ich dachte, basierend auf der Terminologie; Tatsächlich wird diese Art der Zusammenführung mit git nur einmal durchgeführt (wenn man nur zieht und zusammenführt). Aber man kann immer einzelne Commits ziehen und auswählen, zusammenführen oder nach Belieben des Entwicklers neu definieren. Es dauert länger, ist aber durchaus machbar, wenn das automatische Zusammenführen wahrscheinlich fehlschlägt.
D. Ben Knoble
18

Sie möchten die Datei als atomare Operation aufteilen, aber Sie können zwischenzeitlich Änderungen vornehmen. Die Datei wurde mit der Zeit allmählich riesig und kann mit der Zeit allmählich kleiner werden.

Wählen Sie ein Teil aus, das sich seit langem nicht mehr geändert hat ( git blamekann dabei helfen), und teilen Sie es zuerst ab. Lassen Sie diese Änderung in allen Zweigen zusammenführen und wählen Sie dann das nächst einfachere Teil aus, das Sie aufteilen möchten. Vielleicht ist sogar das Teilen eines Teils ein zu großer Schritt, und Sie sollten zunächst einige Änderungen an der großen Datei vornehmen.

Wenn sich die Leute nicht häufig zusammenschließen, um sich weiterzuentwickeln, sollten Sie dazu ermutigen, nach dem Zusammenschluss die Gelegenheit zu nutzen, um die Teile abzuspalten, die sie gerade geändert haben. Oder fordern Sie sie auf, die Abspaltung im Rahmen der Pull-Request-Überprüfung vorzunehmen.

Die Idee ist, sich langsam Ihrem Ziel zu nähern. Es wird sich so anfühlen, als sei der Fortschritt langsam, aber dann werden Sie plötzlich feststellen, dass Ihr Code viel besser ist. Es dauert lange, einen Ozeandampfer zu drehen.

Karl Bielefeldt
quelle
Die Datei hat möglicherweise zu groß begonnen. Dateien dieser Größe können schnell erstellt werden. Ich kenne Leute, die an einem Tag oder in einer Woche 1000 LoC schreiben können. Und OP erwähnte keine automatisierten Tests, was mir anzeigt, dass sie fehlen.
ChuckCottrill
9

Ich werde eine andere als die normale Lösung für dieses Problem vorschlagen.

Verwenden Sie dies als Teamcode-Ereignis. Lassen Sie jeden seinen Code einchecken, der dazu in der Lage ist, und dann anderen helfen, die noch mit der Datei arbeiten. Wenn alle relevanten Personen ihren Code eingecheckt haben, suchen Sie sich einen Konferenzraum mit einem Projektor und arbeiten Sie zusammen, um Dinge in neue Dateien zu verschieben.

Möglicherweise möchten Sie dafür eine bestimmte Zeit festlegen, damit es sich nicht um eine Woche voller Argumente handelt, für die kein Ende in Sicht ist. Stattdessen kann es sich auch um eine wöchentliche Veranstaltung von 1 bis 2 Stunden handeln, bis Sie alle wissen, wie es sein muss. Möglicherweise benötigen Sie nur 1-2 Stunden, um die Datei umzugestalten. Sie werden es wahrscheinlich erst wissen, wenn Sie es versuchen.

Dies hat den Vorteil, dass sich alle Benutzer beim Refactoring auf derselben Seite befinden (kein Wortspiel beabsichtigt). Es kann Ihnen jedoch auch dabei helfen, Fehler zu vermeiden und bei Bedarf von anderen Eingaben zu möglichen zu pflegenden Methodengruppierungen zu erhalten.

Wenn Sie dies auf diese Weise tun, können Sie davon ausgehen, dass Sie über eine integrierte Codeüberprüfung verfügen. Auf diese Weise kann die entsprechende Anzahl von Entwicklern Ihren Code abmelden, sobald Sie ihn eingecheckt haben und zur Überprüfung bereit sind. Möglicherweise möchten sie weiterhin, dass der Code auf verpasste Inhalte überprüft wird, aber dies trägt wesentlich dazu bei, dass der Überprüfungsprozess kürzer wird.

Dies funktioniert möglicherweise nicht in allen Situationen, Teams oder Unternehmen, da die Arbeit nicht so verteilt ist, dass dies problemlos möglich ist. Es kann auch (fälschlicherweise) als Missbrauch der Entwicklungszeit ausgelegt werden. Dieser Gruppencode muss vom Manager sowie vom Refactor selbst bestätigt werden.

Erwähnen Sie das Codeüberprüfungsbit und alle, die von Anfang an wissen, wo sich diese Idee befindet, um Ihren Vorgesetzten beim Verkauf dieser Idee zu unterstützen. Es lohnt sich zu vermeiden, dass Entwickler beim Durchsuchen einer Vielzahl neuer Dateien Zeit verlieren. Außerdem ist es in der Regel eine gute Sache, Entwickler daran zu hindern, sich darüber zu ärgern, wo Dinge gelandet sind oder "komplett fehlen". (Je weniger die Kernschmelzen, desto besser, IMO.)

Sobald Sie eine Datei auf diese Weise überarbeitet haben, können Sie möglicherweise leichter die Genehmigung für weitere Überarbeitungen erhalten, wenn dies erfolgreich und nützlich war.

Wie auch immer Sie sich entscheiden, viel Glück!

computercarguy
quelle
Dies ist ein fantastischer Vorschlag, der einen wirklich guten Weg beschreibt, um die Team-Koordination zu erreichen, die für das Funktionieren entscheidend sein wird. Wenn einige der Zweige nicht zum masterersten zusammengeführt werden können, haben Sie mindestens alle im Raum, um die Zusammenführung in diesen Zweigen zu bewältigen.
Colin Young
+1 für den Vorschlag der Code Mob
Jon Raynor
1
Dies spricht genau den sozialen Aspekt des Problems an.
ChuckCottrill
4

Um dieses Problem zu beheben, müssen sich die anderen Teams anmelden, da Sie versuchen, eine freigegebene Ressource (den Code selbst) zu ändern. Davon abgesehen gibt es meiner Meinung nach eine Möglichkeit, von riesigen monolithischen Dateien "wegzuwandern", ohne die Menschen zu stören.

Ich würde auch empfehlen, nicht alle großen Dateien auf einmal auszurichten, es sei denn, die Anzahl der großen Dateien wächst unkontrolliert zusätzlich zur Größe der einzelnen Dateien.

Das Umgestalten so großer Dateien führt häufig zu unerwarteten Problemen. Der erste Schritt besteht darin, zu verhindern, dass die großen Dateien zusätzliche Funktionen ansammeln, die über das hinausgehen, was derzeit im Master oder in Entwicklungszweigen vorhanden ist .

Ich denke, der beste Weg, dies zu tun, ist mit Commit-Hooks, die bestimmte Zusätze zu den großen Dateien standardmäßig blockieren, aber mit einem magischen Kommentar in der Commit-Nachricht, wie @bigfileokoder so etwas , außer Kraft gesetzt werden können . Es ist wichtig, die Richtlinie auf eine Weise außer Kraft setzen zu können, die schmerzlos, aber nachvollziehbar ist. Im Idealfall sollten Sie den Commit-Hook lokal ausführen können und erfahren, wie Sie diesen bestimmten Fehler in der Fehlermeldung selbst überschreiben können . Dies ist auch nur meine Präferenz, aber nicht erkannte magische Kommentare oder magische Kommentare, die Fehler unterdrücken, die in der Festschreibungsnachricht nicht ausgelöst wurden, sollten eine Warnung oder ein Fehler zur Festschreibungszeit sein, damit Sie nicht versehentlich trainieren, die Haken zu unterdrücken, unabhängig davon ob sie müssen oder nicht.

Der Commit-Hook könnte nach neuen Klassen suchen oder eine andere statische Analyse durchführen (Ad-hoc oder nicht). Sie können auch einfach eine Zeilen- oder Zeichenzahl auswählen, die 10% größer ist als die aktuelle Datei, und sagen, dass die große Datei nicht über das neue Limit hinauswachsen kann. Sie können auch einzelne Festschreibungen ablehnen, bei denen die große Datei um zu viele Zeilen oder zu viele Zeichen oder w / w vergrößert wird.

Sobald die große Datei keine neuen Funktionen mehr ansammelt, können Sie nacheinander Änderungen daran vornehmen (und gleichzeitig die von den Festschreibungs-Hooks erzwungenen Schwellenwerte verringern, um ein erneutes Anwachsen der Datei zu verhindern).

Schließlich sind die großen Dateien so klein, dass die Commit-Hooks vollständig entfernt werden können.

Gregory Nisbet
quelle
-3

Warten Sie bis zur Hometime. Teilen Sie die Datei, übertragen Sie sie und führen Sie sie zum Master zusammen.

Andere Benutzer müssen die Änderungen am Morgen wie jede andere Änderung in ihre Feature-Zweige übernehmen.

Ewan
quelle
3
Trotzdem würde es bedeuten, dass sie meine Refactorings mit ihren Änderungen zusammenführen müssten ...
Hoff,
1
Nun, sie müssen sich sowieso mit Zusammenführungen befassen, wenn sie alle diese Dateien ändern.
Laiv
9
Dies hat das Problem "Überraschung, ich habe alle deine Sachen kaputt gemacht." Das OP muss vor diesem Vorgang ein Buy-in und eine Genehmigung einholen und dies zu einem geplanten Zeitpunkt, zu dem niemand anderes die Datei "in Bearbeitung" hat, tun, um Abhilfe zu schaffen.
Computercarguy
6
Aus Liebe zu Cthulhu, tu das nicht. Es ist ungefähr die schlechteste Art, in einem Team zu arbeiten.
Leichtigkeit Rennen mit Monica