Wir haben also diese riesige Quelldatei (ist 11000 Zeilen groß?) Mainmodule.cpp in unserem Projekt und jedes Mal, wenn ich sie berühren muss, schaudere ich.
Da diese Datei so zentral und groß ist, sammelt sie immer mehr Code an, und ich kann mir keinen guten Weg vorstellen, um sie tatsächlich schrumpfen zu lassen.
Die Datei wird in mehreren (> 10) Wartungsversionen unseres Produkts verwendet und aktiv geändert. Daher ist es sehr schwierig, sie zu überarbeiten. Wenn ich es "einfach", sagen wir zunächst einmal, in 3 Dateien aufteilen würde, würde das Zusammenführen von Änderungen aus Wartungsversionen zu einem Albtraum. Und auch wenn Sie eine Datei mit einem so langen und umfangreichen Verlauf aufteilen, SCC
wird es plötzlich viel schwieriger , alte Änderungen im Verlauf zu verfolgen und zu überprüfen .
Die Datei enthält im Wesentlichen die "Hauptklasse" (interne interne Arbeitsverteilung und -koordination) unseres Programms. Jedes Mal, wenn ein Feature hinzugefügt wird, wirkt sich dies auch auf diese Datei aus und jedes Mal, wenn sie wächst. :-(
Was würden Sie in dieser Situation tun? Haben Sie Ideen, wie Sie neue Funktionen in eine separate Quelldatei verschieben können, ohne den SCC
Workflow zu beeinträchtigen?
(Hinweis zu den Tools: Wir verwenden C ++ mit Visual Studio
; Wir verwenden AccuRev
als, SCC
aber ich denke, die Art von SCC
spielt hier keine Rolle. Wir verwenden Araxis Merge
den tatsächlichen Vergleich und das Zusammenführen von Dateien.)
quelle
Antworten:
Suchen Sie in der Datei nach Code, der relativ stabil ist (sich nicht schnell ändert und zwischen den Zweigen nicht sehr unterschiedlich ist) und als unabhängige Einheit fungieren kann. Verschieben Sie dies in eine eigene Datei und in alle Zweige in eine eigene Klasse. Da es stabil ist, führt dies nicht zu (vielen) "umständlichen" Zusammenführungen, die auf eine andere Datei angewendet werden müssen als die, für die sie ursprünglich erstellt wurden, wenn Sie die Änderung von einem Zweig zu einem anderen zusammenführen. Wiederholen.
Suchen Sie in der Datei nach Code, der im Grunde nur für eine kleine Anzahl von Zweigen gilt und für sich allein stehen kann. Es spielt keine Rolle, ob es sich schnell ändert oder nicht, aufgrund der geringen Anzahl von Zweigen. Verschieben Sie dies in eigene Klassen und Dateien. Wiederholen.
Wir haben also den Code entfernt, der überall gleich ist, und den Code, der für bestimmte Zweige spezifisch ist.
Dadurch erhalten Sie einen Kern von schlecht verwaltetem Code - er wird überall benötigt, ist jedoch in jedem Zweig unterschiedlich (und / oder ändert sich ständig, sodass einige Zweige hinter anderen ausgeführt werden), und dennoch befinden Sie sich in einer einzigen Datei erfolglos versucht, zwischen Zweigen zusammenzuführen. Hör auf damit. Verzweigen Sie die Datei dauerhaft , möglicherweise indem Sie sie in jedem Zweig umbenennen. Es ist nicht mehr "main", sondern "main for configuration X". OK, Sie verlieren also die Möglichkeit, dieselbe Änderung durch Zusammenführen auf mehrere Zweige anzuwenden. Dies ist jedoch in jedem Fall der Kern des Codes, in dem das Zusammenführen nicht sehr gut funktioniert. Wenn Sie die Zusammenführungen ohnehin manuell verwalten müssen, um Konflikte zu lösen, ist es kein Verlust, sie manuell unabhängig auf jeden Zweig anzuwenden.
Ich denke, Sie können zu Unrecht sagen, dass die Art des SCC keine Rolle spielt, da beispielsweise die Zusammenführungsfähigkeiten von git wahrscheinlich besser sind als das von Ihnen verwendete Zusammenführungswerkzeug. Das Kernproblem "Zusammenführen ist schwierig" tritt also zu unterschiedlichen Zeiten für unterschiedliche SCCs auf. Es ist jedoch unwahrscheinlich, dass Sie SCCs ändern können, sodass das Problem wahrscheinlich irrelevant ist.
quelle
Das Zusammenführen wird kein so großer Albtraum sein, wie es sein wird, wenn Sie in Zukunft 30000 LOC-Dateien erhalten. So:
Wenn Sie nicht können Codierung während Refactoring Prozesses stoppen, könnten Sie diese große Datei verlassen wie für eine Weile zumindest ohne mehr Code , um es fügt hinzu: da es eine „Hauptklasse“ enthält könnte man von ihm erben und vererbten Klasse halten ( es) mit überladenen Funktionen in mehreren neuen kleinen und gut gestalteten Dateien.
quelle
Es hört sich für mich so an, als ob Sie hier einer Reihe von Code-Gerüchen ausgesetzt sind. Zunächst scheint die Hauptklasse gegen das Open / Closed-Prinzip zu verstoßen . Es klingt auch so, als würde es zu viele Verantwortlichkeiten übernehmen . Aus diesem Grund würde ich annehmen, dass der Code spröder ist, als er sein muss.
Obwohl ich Ihre Bedenken hinsichtlich der Rückverfolgbarkeit nach einem Refactoring verstehen kann, würde ich erwarten, dass diese Klasse nur schwer zu warten und zu verbessern ist und dass Änderungen, die Sie vornehmen, wahrscheinlich Nebenwirkungen verursachen. Ich würde annehmen, dass die Kosten für diese die Kosten für die Umgestaltung der Klasse überwiegen.
Da sich die Code-Gerüche mit der Zeit nur verschlechtern, überwiegen zumindest irgendwann die Kosten für diese die Kosten für das Refactoring. Aus Ihrer Beschreibung würde ich annehmen, dass Sie den Wendepunkt überschritten haben.
Das Refactoring sollte in kleinen Schritten erfolgen. Fügen Sie nach Möglichkeit automatisierte Tests hinzu, um das aktuelle Verhalten zu überprüfen, bevor Sie etwas umgestalten. Wählen Sie dann kleine Bereiche mit isolierten Funktionen aus und extrahieren Sie diese als Typen, um die Verantwortung zu delegieren.
Auf jeden Fall klingt es wie ein großes Projekt, also viel Glück :)
quelle
Die einzige Lösung, die ich mir jemals für solche Probleme vorgestellt habe, folgt. Der tatsächliche Gewinn durch das beschriebene Verfahren ist die Progressivität der Entwicklungen. Keine Revolutionen hier, sonst werden Sie sehr schnell in Schwierigkeiten geraten.
Fügen Sie eine neue cpp-Klasse über der ursprünglichen Hauptklasse ein. Im Moment würde es im Grunde alle Aufrufe an die aktuelle Hauptklasse umleiten, aber darauf abzielen, die API dieser neuen Klasse so klar und prägnant wie möglich zu gestalten.
Sobald dies geschehen ist, haben Sie die Möglichkeit, neue Funktionen in neuen Klassen hinzuzufügen.
Bestehende Funktionen müssen schrittweise in neue Klassen verschoben werden, da sie stabil genug sind. Sie werden die SCC-Hilfe für diesen Code verlieren, aber es kann nicht viel dagegen getan werden. Wählen Sie einfach das richtige Timing.
Ich weiß, dass dies nicht perfekt ist, obwohl ich hoffe, dass es helfen kann, und der Prozess muss an Ihre Bedürfnisse angepasst werden!
Zusätzliche Information
Beachten Sie, dass Git ein SCC ist, der Codeteile von einer Datei zur anderen verfolgen kann. Ich habe gute Dinge darüber gehört, daher könnte es helfen, während Sie Ihre Arbeit schrittweise verschieben.
Git basiert auf dem Begriff der Blobs, die, wenn ich das richtig verstehe, Teile von Codedateien darstellen. Bewegen Sie diese Teile in verschiedenen Dateien und Git findet sie, auch wenn Sie sie ändern. Abgesehen von dem Video von Linus Torvalds, das in den Kommentaren unten erwähnt wurde, konnte ich nichts klares darüber finden.
quelle
Konfuzius sagt: "Der erste Schritt, um aus dem Loch herauszukommen, besteht darin, das Graben des Lochs zu beenden."
quelle
Lassen Sie mich raten: Zehn Kunden mit unterschiedlichen Funktionen und einem Vertriebsleiter, der die "Anpassung" fördert? Ich habe schon früher an solchen Produkten gearbeitet. Wir hatten im Wesentlichen das gleiche Problem.
Sie erkennen, dass eine riesige Datei ein Problem ist, aber noch mehr Probleme sind zehn Versionen, die Sie "aktuell" halten müssen. Das ist mehrfache Wartung. SCC kann das einfacher machen, aber es kann es nicht richtig machen.
Bevor Sie versuchen, die Datei in Teile zu zerlegen, müssen Sie die zehn Zweige wieder synchronisieren, damit Sie den gesamten Code auf einmal sehen und formen können. Sie können diesen Zweig einzeln ausführen und beide Zweige mit derselben Hauptcodedatei testen. Um das benutzerdefinierte Verhalten zu erzwingen, können Sie #ifdef und friends verwenden. Es ist jedoch besser, gewöhnliches if / else für definierte Konstanten zu verwenden. Auf diese Weise überprüft Ihr Compiler alle Typen und eliminiert höchstwahrscheinlich ohnehin "toten" Objektcode. (Möglicherweise möchten Sie jedoch die Warnung vor totem Code deaktivieren.)
Sobald es nur eine Version dieser Datei gibt, die implizit von allen Zweigen gemeinsam genutzt wird, ist es einfacher, mit herkömmlichen Refactoring-Methoden zu beginnen.
Die #ifdefs eignen sich hauptsächlich für Abschnitte, in denen der betroffene Code nur im Zusammenhang mit anderen Anpassungen pro Zweig sinnvoll ist. Man kann argumentieren, dass diese auch eine Chance für das gleiche Zweigverschmelzungsschema darstellen, aber nicht wild werden. Bitte jeweils ein kolossales Projekt.
Kurzfristig scheint die Datei zu wachsen. Das ist in Ordnung. Was Sie tun, ist Dinge zusammenzubringen, die zusammen sein müssen. Danach sehen Sie Bereiche, die unabhängig von der Version eindeutig identisch sind. Diese können in Ruhe gelassen oder nach Belieben überarbeitet werden. Andere Bereiche unterscheiden sich je nach Version deutlich. In diesem Fall haben Sie eine Reihe von Optionen. Eine Methode besteht darin, die Unterschiede an Strategieobjekte pro Version zu delegieren. Eine andere Möglichkeit besteht darin, Client-Versionen von einer gemeinsamen abstrakten Klasse abzuleiten. Aber keine dieser Transformationen ist möglich, solange Sie zehn "Entwicklungstipps" in verschiedenen Branchen haben.
quelle
Ich weiß nicht, ob dies Ihr Problem löst, aber ich denke, Sie möchten den Inhalt der Datei unabhängig voneinander in kleinere Dateien migrieren (zusammengefasst). Was ich auch bekomme ist, dass Sie ungefähr 10 verschiedene Versionen der Software haben und Sie müssen sie alle unterstützen, ohne die Dinge durcheinander zu bringen.
Erstens gibt es einfach keine Möglichkeit, dass dies einfach ist und sich in wenigen Minuten Brainstorming von selbst lösen wird. Die in Ihrer Datei verknüpften Funktionen sind für Ihre Anwendung von entscheidender Bedeutung. Wenn Sie sie einfach ausschneiden und in andere Dateien migrieren, wird Ihr Problem nicht behoben.
Ich denke, Sie haben nur folgende Möglichkeiten:
Migrieren Sie nicht und bleiben Sie bei dem, was Sie haben. Kündigen Sie möglicherweise Ihren Job und beginnen Sie mit der Arbeit an seriöser Software mit gutem Design. Extreme Programmierung ist nicht immer die beste Lösung, wenn Sie an einem Langzeitprojekt mit genügend Geld arbeiten, um ein oder zwei Abstürze zu überstehen.
Erarbeiten Sie ein Layout, wie Ihre Datei nach dem Aufteilen aussehen soll. Erstellen Sie die erforderlichen Dateien und integrieren Sie sie in Ihre Anwendung. Benennen Sie die Funktionen um oder überladen Sie sie, um einen zusätzlichen Parameter zu übernehmen (möglicherweise nur einen einfachen Booleschen Wert?). Wenn Sie an Ihrem Code arbeiten müssen, migrieren Sie die Funktionen, an denen Sie arbeiten müssen, in die neue Datei und ordnen Sie die Funktionsaufrufe der alten Funktionen den neuen Funktionen zu. Sie sollten Ihre Hauptdatei weiterhin auf diese Weise haben und weiterhin die Änderungen sehen können, die daran vorgenommen wurden, sobald es sich um eine bestimmte Funktion handelt, von der Sie genau wissen, wann sie ausgelagert wurde, und so weiter.
Versuchen Sie, Ihre Mitarbeiter mit einem guten Kuchen davon zu überzeugen, dass der Workflow überbewertet ist und dass Sie einige Teile der Anwendung neu schreiben müssen, um ernsthafte Geschäfte zu machen.
quelle
Genau dieses Problem wird in einem der Kapitel des Buches "Effektiv mit Legacy-Code arbeiten" ( http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 ) behandelt.
quelle
Ich denke, Sie sollten am besten eine Reihe von Befehlsklassen erstellen , die den API-Punkten der Datei mainmodule.cpp zugeordnet sind.
Sobald sie vorhanden sind, müssen Sie die vorhandene Codebasis umgestalten, um über die Befehlsklassen auf diese API-Punkte zugreifen zu können. Sobald dies erledigt ist, können Sie die Implementierung jedes Befehls in eine neue Klassenstruktur umgestalten.
Natürlich ist der Code in einer einzelnen Klasse von 11 KLOC wahrscheinlich stark gekoppelt und spröde, aber das Erstellen einzelner Befehlsklassen hilft viel mehr als jede andere Proxy- / Fassadenstrategie.
Ich beneide die Aufgabe nicht, aber mit der Zeit wird sich dieses Problem nur verschlimmern, wenn es nicht angegangen wird.
Aktualisieren
Ich würde vorschlagen, dass das Befehlsmuster einer Fassade vorzuziehen ist.
Das Verwalten / Organisieren vieler verschiedener Befehlsklassen über eine (relativ) monolithische Fassade ist vorzuziehen. Das Zuordnen einer einzelnen Fassade zu einer 11 KLOC-Datei muss wahrscheinlich selbst in einige verschiedene Gruppen unterteilt werden.
Warum sollte man sich die Mühe machen, diese Fassadengruppen herauszufinden? Mit dem Befehlsmuster können Sie diese kleinen Klassen organisch gruppieren und organisieren, sodass Sie viel mehr Flexibilität haben.
Natürlich sind beide Optionen besser als die einzelne 11 KLOC- und wachsende Datei.
quelle
Ein wichtiger Rat: Mischen Sie kein Refactoring und keine Bugfixes. Was Sie wollen, ist eine Version Ihres Programms, die mit der vorherigen Version identisch ist , außer dass der Quellcode anders ist.
Eine Möglichkeit könnte darin bestehen, die am wenigsten große Funktion / das am wenigsten große Teil in eine eigene Datei aufzuteilen und dann entweder mit einem Header einzuschließen (wodurch main.cpp in eine Liste von #includes umgewandelt wird, was an sich nach Code riecht * Ich bin es nicht ein C ++ Guru zwar), aber zumindest ist es jetzt in Dateien aufgeteilt).
Sie können dann versuchen, alle Wartungsversionen auf die "neue" main.cpp oder eine andere Struktur umzustellen. Nochmals: Keine weiteren Änderungen oder Bugfixes, da das Verfolgen dieser Änderungen verdammt verwirrend ist.
Eine andere Sache: So sehr Sie sich wünschen, das Ganze auf einmal zu überarbeiten, können Sie mehr abbeißen, als Sie kauen können. Vielleicht wählen Sie einfach ein oder zwei "Teile" aus, nehmen Sie sie in alle Releases auf, und erhöhen Sie dann den Wert für Ihren Kunden (schließlich bietet Refactoring keinen direkten Mehrwert, sodass die Kosten gerechtfertigt sein müssen) und wählen Sie dann einen anderen aus ein oder zwei Teile.
Offensichtlich erfordert dies etwas Disziplin im Team, um die geteilten Dateien tatsächlich zu verwenden und nicht nur ständig neue Inhalte zur main.cpp hinzuzufügen, sondern auch hier ist der Versuch, einen massiven Refactor durchzuführen, möglicherweise nicht die beste Vorgehensweise.
quelle
Rofl, das erinnert mich an meinen alten Job. Es scheint, dass sich vor meinem Beitritt alles in einer riesigen Datei befand (auch C ++). Dann haben sie es (an völlig zufälligen Stellen mit Includes) in ungefähr drei (immer noch riesige Dateien) aufgeteilt. Die Qualität dieser Software war, wie zu erwarten, schrecklich. Das Projekt belief sich auf rund 40.000 LOC. (enthält fast keine Kommentare, aber viel doppelten Code)
Am Ende habe ich das Projekt komplett neu geschrieben. Ich begann damit, den schlimmsten Teil des Projekts von Grund auf neu zu erstellen. Natürlich hatte ich eine mögliche (kleine) Schnittstelle zwischen diesem neuen Teil und dem Rest im Sinn. Dann habe ich diesen Teil in das alte Projekt eingefügt. Ich habe den alten Code nicht überarbeitet, um die erforderliche Schnittstelle zu erstellen, sondern ihn nur ersetzt. Dann machte ich kleine Schritte von dort und schrieb den alten Code neu.
Ich muss sagen, dass dies ungefähr ein halbes Jahr gedauert hat und es in dieser Zeit keine Entwicklung der alten Codebasis neben Bugfixes gab.
bearbeiten:
Die Größe blieb bei etwa 40.000 LOC, aber die neue Anwendung enthielt in ihrer ursprünglichen Version viel mehr Funktionen und vermutlich weniger Fehler als die 8 Jahre alte Software. Ein Grund für das Umschreiben war auch, dass wir die neuen Funktionen brauchten und es fast unmöglich war, sie in den alten Code einzuführen.
Die Software war für ein eingebettetes System, einen Etikettendrucker.
Ein weiterer Punkt, den ich hinzufügen sollte, ist, dass das Projekt theoretisch C ++ war. Aber es war überhaupt nicht OO, es hätte C sein können. Die neue Version war objektorientiert.
quelle
OK, zum größten Teil ist das Umschreiben der API von Produktionscode zunächst eine schlechte Idee. Zwei Dinge müssen passieren.
Erstens muss Ihr Team tatsächlich entscheiden, ob der Code für die aktuelle Produktionsversion dieser Datei eingefroren werden soll.
Zweitens müssen Sie diese Produktionsversion verwenden und einen Zweig erstellen, der die Builds mithilfe von Vorverarbeitungsanweisungen verwaltet, um die große Datei aufzuteilen. Das Aufteilen der Kompilierung mithilfe von JUST-Präprozessoranweisungen (#ifdefs, #includes, #endifs) ist einfacher als das Neukodieren der API. Es ist definitiv einfacher für Ihre SLAs und den laufenden Support.
Hier können Sie einfach Funktionen ausschneiden, die sich auf ein bestimmtes Subsystem innerhalb der Klasse beziehen, und sie in eine Datei mit dem Namen mainloop_foostuff.cpp einfügen und an der richtigen Stelle in mainloop.cpp einfügen.
ODER
Ein zeitaufwändigerer, aber robusterer Weg wäre es, eine interne Abhängigkeitsstruktur mit doppelter Indirektion zu entwickeln, wie die Dinge aufgenommen werden. Auf diese Weise können Sie die Dinge aufteilen und sich trotzdem um Co-Abhängigkeiten kümmern. Beachten Sie, dass dieser Ansatz eine Positionscodierung erfordert und daher mit entsprechenden Kommentaren gekoppelt werden sollte.
Dieser Ansatz würde Komponenten umfassen, die basierend auf der zu kompilierenden Variante verwendet werden.
Die Grundstruktur besteht darin, dass Ihre mainclass.cpp nach einem Anweisungsblock wie der folgenden eine neue Datei mit dem Namen MainClassComponents.cpp enthält:
Die Primärstruktur der Datei MainClassComponents.cpp dient dazu, Abhängigkeiten innerhalb der Unterkomponenten wie folgt zu ermitteln:
Und jetzt erstellen Sie für jede Komponente eine component_xx.cpp-Datei.
Natürlich verwende ich Zahlen, aber Sie sollten etwas logischeres verwenden, das auf Ihrem Code basiert.
Mit dem Präprozessor können Sie Dinge aufteilen, ohne sich um API-Änderungen kümmern zu müssen, was in der Produktion ein Albtraum ist.
Sobald Sie die Produktion abgeschlossen haben, können Sie tatsächlich an der Neugestaltung arbeiten.
quelle
Nun, ich verstehe deinen Schmerz :) Ich war auch in einigen solchen Projekten und es ist nicht schön. Darauf gibt es keine einfache Antwort.
Ein Ansatz, der für Sie möglicherweise funktioniert, besteht darin, in allen Funktionen sichere Schutzfunktionen hinzuzufügen, dh Argumente und Vor- / Nachbedingungen in Methoden zu überprüfen und schließlich alle Komponententests hinzuzufügen, um die aktuelle Funktionalität der Quellen zu erfassen. Sobald Sie dies haben, sind Sie besser in der Lage, den Code neu zu faktorisieren, da Behauptungen und Fehler auftauchen, die Sie benachrichtigen, wenn Sie etwas vergessen haben.
Manchmal kann es jedoch vorkommen, dass Refactoring mehr Schmerzen als Nutzen bringt. Dann ist es möglicherweise besser, das ursprüngliche Projekt einfach in einem Pseudowartungszustand zu belassen, von vorne zu beginnen und dann schrittweise die Funktionalität des Tieres hinzuzufügen.
quelle
Sie sollten sich nicht mit der Reduzierung der Dateigröße befassen, sondern mit der Reduzierung der Klassengröße. Es kommt fast auf dasselbe an, aber Sie sehen das Problem aus einem anderen Blickwinkel (wie @Brian Rasmussen vorschlägt , scheint Ihre Klasse zu viele Aufgaben zu haben).
quelle
Was Sie haben, ist ein klassisches Beispiel für ein bekanntes Design-Antimuster namens Blob . Nehmen Sie sich etwas Zeit, um den Artikel zu lesen, auf den ich hier verweise, und vielleicht finden Sie etwas Nützliches. Wenn dieses Projekt so groß ist, wie es aussieht, sollten Sie ein Design in Betracht ziehen, um zu verhindern, dass Code entsteht, den Sie nicht kontrollieren können.
quelle
Dies ist keine Antwort auf das große Problem, sondern eine theoretische Lösung für ein bestimmtes Stück davon:
Finden Sie heraus, wo Sie die große Datei in Unterdateien aufteilen möchten. Fügen Sie an jedem dieser Punkte Kommentare in einem speziellen Format ein.
Schreiben Sie ein ziemlich triviales Skript, das die Datei an diesen Stellen in Unterdateien aufteilt. (Möglicherweise sind in den speziellen Kommentaren Dateinamen eingebettet, die das Skript als Anweisungen zum Teilen verwenden kann.) Die Kommentare sollten im Rahmen der Aufteilung beibehalten werden.
Führen Sie das Skript aus. Löschen Sie die Originaldatei.
Wenn Sie aus einem Zweig zusammenführen müssen, erstellen Sie zuerst die große Datei neu, indem Sie die Teile wieder zusammenfügen, die Zusammenführung durchführen und sie dann erneut aufteilen.
Wenn Sie den SCC-Dateiverlauf beibehalten möchten, sollten Sie Ihrem Quellcodeverwaltungssystem am besten mitteilen, dass es sich bei den einzelnen Stückdateien um Kopien des Originals handelt. Dann wird der Verlauf der Abschnitte beibehalten, die in dieser Datei gespeichert wurden, obwohl natürlich auch aufgezeichnet wird, dass große Teile "gelöscht" wurden.
quelle
Eine Möglichkeit, es ohne allzu große Gefahr aufzuteilen, wäre ein historischer Blick auf alle Linienänderungen. Gibt es bestimmte Funktionen, die stabiler sind als andere? Hot Spots der Veränderung, wenn Sie so wollen.
Wenn eine Zeile in einigen Jahren nicht geändert wurde, können Sie sie wahrscheinlich ohne allzu große Sorgen in eine andere Datei verschieben. Ich würde einen Blick auf die Quelle werfen, die mit der letzten Revision versehen ist, die eine bestimmte Zeile berührt hat, und prüfen, ob es Funktionen gibt, die Sie herausziehen könnten.
quelle
Wow, hört sich toll an. Ich denke, es ist einen Versuch wert, Ihrem Chef zu erklären, dass Sie viel Zeit brauchen, um das Biest umzugestalten. Wenn er nicht einverstanden ist, ist das Beenden eine Option.
Was ich jedenfalls vorschlage, ist, die gesamte Implementierung wegzuwerfen und sie in neue Module zu gruppieren. Nennen wir diese "globalen Dienste". Das "Hauptmodul" würde nur an diese Dienste weiterleiten, und JEDER neue Code, den Sie schreiben, verwendet sie anstelle des "Hauptmoduls". Dies sollte in angemessener Zeit möglich sein (da es sich hauptsächlich um Kopieren und Einfügen handelt). Sie können vorhandenen Code nicht beschädigen und können jeweils eine Wartungsversion ausführen. Und wenn Sie noch Zeit haben, können Sie alle alten abhängigen Module neu gestalten, um auch die globalen Dienste zu nutzen.
quelle
Mein Mitgefühl - in meinem vorherigen Job bin ich auf eine ähnliche Situation mit einer Datei gestoßen, die um ein Vielfaches größer war als die, mit der Sie sich befassen müssen. Lösung war:
Die Klassen, die Sie in Schritt 3 erstellen, werden wahrscheinlich mehr Code enthalten, der für ihre neu gelöschte Funktion geeignet ist.
Ich könnte auch hinzufügen:
0: kaufe das Buch von Michael Feathers von über die Arbeit mit Legacy-Code
Leider ist diese Art von Arbeit nur allzu häufig, aber meiner Erfahrung nach ist es von großem Wert, funktionierenden, aber schrecklichen Code inkrementell weniger schrecklich zu machen, während er weiter funktioniert.
quelle
Überlegen Sie, wie Sie die gesamte Anwendung sinnvoller umschreiben können. Schreiben Sie vielleicht einen kleinen Teil davon als Prototyp um, um zu sehen, ob Ihre Idee machbar ist.
Wenn Sie eine praktikable Lösung gefunden haben, überarbeiten Sie die Anwendung entsprechend.
Wenn alle Versuche, eine rationalere Architektur zu erstellen, fehlschlagen, wissen Sie zumindest, dass die Lösung wahrscheinlich darin besteht, die Funktionalität des Programms neu zu definieren.
quelle
Meine 0,05 Eurocent:
Entwerfen Sie das gesamte Durcheinander neu und teilen Sie es unter Berücksichtigung der technischen und geschäftlichen Anforderungen in Subsysteme auf (= viele parallele Wartungspfade mit möglicherweise unterschiedlicher Codebasis für jeden, es besteht offensichtlich ein Bedarf an hoher Modifizierbarkeit usw.).
Analysieren Sie bei der Aufteilung in Subsysteme die Orte, die sich am meisten geändert haben, und trennen Sie diese von den unveränderlichen Teilen. Dies sollte Ihnen die Problemstellen zeigen. Trennen Sie die sich am meisten ändernden Teile in ihre eigenen Module (z. B. DLL), sodass die Modul-API intakt bleibt und Sie BC nicht ständig unterbrechen müssen. Auf diese Weise können Sie bei Bedarf verschiedene Versionen des Moduls für verschiedene Wartungszweige bereitstellen, während der Kern unverändert bleibt.
Das Redesign muss wahrscheinlich ein separates Projekt sein. Der Versuch, es mit einem sich bewegenden Ziel zu tun, funktioniert nicht.
Was den Quellcodeverlauf betrifft, meine Meinung: Vergiss ihn für den neuen Code. Bewahren Sie den Verlauf jedoch irgendwo auf, damit Sie ihn bei Bedarf überprüfen können. Ich wette, Sie werden es nach dem Anfang nicht mehr so oft brauchen.
Sie müssen höchstwahrscheinlich ein Management-Buy-In für dieses Projekt erhalten. Sie können vielleicht mit einer schnelleren Entwicklungszeit, weniger Fehlern, einer einfacheren Wartung und weniger allgemeinem Chaos argumentieren. Etwas in der Art von "Proaktiv die Zukunftssicherheit und Wartbarkeit unserer kritischen Software-Assets ermöglichen" :)
So würde ich zumindest anfangen, das Problem anzugehen.
quelle
Fügen Sie zunächst Kommentare hinzu. In Bezug darauf, wo Funktionen aufgerufen werden und ob Sie Dinge bewegen können. Dies kann Dinge in Bewegung bringen. Sie müssen wirklich beurteilen, wie spröde die Codebasis ist. Verschieben Sie dann gemeinsame Funktionen. Kleine Änderungen auf einmal.
quelle
Ein weiteres Buch, das Sie vielleicht interessant / hilfreich finden, ist Refactoring .
quelle
Ich finde es nützlich (und ich mache es jetzt, obwohl nicht in der Größenordnung, in der Sie sich befinden), Methoden als Klassen zu extrahieren (Methodenobjekt-Refactoring). Die Methoden, die sich in Ihren verschiedenen Versionen unterscheiden, werden zu verschiedenen Klassen, die in eine gemeinsame Basis eingefügt werden können, um das unterschiedliche Verhalten bereitzustellen, das Sie benötigen.
quelle
Ich fand diesen Satz der interessanteste Teil Ihres Beitrags:
> Die Datei wird in mehreren (> 10) Wartungsversionen unseres Produkts verwendet und aktiv geändert. Daher ist es sehr schwierig, sie zu überarbeiten
Zunächst würde ich empfehlen, dass Sie ein Quellcodeverwaltungssystem für die Entwicklung dieser 10+ Wartungsversionen verwenden, das die Verzweigung unterstützt.
Zweitens würde ich zehn Zweige erstellen (einen für jede Ihrer Wartungsversionen).
Ich kann dich schon zusammenzucken fühlen! Aber entweder funktioniert Ihre Quellcodeverwaltung aufgrund fehlender Funktionen nicht für Ihre Situation oder sie wird nicht richtig verwendet.
Nun zu der Branche, an der Sie arbeiten - überarbeiten Sie sie nach Belieben, in der Gewissheit, dass Sie die anderen neun Branchen Ihres Produkts nicht verärgern werden.
Ich wäre ein bisschen besorgt, dass Sie so viel in Ihrer main () -Funktion haben.
In allen Projekten, die ich schreibe, würde ich main () verwenden, um nur die Initialisierung von Kernobjekten durchzuführen - wie ein Simulations- oder Anwendungsobjekt - in diesen Klassen sollte die eigentliche Arbeit fortgesetzt werden.
Ich würde auch ein Anwendungsprotokollierungsobjekt in main für die globale Verwendung im gesamten Programm initialisieren.
Schließlich füge ich im Wesentlichen auch Leckerkennungscode in Präprozessorblöcken hinzu, der sicherstellt, dass er nur in DEBUG-Builds aktiviert ist. Dies ist alles, was ich zu main () hinzufügen würde. Main () sollte kurz sein!
Du sagst das
> Die Datei enthält im Wesentlichen die "Hauptklasse" (interne interne Arbeitsverteilung und -koordination) unseres Programms
Es hört sich so an, als könnten diese beiden Aufgaben in zwei separate Objekte aufgeteilt werden - einen Koordinator und einen Arbeitsverteiler.
Wenn Sie diese aufteilen, können Sie Ihren "SCC-Workflow" durcheinander bringen, aber es scheint, als würde die strikte Einhaltung Ihres SCC-Workflows zu Problemen bei der Softwarewartung führen. Lassen Sie es jetzt fallen und schauen Sie nicht zurück, denn sobald Sie es repariert haben, werden Sie anfangen, ruhig zu schlafen.
Wenn Sie nicht in der Lage sind, die Entscheidung zu treffen, kämpfen Sie mit Ihrem Manager darum - Ihre Anwendung muss überarbeitet werden - und das nach den Geräuschen! Nimm kein Nein als Antwort!
quelle
Wie Sie es beschrieben haben, besteht das Hauptproblem darin, Pre-Split und Post-Split zu unterscheiden und Fehlerbehebungen usw. zusammenzuführen. Es wird nicht so lange dauern, ein Skript in Perl, Ruby usw. fest zu codieren, um den größten Teil des Rauschens aus dem Unterschied zwischen Pre-Split und Post-Split herauszuholen. Tun Sie, was im Umgang mit Geräuschen am einfachsten ist:
Sie können es sogar so machen, dass beim Einchecken die Verkettung ausgeführt wird und Sie etwas vorbereitet haben, das sich von den Einzeldateiversionen unterscheidet.
quelle
quelle
"Die Datei enthält im Wesentlichen die" Hauptklasse "(interne interne Arbeitsverteilung und -koordination) unseres Programms. Jedes Mal, wenn ein Feature hinzugefügt wird, wirkt sich dies auch auf diese Datei aus und jedes Mal, wenn sie wächst."
Wenn dieser große Schalter (von dem ich glaube, dass er vorhanden ist) zum Hauptwartungsproblem wird, können Sie ihn so umgestalten, dass er das Wörterbuch und das Befehlsmuster verwendet und die gesamte Schaltlogik aus dem vorhandenen Code in den Loader entfernt, der diese Karte auffüllt, dh:
quelle
Ich denke, der einfachste Weg, den Verlauf der Quelle beim Teilen einer Datei zu verfolgen, wäre ungefähr so:
quelle
Ich denke, was ich in dieser Situation tun würde, ist die Kugel und:
Das Verfolgen alter Änderungen an der Datei wird einfach durch Ihren ersten Check-in-Kommentar gelöst, der so etwas wie "Von mainmodule.cpp trennen" sagt. Wenn Sie zu etwas Neuem zurückkehren müssen, werden sich die meisten Menschen an die Änderung erinnern. In zwei Jahren werden sie anhand des Kommentars darüber informiert, wo sie suchen müssen. Wie wertvoll wird es natürlich sein, mehr als zwei Jahre zurück zu gehen, um zu sehen, wer den Code geändert hat und warum?
quelle