Wie verhindere ich das unbekannte Duplizieren von Code?

33

Ich arbeite auf einer ziemlich großen Codebasis. Hunderte von Klassen, Tonnen von verschiedenen Dateien, viele Funktionen, es dauert mehr als 15 Minuten, um eine neue Kopie abzurufen usw.

Ein großes Problem bei einer so großen Codebasis ist, dass es einige Dienstprogrammmethoden gibt, die dasselbe tun, oder Code, der diese Dienstprogrammmethoden nicht verwendet, wenn dies möglich ist. Und auch die Utility-Methoden sind nicht alle in einer Klasse (weil es ein riesiges Durcheinander sein würde).

Ich bin ziemlich neu in der Codebasis, aber der Teamleiter, der seit Jahren daran arbeitet, scheint dasselbe Problem zu haben. Es führt zu vielen Code- und Arbeitsduplikationen. Wenn also etwas kaputt geht, werden normalerweise vier Kopien des gleichen Codes erstellt

Wie können wir dieses Muster eindämmen? Wie bei den meisten großen Projekten ist nicht der gesamte Code dokumentiert (obwohl es einige gibt) und nicht der gesamte Code ist ... gut, sauber. Aber im Grunde wäre es wirklich schön, wenn wir daran arbeiten könnten, die Qualität in dieser Hinsicht zu verbessern, damit wir in Zukunft weniger Code-Duplikate haben und Dinge wie Dienstprogramme leichter zu entdecken sind.

Außerdem befinden sich die Dienstprogrammfunktionen normalerweise entweder in einer statischen Hilfsklasse, in einer nicht statischen Hilfsklasse, die für ein einzelnes Objekt arbeitet, oder in einer statischen Methode für die Klasse, bei der sie hauptsächlich "hilft".

Ich hatte ein Experiment zum Hinzufügen von Dienstprogrammfunktionen als Erweiterungsmethoden (ich brauchte keine Interna der Klasse und es war definitiv nur in sehr spezifischen Szenarien erforderlich). Dies hatte zur Folge, dass die Primärklasse und dergleichen nicht überladen wurde, aber es ist nicht mehr wirklich erkennbar, es sei denn, Sie wissen bereits davon

Earlz
quelle

Antworten:

30

Die einfache Antwort ist, dass Sie eine Codeduplizierung nicht wirklich verhindern können. Sie können das Problem jedoch durch einen schwierigen, sich ständig wiederholenden inkrementellen Prozess beheben, der sich in zwei Schritten zusammensetzt:

Schritt 1. Beginnen Sie mit dem Schreiben von Tests für Legacy-Code (vorzugsweise unter Verwendung eines Testframeworks).

Schritt 2. Schreiben Sie den duplizierten Code neu bzw. überarbeiten Sie ihn mit dem, was Sie aus den Tests gelernt haben

Sie können statische Analysetools verwenden , um doppelten Code zu erkennen, und für C # gibt es eine Vielzahl von Tools, die dies für Sie tun können:

Mit Tools wie diesem können Sie Punkte in Code finden, die ähnliche Aufgaben ausführen. Schreiben Sie weiterhin Tests, um festzustellen, ob dies tatsächlich der Fall ist. Verwenden Sie dieselben Tests, um die Verwendung des doppelten Codes zu vereinfachen. Dieses "Refactoring" kann auf verschiedene Arten durchgeführt werden und Sie können diese Liste verwenden, um die richtige zu bestimmen:

Darüber hinaus gibt es auch ein ganzes Buch von Michael C. Feathers über das effektive Arbeiten mit Legacy-Code . Es geht in die Tiefe verschiedene Strategien, die Sie ergreifen können, um den Code zum Besseren zu ändern. Er hat einen "Legacy-Code-Änderungsalgorithmus", der nicht weit vom obigen zweistufigen Prozess entfernt ist:

  1. Änderungspunkte identifizieren
  2. Finden Sie Testpunkte
  3. Abhängigkeiten auflösen
  4. Schreibe Tests
  5. Nehmen Sie Änderungen vor und überarbeiten Sie sie

Das Buch ist eine gute Lektüre, wenn Sie sich mit Brown-Field-Entwicklung beschäftigen, dh mit Legacy-Code, der geändert werden muss.

