Wie man am besten wechseln aus Vorlage Chaos zu reinigen Klassen Architektur (C ++)?

StackOverflow https://stackoverflow.com/questions/324043

  •  11-07-2019
  •  | 
  •  

Frage

eine ziemlich große Vorlagenbibliothek mit rund 100 Dateien Unter der Annahme, rund 100 Vorlagen mit insgesamt mehr als 200.000 Zeilen Code enthalten. Einige der Vorlagen verwenden, Mehrfachvererbung die Nutzung der Bibliothek zu machen selbst recht einfach (das heißt erben von einigen Basisvorlagen und mit nur bestimmten Geschäftsregeln zu implementieren).

Alles, was existiert (über mehrere Jahre gewachsen), „arbeitet“ und wird für Projekte verwendet wird.

Allerdings Zusammenstellung von Projekten, die Bibliothek verbraucht eine wachsende Menge an Zeit, und es dauert einige Zeit, um die Quelle für bestimmte Fehler zu lokalisieren. Fixierung verursacht oft unerwartete Nebenwirkungen oder ist ziemlich schwierig, weil einige voneinander abhängig Vorlagen geändert werden müssen. Die Prüfung ist fast unmöglich, aufgrund der schieren Menge an Funktionen.

Nun würde Ich mag die Architektur vereinfachen weniger Vorlagen zu verwenden und mehr spezialisierte kleinere Klassen.

Gibt es eine bewährte Methode, um diese Aufgabe zu gehen? Was wäre ein guter Ort, um zu starten?

War es hilfreich?

Lösung

Ich bin nicht sicher, ich sehe, wie / warum Vorlagen das Problem sind, und warum Ebene nicht-Templat Klassen eine Verbesserung wäre. Wäre das nicht nur bedeuten, auch mehr Klassen, weniger Typsicherheit und so größeres Potenzial für Fehler?

kann ich verstehen, die Architektur, Refactoring zu vereinfachen und Abhängigkeiten zwischen den verschiedenen Klassen und Vorlagen zu entfernen, aber automatisch unter der Annahme, dass „weniger Vorlagen werden die Architektur besser machen“ imo fehlerhaft ist.

Ich würde sagen, dass Vorlagen möglicherweise können Ihnen eine viel saubere Architektur bauen, als man ohne sie bekommen würde. Ganz einfach, weil Sie separate Klassen machen können total unabhängig. Ohne Vorlagen, Klassen-Funktionen, die in eine andere Klasse aufrufen müssen über die Klasse kennen, oder eine Schnittstelle erbt, im Voraus. Mit Vorlagen, ist diese Kopplung nicht erforderlich.

Entfernen von Vorlagen würden nur führen mehr Abhängigkeiten, nicht weniger. Die Mehrtypsicherheit von Vorlagen verwendet werden kann, eine Reihe von Fehlern bei der Kompilierung-Zeit (Streuen Sie Ihren Code zügig mit static_assert dem für diesen Zweck)

erkennen

Natürlich kann die zugegebene Compile-Zeit ein triftiger Grund seine Vorlage in einigen Fällen zu vermeiden, und wenn Sie nur eine Reihe von Java-Programmierern haben, die in „traditionellen“ OOP Begriffen zu denken verwendet werden, Vorlagen könnten sie verwirren , was ein weiterer triftiger Grund zu vermeiden Vorlagen werden kann.

Aber von einer Architektur Sicht Ich denke, Vorlagen zu vermeiden, ist ein Schritt in der falschen Richtung.

die Anwendung Umgestalten, sicher, es klingt wie das gebraucht wird. Aber nicht wirft weg eines der nützlichsten Werkzeuge für die Herstellung von erweiterbarem und robusten Code nur, weil die ursprüngliche Version des App es missbraucht. Vor allem, wenn Sie bereits besorgt über die Menge an Code, Entfernen von Vorlagen höchstwahrscheinlich zu mehr Zeilen Code.

Andere Tipps

Sie müssen automatisierte Tests, auf diese Weise in zehn Jahren, wenn Ihr succesor das gleiche Problem hat er den Code Refactoring kann (wahrscheinlich mehr Vorlagen hinzufügen, weil er denkt, dass es Nutzung der Bibliothek vereinfacht) und wissen, dass es noch erfüllt alle Test Fälle. Ebenso sind die Nebenwirkungen von irgendwelchen kleinere Fehler behoben werden sofort sichtbar (Ihre Testfälle sind gut angenommen wird).

Other than that, "Teile und conqueor"

schreiben Unit-Tests.

Wenn der neue Code der gleiche wie der alte Code tun muss.

Das ist eine Spitze zumindest.

Edit:

Wenn Sie alten Code verbitten, die Sie mit der neuen Funktionalität ersetzt haben Sie Phase über können auf den neuen Code nach und nach.

