Einleitung
\nDie Arbeit mit Legacy-Code in C++ ist für viele Entwickler eine unvermeidliche Realität. Alte Projekte, die vor 10-15 Jahren geschrieben wurden, enthalten oft veraltete Konstrukte, Makros, manuelle Speicherverwaltung und fehlende Modultests. Das Refactoring solchen Codes ist keine leichte Aufgabe, aber für die Aufrechterhaltung der Lebensfähigkeit des Produkts äußerst notwendig. In diesem Artikel werden wir einen schrittweisen Plan zum Refactoring von Legacy-C++-Code durchgehen: von der Analyse bis zur Einführung moderner Praktiken.
\n\n1. Vorbereitung auf das Refactoring: Analyse und Testen
\n\n1.1. Inventur der Codebasis
\nDer erste Schritt besteht darin, zu verstehen, womit Sie es zu tun haben. Verwenden Sie statische Analysetools wie Clang-Tidy, PVS-Studio oder Cppcheck, um Problemstellen zu identifizieren: Speicherlecks, undefiniertes Verhalten, veraltete Muster.
// Beispiel für den Start von Clang-Tidy\nclang-tidy main.cpp -- -std=c++17 -I./include\nErstellen Sie eine Abhängigkeitskarte der Module. Oft leidet Legacy-Code unter zirkulären Abhängigkeiten und "gottgleichen Klassen".
\n\n1.2. Testabdeckung
\nOhne Tests ist Refactoring russisches Roulette. Schreiben Sie Charakterisierungstests (characterization tests), die das aktuelle Verhalten des Systems festhalten. Verwenden Sie die Frameworks Google Test oder Catch2.
#include <gtest/gtest.h>\n\nTEST(LegacyModule, ProcessData_ShouldReturnExpected) {\n LegacyClass obj;\n int result = obj.ProcessData(42);\n EXPECT_EQ(result, 84); // Aktuelles Verhalten festhalten\n}\nAuch wenn der Code "falsch" erscheint, sollte der Test seine tatsächliche Funktionsweise widerspiegeln. Nach dem Refactoring sollten die Tests bestanden werden, wenn Sie die Logik nicht geändert haben.
\n\n2. Schrittweise Modernisierung: Von Makros zu constexpr
\n\n2.1. Ersetzen von Makros und #define
\nMakros sind eine Geißel von Legacy-Code. Sie haben keinen Typ, unterliegen keinen Namensräumen und erschweren das Debugging. Ersetzen Sie sie durch constexpr, inline-Funktionen oder enum class.
// Vor dem Refactoring\n#define BUFFER_SIZE 256\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\n\n// Nach dem Refactoring\nconstexpr size_t BUFFER_SIZE = 256;\n\ntemplate<typename T>\nconstexpr T max(T a, T b) noexcept {\n return (a > b) ? a : b;\n}\n\n2.2. Verbesserung der Speicherverwaltung
\nManuelle Speicherverwaltung mit new/delete ist eine Quelle für Lecks und doppelte Freigaben. Wechseln Sie zu intelligenten Zeigern (std::unique_ptr, std::shared_ptr) und STL-Containern.
// Vor dem Refactoring\nvoid process() {\n Widget* w = new Widget();\n // ... Code ...\n delete w; // bei Ausnahmen leicht zu vergessen\n}\n\n// Nach dem Refactoring\nvoid process() {\n auto w = std::make_unique<Widget>();\n // ... Code ...\n // automatische Löschung beim Verlassen des Gültigkeitsbereichs\n}\n\n2.3. Beseitigung von rohen Zeigern und Arrays
\nErsetzen Sie C-Stil-Arrays durch std::array oder std::vector und Zeichenketten durch std::string.
// Vor dem Refactoring\nvoid copy_data(char* dest, const char* src, size_t len) {\n memcpy(dest, src, len); // Risiko eines Pufferüberlaufs\n}\n\n// Nach dem Refactoring\nvoid copy_data(std::span<char> dest, std::span<const char> src) {\n std::copy(src.begin(), src.end(), dest.begin());\n}\n\n3. Strukturelle Änderungen: Aufteilung und Modularität
\n\n3.1. Aufteilung von "gottgleichen Klassen"
\nKlassen, die alles machen (God Object), sind ein sicheres Zeichen für eine schlechte Architektur. Verwenden Sie das Prinzip der einzigen Verantwortung (SRP). Trennen Sie separate Klassen für Dateiarbeit, Protokollierung und Geschäftslogik.
\n// Vorher: Eine Klasse macht alles\nclass MonsterManager {\n void LoadFromFile(const std::string& path);\n void Render();\n void UpdateAI();\n void SaveToDatabase();\n /