In diesem Fall

Im Falle des OP kann ich mir vorstellen, dass der nicht testbare Code durch einen Honigtopf für "Nutzmethoden und Tricks" verursacht wird, die verschiedene Formen annehmen:

  • statische Methoden
  • Nutzung statischer Ressourcen
  • Singleton-Klassen
  • magische Werte

Beachten Sie, dass daran nichts auszusetzen ist, sie jedoch normalerweise schwer zu warten und zu ändern sind. Erweiterungsmethoden in .NET sind statische Methoden, aber auch relativ einfach zu testen.

Bevor Sie jedoch mit den Refactorings fertig werden, sprechen Sie mit Ihrem Team darüber. Sie müssen auf der gleichen Seite wie Sie gespeichert sein, bevor Sie fortfahren. Dies liegt daran, dass bei der Umgestaltung von Objekten die Wahrscheinlichkeit hoch ist, dass Zusammenführungskonflikte auftreten. Bevor Sie etwas überarbeiten, untersuchen Sie es, und fordern Sie Ihr Team auf, eine Weile vorsichtig an diesen Codepunkten zu arbeiten, bis Sie fertig sind.

Da das OP neu im Code ist, müssen Sie noch einige andere Dinge tun, bevor Sie etwas tun können:

  • Nehmen Sie sich Zeit, um aus der Codebasis zu lernen, dh "alles" zu brechen, "alles" zu testen und zurückzusetzen.
  • Bitten Sie jemanden aus dem Team, Ihren Code vor dem Festschreiben zu überprüfen. ;-)

Viel Glück!