Nun, das ist das Problem, dass Vorlage Denkweise ist sehr verschieden von objektorientierten Vererbung basiert. Es ist schwer, etwas anderes zu beantworten als „neu zu gestalten, die ganze Sache und von vorne anfangen.“

Natürlich kann es eine einfache Art und Weise für einen bestimmten Fall sein. Wir können nicht sagen, ohne mehr zu wissen, was Sie haben.

Die Tatsache, dass die Vorlage Lösung so schwierig ist, ein Hinweis auf ein schlechtes Design zu halten ist sowieso.

Einige Punkte (aber beachten. Dies ist nicht Übel in der Tat Wenn Sie Nicht-Template-Code ändern mögen, aber dies kann helfen):


Nachschlagen Ihre statische Schnittstellen . Wo hängen Vorlagen auf welche Funktionen existieren? Wo brauchen sie typedefs?

Setzen Sie

die gemeinsamen Teile in einer abstrakten Basisklasse. Ein gutes Beispiel dafür ist, wenn Sie über das CRTP Idiom stolpern passieren. Sie können es einfach ersetzen eine abstrakte Basisklasse virtuelle Funktionen.

Lookup integer Listen . Wenn Sie Ihren Code verwendet integrale Listen wie list<1, 3, 3, 1, 3> finden, können Sie sie mit std::vector ersetzen, wenn alle Codes mit ihnen arbeiten mit Laufzeitwerten statt konstanten Ausdrücken leben können.

Lookup Typ Merkmale . Es gibt viel Code beteiligt zu prüfen, ob einige typedef vorhanden ist, oder ob einige Verfahren besteht in typischen Templat-Code. Abstrakte Basisklassen lösen diese beiden Probleme durch rein virtuelle Methoden und durch typedefs an der Basis zu erben. Oft sind typedefs nur scheußlich Funktionen wie SFINAE auslösen benötigt, die dann überflüssig sein würde.

Lookup Ausdruck Vorlagen . Wenn Ihr Code Vorlagen für Ausdrücke verwendet Provisorien zu vermeiden erstellen, werden Sie sie beseitigen haben und verwenden Sie die traditionelle Art der Rückkehr / vorbei Provisorien an die beteiligten Betreiber.

Lookup-Funktion Objekte . Wenn Sie feststellen, Ihre Code-Funktion Objekte verwendet, können Sie sie ändern zu abstrakt Basisklassen zu verwenden, und haben so etwas wie void run(); sie zu nennen (oder wenn Sie behalten möchten operator() verwenden, besser so! Es kann auch virtuell sein).

Wie ich verstehe, Sie sind am meisten betroffen mit Bauzeiten und die Wartbarkeit Ihrer Bibliothek?

Zuerst versuchen Sie nicht, zu „reparieren“ alles auf einmal.

Zweitens verstehen, was Sie zu beheben. Template Komplexität ist es oft für einen Grund, z.B. bestimmte Anwendung zu erzwingen und den Compiler dazu beitragen, dass Sie nicht einen Fehler machen. Dieser Grund könnte manchmal zu weit gehen, aber 100 Zeilen werfen, weil „niemand wirklich weiß, was sie tun“ sollte nicht leichtfertig getroffen werden. Alles, was ich hier vorschlagen kann wirklich fiese Bugs vorstellen, Sie sind gewarnt worden.

Drittens betrachtet billige Behebungen zuerst: z schnellere Maschinen oder verteilte Build-Tools. Wenigstens werfen in allen RAM nehmen die Bretter, und alte Platten werfen. Es tut maike einen Unterschied. Ein Laufwerk für OS, ein Laufwerk für Build ist ein billiger mans RAID.

Ist die Bibliothek gut dokumentiert? Das ist Ihre beste Chance auf die es in Werkzeuge Schauen wie doxygen, dass Sie eine solche Dokumentation erstellen helfen.

Alle in Betracht gezogen? OK, jetzt einige Vorschläge für die Bauzeiten;)


Verstehen Sie die C ++ Modell bauen : jeder CPP wird individuell zusammengestellt. Das bedeutet, dass viele CPP-Dateien mit vielen headers = riesigen bauen. Dies ist nicht eine Beratung, alles in eine CPP-Datei zu setzen, aber! Allerdings ist ein Trick (!), Die immens einen Build beschleunigen kann, ist eine einzige CPP-Datei zu erstellen, die eine Reihe von CPP-Dateien enthält, und nur füttern, dass „Master“ Datei an den Compiler. Sie können nicht blind machen, obwohl -. Sie, welche Arten von Fehlern zu verstehen, müssen diese einführen könnte

Wenn Sie eine noch nicht haben, erhalten eine separate Build-Maschine , dass Sie Remote-in. Sie müssen eine Menge von fast voll tun, baut zu überprüfen, ob Sie einige sind pleite. Sie wollen dies in einer anderen Maschine laufen zu lassen, die Sie nicht anders von der Arbeit an etwas blockieren. Langfristig Sie werden es brauchen für die tägliche Integration baut sowieso;)