Spoike
quelle
Wir haben tatsächlich einige Unit- und Integrationstests. Nicht 100% Deckung, aber einige der Dinge, die wir tun, sind fast unmöglich, ohne radikale Änderungen an unserer Codebasis zu testen. Ich habe nie in Betracht gezogen, statische Analysen zu verwenden, um Duplikate zu finden. Ich werde das als nächstes versuchen müssen.
Earlz
@Earlz: Statische Code-Analyse ist fantastisch! ;-) Überlegen Sie sich außerdem, wann immer Sie Änderungen vornehmen müssen, Lösungen, um die Änderungen zu vereinfachen (
lesen
+1 Ich würde verstehen, wenn jemand ein Kopfgeld für dieses Q einsetzt, um diese Antwort als "besonders hilfreich" zu bewerten. Der Refactor-to-Patterns-Katalog ist in Gold, solche Dinge, wie sie in GuidanceExplorer.codeplex.com beschrieben werden, sind großartige Programmierhilfen.
Jeremy Thompson
2

Wir könnten auch versuchen, das Problem aus einem anderen Blickwinkel zu betrachten. Anstatt zu glauben, das Problem liege in der Vervielfältigung von Code, können wir prüfen, ob das Problem auf das Fehlen von Richtlinien für die Wiederverwendung von Code zurückzuführen ist.

Ich habe kürzlich das Buch Software-Engineering mit wiederverwendbaren Komponenten gelesen. Es enthält in der Tat eine Reihe sehr interessanter Ideen zur Förderung der Wiederverwendbarkeit von Code auf Organisationsebene.

Der Autor dieses Buches, Johannes Sametinger, beschreibt eine Reihe von Hindernissen für die Wiederverwendung von Code, einige konzeptionelle und andere technische. Zum Beispiel:

Konzeptionell und technisch

  • Schwierigkeiten beim Auffinden wiederverwendbarer Software : Software kann nur wiederverwendet werden, wenn sie gefunden wird. Die Wiederverwendung ist unwahrscheinlich, wenn ein Repository nicht über ausreichende Informationen zu Komponenten verfügt oder wenn Komponenten schlecht klassifiziert sind.
  • Nicht-Wiederverwendbarkeit gefundener Software : Der einfache Zugriff auf vorhandene Software erhöht nicht unbedingt die Wiederverwendung von Software. Software wird selten ungewollt so geschrieben, dass andere sie wiederverwenden können. Das Ändern und Anpassen der Software einer anderen Person kann sogar noch teurer werden als das Programmieren der erforderlichen Funktionen von Grund auf.
  • Ältere Komponenten, die nicht zur Wiederverwendung geeignet sind : Die Wiederverwendung von Komponenten ist schwierig oder unmöglich, es sei denn, sie wurden für die Wiederverwendung konzipiert und entwickelt. Es reicht für eine systematische Wiederverwendung nicht aus, vorhandene Komponenten von verschiedenen älteren Softwaresystemen zu sammeln und für neue Entwicklungen wiederzuverwenden. Reengineering kann beim Extrahieren wiederverwendbarer Komponenten hilfreich sein, der Aufwand kann jedoch beträchtlich sein.
  • Objektorientierte Technologie : Es wird allgemein angenommen, dass sich objektorientierte Technologie positiv auf die Wiederverwendung von Software auswirkt. Leider und zu Unrecht glauben viele, dass die Wiederverwendung von dieser Technologie abhängt oder dass die Übernahme objektorientierter Technologie für die Wiederverwendung von Software ausreicht.
  • Modifikation : Komponenten werden nicht immer genau so sein, wie wir sie wollen. Wenn Änderungen erforderlich sind, sollten wir in der Lage sein, deren Auswirkungen auf das Bauteil und dessen vorherige Überprüfungsergebnisse zu bestimmen.
  • Wiederverwendung von Müll : Die Zertifizierung wiederverwendbarer Komponenten auf bestimmte Qualitätsstufen trägt zur Minimierung möglicher Mängel bei. Schlechte Qualitätskontrollen sind eines der Haupthindernisse für die Wiederverwendung. Wir brauchen ein Mittel, um zu beurteilen, ob die erforderlichen Funktionen mit den von einer Komponente bereitgestellten Funktionen übereinstimmen.

Andere grundlegende technische Schwierigkeiten umfassen

  • Einigung darüber, was eine wiederverwendbare Komponente darstellt.
  • Verstehen, was eine Komponente tut und wie sie verwendet wird.
  • Verstehen, wie wiederverwendbare Komponenten mit dem Rest eines Entwurfs verbunden werden.
  • Entwerfen Sie wiederverwendbare Komponenten so, dass sie einfach angepasst und kontrolliert geändert werden können.
  • Organisieren eines Repositorys, damit Programmierer das finden und verwenden können, was sie benötigen.

Je nach Reifegrad einer Organisation ergeben sich nach Angaben des Autors unterschiedliche Wiederverwendbarkeitsebenen.

  • Ad-hoc-Wiederverwendung zwischen Anwendungsgruppen : Wenn keine ausdrückliche Verpflichtung zur Wiederverwendung besteht, kann die Wiederverwendung bestenfalls auf informelle und willkürliche Weise erfolgen. Der Großteil der Wiederverwendung erfolgt, falls vorhanden, innerhalb von Projekten. Dies führt auch zu Code-Bereinigung und endet in Code-Duplizierung.
  • Repository-basierte Wiederverwendung zwischen Anwendungsgruppen : Die Situation verbessert sich geringfügig, wenn ein Komponenten-Repository verwendet wird, auf das verschiedene Anwendungsgruppen zugreifen können. Es gibt jedoch keinen expliziten Mechanismus zum Einfügen von Komponenten in das Repository und niemand ist für die Qualität der Komponenten im Repository verantwortlich. Dies kann zu vielen Problemen führen und die Wiederverwendung von Software behindern.
  • Zentrale Wiederverwendung mit einer Komponentengruppe: In diesem Szenario ist eine Komponentengruppe explizit für das Repository verantwortlich. Die Gruppe bestimmt, welche Komponenten im Repository gespeichert werden sollen, stellt die Qualität dieser Komponenten und die Verfügbarkeit der erforderlichen Dokumentation sicher und hilft beim Abrufen geeigneter Komponenten in einem bestimmten Wiederverwendungsszenario. Anwendungsgruppen werden von der Komponentengruppe getrennt, die als eine Art Subunternehmer für jede Anwendungsgruppe fungiert. Ein Ziel der Komponentengruppe ist die Minimierung der Redundanz. In einigen Modellen können die Mitglieder dieser Gruppe auch an bestimmten Projekten arbeiten. Bei Projektstarts ist ihr Wissen für die Förderung der Wiederverwendung von Nutzen und dank ihrer Beteiligung an einem bestimmten Projekt können sie mögliche Kandidaten für die Aufnahme in das Repository identifizieren.
  • Domänenbasierte Wiederverwendung : Die Spezialisierung von Komponentengruppen führt zu einer domänenbasierten Wiederverwendung. Jede Domänengruppe ist für Komponenten in ihrer Domäne verantwortlich, z. B. Netzwerkkomponenten, Benutzeroberflächenkomponenten und Datenbankkomponenten.

Vielleicht können Sie neben all den Vorschlägen in anderen Antworten auch ein Wiederverwendbarkeitsprogramm entwerfen, das Management einbeziehen, eine Komponentengruppe bilden, die für die Identifizierung wiederverwendbarer Komponenten verantwortlich ist, indem Sie eine Domänenanalyse durchführen und ein Repository mit wiederverwendbaren Komponenten definieren, das andere Entwickler problemlos verwenden können Fragen und suchen Sie nach gekochten Lösungen für ihre Probleme.

edalorzo
quelle
1

Es gibt 2 mögliche Lösungen:

Prävention - Versuchen Sie, so gut wie möglich zu dokumentieren. Stellen Sie sicher, dass alle Funktionen ordnungsgemäß dokumentiert und die gesamte Dokumentation einfach durchsucht werden kann. Stellen Sie außerdem beim Schreiben von Code klar, wohin der Code gehen soll, damit klar ist, wo Sie suchen müssen. Die Beschränkung der Anzahl von "Utility" -Codes ist einer der wichtigsten Punkte. Jedes Mal, wenn ich höre, "Lass uns Utility-Klasse machen", steigen meine Haare und mein Blut gefriert, weil es offensichtlich ein Problem ist. Bitten Sie die Benutzer immer schnell und einfach, die Codebasis zu kennen, wenn eine Funktion bereits vorhanden ist.

Lösung - Wenn die Vorbeugung fehlschlägt, sollten Sie in der Lage sein, das problematische Stück Code schnell und effizient zu lösen. Ihr Entwicklungsprozess sollte es ermöglichen, doppelten Code schnell zu reparieren. Unit-Tests sind hierfür perfekt geeignet, da Sie Code effizient ändern können, ohne befürchten zu müssen, dass er beschädigt wird. Wenn Sie also zwei ähnliche Codeteile finden, sollte es mit ein wenig Umgestaltung einfach sein, sie in eine Funktion oder Klasse zu abstrahieren.

Ich persönlich glaube nicht, dass Prävention möglich ist. Je mehr Sie versuchen, desto schwieriger ist es, bereits vorhandene Funktionen zu finden.

Euphorisch
quelle
0

Ich denke nicht, dass diese Art von Problemen die allgemeine Lösung haben. Es wird kein doppelter Code erstellt, wenn die Entwickler ausreichend bereit sind, vorhandenen Code nachzuschlagen. Auch Entwickler konnten die Probleme sofort beheben, wenn sie wollten.

Wenn die Sprache C / C ++ ist, ist das Zusammenführen von Duplikaten aufgrund der Flexibilität der Verknüpfung einfacher (man kann beliebige externFunktionen ohne vorherige Informationen aufrufen ). Für Java oder .NET müssen Sie möglicherweise Hilfsklassen und / oder Dienstprogrammkomponenten entwickeln.

Normalerweise beginne ich mit dem Entfernen des vorhandenen Codes durch Duplizieren nur, wenn die Hauptfehler durch die duplizierten Teile verursacht werden.

9dan
quelle
0

Dies ist ein typisches Problem eines größeren Projekts, das von vielen Programmierern bearbeitet wurde, die mitunter unter großem Gruppendruck einen Beitrag geleistet haben. Es ist sehr verlockend, eine Kopie einer Klasse anzufertigen und an diese spezielle Klasse anzupassen. Wenn jedoch ein Problem in der Ursprungsklasse gefunden wurde, sollte es auch in ihren Nachkommen gelöst werden, was oft vergessen wird.

Hierfür gibt es eine Lösung. Sie heißt Generics und wurde in Java 6 eingeführt. Sie entspricht C ++ und heißt Template. Code, dessen genaue Klasse in einer generischen Klasse noch nicht bekannt ist. Bitte suchen Sie nach Java Generics und Sie werden Tonnen und Tonnen an Dokumentation dafür finden.

Ein guter Ansatz ist, Code neu zu schreiben, der an vielen Stellen kopiert / eingefügt zu werden scheint, indem Sie den ersten Code neu schreiben, den Sie zB aufgrund eines bestimmten Fehlers beheben müssen. Schreiben Sie es um, um Generics zu verwenden, und schreiben Sie außerdem sehr strengen Testcode.

Stellen Sie sicher, dass jede Methode der Generic-Klasse aufgerufen wird. Sie können auch Tools zur Codeabdeckung einführen: Generischer Code sollte vollständig codeabgedeckt sein, da er an mehreren Stellen verwendet wird.

Schreiben Sie auch Testcode, z. B. mit JUnit oder ähnlichem für die erste angegebene Klasse, die in Verbindung mit dem generischen Codeteil verwendet werden soll.

Beginnen Sie mit der Verwendung des generischen Codes für die zweite (meistens) kopierte Version, wenn der gesamte vorhergehende Code funktioniert und vollständig getestet wurde. Sie werden sehen, dass es einige Codezeilen gibt, die für diese bestimmte Klasse spezifisch sind. Sie können diese Codezeilen in einer abstrakten geschützten Methode aufrufen, die von der abgeleiteten Klasse implementiert werden muss, die die Basisklasse Generic verwendet.

Ja, es ist eine mühsame Aufgabe, aber mit der Zeit wird es immer besser, ähnliche Klassen herauszureißen und durch etwas zu ersetzen, das sehr, sehr sauber, gut geschrieben und viel einfacher zu warten ist.

Ich hatte eine ähnliche Situation, in der in einer allgemeinen Klasse irgendwann 6 oder 7 andere fast identische Klassen ersetzt wurden, die alle fast identisch waren, aber über einen bestimmten Zeitraum von verschiedenen Programmierern kopiert und eingefügt wurden.

Und ja, ich bin sehr für ein automatisiertes Testen des Codes. Am Anfang wird es mehr kosten, aber es wird Ihnen auf jeden Fall enorm viel Zeit sparen. Versuchen Sie außerdem, eine Codeabdeckung von insgesamt mindestens 80% und 100% für generischen Code zu erreichen.

Hoffe das wird helfen und viel Glück.

André van Kouwen
quelle
0

Ich werde die am wenigsten verbreitete Meinung hier wiederholen Gangnusund behaupten, dass Code-Vervielfältigung nicht immer schädlich ist und manchmal das geringere Übel sein könnte.

Wenn Sie mir zum Beispiel die Möglichkeit geben, Folgendes zu verwenden:

A) Eine stabile (unveränderliche) und winzige, gut getestete Bildbibliothek, die ein paar Dutzend Zeilen trivialen mathematischen Codes für Vektormathematik wie Punktprodukte und Lerps und Klemmen dupliziert, sich jedoch vollständig von allem anderen entkoppelt und in einem Bruchteil von erstellt eine Sekunde.