Mit vorkompilierte Header . (Skaliert besser mit schnellen Maschinen, siehe oben)

Überprüfen Sie Ihre Kopfeingliederungspolitik . Während jede Datei „unabhängig“ sein sollte (das heißt alles enthalten muss es von jemand anderem aufgenommen werden), enthalten nicht liberal. Leider habe ich noch nicht ein Werkzeug gefunden unnötige #incldue Aussagen zu finden, aber es könnte einige Zeit Entfernen nicht verwendete Header in „Hotspot“ Dateien.

verbringen helfen

Erstellen und Verwenden von Vorwärts-Erklärungen für die Vorlagen, die Sie verwenden. Oft können Sie einen Header mit forwad Erklärungen an vielen Orten incldue und den vollständigen Header nur in wenige speziell denjenigen verwenden. Dies kann erheblich dazu beitragen, Zeit zu kompilieren. Überprüfen Sie die <iosfwd> Header, wie die Standard-Bibliothek macht das für I / O-Streams.

Überlastungen für Vorlagen für einige Typen : Wenn Sie eine komplexe Funktionsvorlage, die nur für sehr wenige Arten wie dies nützlich:

// .h
template <typename FLOAT> // float or double only
FLOAT CalcIt(int len, FLOAT * values) { ... }

Sie können die Überlastungen im Header deklarieren und die Vorlage an den Körper bewegen:

// .h
float CalcIt(int len, float * values);
double CalcIt(int len, double * values);

// .cpp
template <typename FLOAT> // float or double only
FLOAT CalcItT(int len, FLOAT * values) { ... }

float CalcIt(int len, float * values) { return CalcItT(len, values); }
double CalcIt(int len, double * values) { return CalcItT(len, values); }

Damit wird die lange Vorlage zu einer einzigen Übersetzungseinheit.
Leider ist dies nur bedingt geeignet ist für die Klassen.

Überprüfen Sie, ob die PIMPL Idiom können bewegt Code aus den Headern in CPP-Dateien.

Die allgemeine Regel, die versteckt sich hinter die ist trenne die Schnittstelle Ihrer Bibliothek aus der Umsetzung . Verwenden Sie Kommentare, detail namesapces und separate .impl.h Header geistig und körperlich zu isolieren, was nach außen bekannt sein sollte, wie es erreicht wird. Dies legt den realen Wert Ihrer Bibliothek (macht es die Komplexität tatsächlich einkapseln?), Und gibt Ihnen eine Chance „leichte Ziele“ erste zu ersetzen.


Speziellere beraten - und wie nützlich gegeben derjenige ist - deweitgehend auf der tatsächlichen Bibliothek pends.

Viel Glück!

Wie bereits erwähnt, Unit-Tests sind eine gute Idee. Tatsächlich, anstatt den Code zu brechen durch „einfache“ Änderungen einzuführen, die wahrscheinlich kräuseln out sind nur darauf konzentrieren, eine Reihe von Tests zu schaffen und mit den Tests der Nichteinhaltung zu fixieren. Haben Sie eine Aktivität, die Tests zu aktualisieren, wenn Fehler ans Licht kommen.

Darüber hinaus würde ich vorschlagen, Ihre Tools aktualisieren, wenn möglich, mit Debug-Vorlage bezogenen Problemen zu helfen.

Ich habe oft über Legacy-Vorlagen kommen, die waren riesig und sehr viel Zeit und Speicher benötigt zu instanziiert, muss aber nicht sein. In diesen Fällen war der einfachste Weg, um das Fett zu schneiden die gesamten Code zu nehmen, die nicht auf einen der Vorlage Arguments verlassen haben und sie in getrennten Funktionen in einer normalen Übersetzungseinheit definiert verstecken. Dies hatte auch die positive Nebenwirkung von weniger Neukompilierungen Auslösung, wenn dieser Code etwas geändert werden mußte oder Dokumentation geändert. Es klingt ziemlich offensichtlich, aber es ist wirklich erstaunlich, wie oft Menschen eine Klassenvorlage schreiben und denken, dass alles, was es tut, hat in der Kopfzeile definiert werden, anstatt nur den Code, der die Templat-Informationen benötigt.

Eine andere Sache, die Sie vielleicht prüfen ist, wie oft Sie, indem die Vorlagen „mixin“ Stil statt Aggregationen von Mehrfachvererbung die Vererbungshierarchien aufzuräumen. Sehen Sie, wie viele Orte, die Sie weg mit zu einem der Vorlage Argumente der Name der Basisklasse erhalten können, die sie aus (die Art und Weise boost::enable_shared_from_this Werke) ableiten sollte. Natürlich nur dies in der Regel funktioniert gut, wenn die Konstrukteure keine Argumente haben, wie Sie richtig etwas nicht über die Initialisierung kümmern.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top