B) Eine instabile (sich schnell ändernde) Bildbibliothek, die von einer epischen Mathematikbibliothek abhängt, um das oben erwähnte Dutzend Codezeilen zu vermeiden, wobei die Mathematikbibliothek instabil ist und ständig neue Aktualisierungen und Änderungen erhält und daher auch die Bildbibliothek muss wieder aufgebaut werden, wenn nicht auch komplett geändert. Es dauert 15 Minuten, um das Ganze sauber aufzubauen.

... dann sollte es für die meisten Menschen ein Kinderspiel sein, dass A, und zwar gerade wegen seiner geringfügigen Code-Duplizierung, vorzuziehen ist. Der Schwerpunkt, den ich setzen muss, ist der erprobte Teil. Offensichtlich gibt es nichts Schlimmeres, als duplizierten Code zu haben, der überhaupt nicht funktioniert. An diesem Punkt werden Fehler dupliziert.

Aber es gibt auch Kopplung und Stabilität zu bedenken, und einige bescheidene Duplizierungen können hier und da als Entkopplungsmechanismus dienen, der auch die Stabilität (unveränderliche Natur) des Pakets erhöht.

Mein Vorschlag wird also sein, mich mehr auf das Testen und Erarbeiten von etwas wirklich Stabilem zu konzentrieren (wie etwa Unveränderlichem, Finden weniger Gründe, um sich in der Zukunft zu ändern) und Zuverlässigem, dessen Abhängigkeiten von externen Quellen, falls vorhanden, bestehen Sehr stabil, da versucht wurde, alle Formen der Vervielfältigung in Ihrer Codebasis auszumerzen. In einer Umgebung mit großen Teams ist letzteres in der Regel ein unpraktisches Ziel, ganz zu schweigen davon, dass es die Kopplung und die Menge an instabilem Code in Ihrer Codebasis erhöhen kann.


quelle
-2

Vergessen Sie nicht, dass die Code-Vervielfältigung nicht immer schädlich ist. Stellen Sie sich vor: Jetzt müssen Sie einige Aufgaben in absolut unterschiedlichen Modulen Ihres Projekts lösen. Gerade jetzt ist es die gleiche Aufgabe.

Dafür kann es drei Gründe geben:

  1. Einige Themen rund um diese Aufgabe sind für beide Module gleich. In diesem Fall ist die Code-Vervielfältigung schlecht und sollte liquidiert werden. Es wäre klug, eine Klasse oder ein Modul zur Unterstützung dieses Themas zu erstellen und seine Methoden in beiden Modulen zu verwenden.

  2. Die Aufgabe ist in Bezug auf Ihr Projekt theoretisch. Zum Beispiel ist es aus der Physik oder Mathematik usw. Die Aufgabe existiert unabhängig von Ihrem Projekt. In diesem Fall ist die Code-Vervielfältigung schlecht und sollte auch liquidiert werden. Ich würde eine spezielle Klasse für solche Funktionen erstellen. Verwenden Sie eine solche Funktion in jedem Modul, in dem Sie sie benötigen.

  3. In anderen Fällen ist das Zusammentreffen von Aufgaben ein vorübergehendes Zusammentreffen und nichts anderes. Es wäre gefährlich zu glauben, dass diese Aufgaben während Änderungen des Projekts aufgrund von Refactoring und sogar Debugging gleich bleiben. In diesem Fall ist es besser , zwei gleiche Funktionen / Codeteile an verschiedenen Stellen zu erstellen. Und zukünftige Änderungen in einem von ihnen werden den anderen nicht berühren.

Und dieser dritte Fall kommt sehr oft vor. Wenn Sie "unwissentlich" duplizieren, geschieht dies meistens aus diesem Grund - es handelt sich nicht um eine echte Duplizierung!

Versuchen Sie also, es sauber zu halten, wenn es wirklich notwendig ist, und fürchten Sie sich nicht vor Vervielfältigungen, wenn es nicht das Muss ist.

Gangnus
quelle
2
code duplication is not always harmfulist ein schlechter Rat.
Tulains Córdova
1
Soll ich mich deiner Autorität beugen? Ich hatte hier meine Gründe angegeben. Wenn ich mich irre, zeige, wo der Fehler ist. Jetzt scheint es eher deine schlechte Fähigkeit zu sein, die Diskussion zu führen.
Gangnus
3
Codeduplizierung ist eines der Hauptprobleme bei der Softwareentwicklung, und viele Informatiker und Theoretiker haben Paradigmen und Methoden entwickelt, um Codeduplizierungen als Hauptursache für Probleme mit der Wartbarkeit bei der Softwareentwicklung zu vermeiden. Es ist so, als würde man sagen, dass das Schreiben von schlechtem Code nicht immer schlecht ist. Auf diese Weise kann alles rhetorisch gerechtfertigt werden. Vielleicht haben Sie Recht, aber das Vermeiden von Code-Duplikaten ist ein zu gutes Prinzip, um das Gegenteil zu fördern.
Tulains Córdova
Ich habe hier Argumente vorgebracht. Das hast du nicht. Der Hinweis auf Behörden wird seit dem 16. Jahrhundert nicht mehr funktionieren. Sie können nicht garantieren, dass Sie sie richtig verstanden haben und dass sie auch für mich Autoritäten sind.
Gangnus
Sie haben Recht, Codeduplizierung ist nicht eines der Hauptprobleme bei der Softwareentwicklung, und es wurden keine Paradigmen und Methoden entwickelt, um dies zu vermeiden.
Tulains Córdova