مقدمة
\nالعمل مع الكود القديم بلغة C++ هو واقع لا مفر منه للعديد من المطورين. غالبًا ما تحتوي المشاريع القديمة، المكتوبة منذ 10-15 عامًا، على تركيبات قديمة، ووحدات ماكرو، وإدارة يدوية للذاكرة، وغياب اختبارات الوحدة. إعادة هيكلة مثل هذا الكود ليست بالمهمة السهلة، ولكنها ضرورية للغاية للحفاظ على حيوية المنتج. في هذه المقالة، سنقوم بتحليل خطة خطوة بخطوة لإعادة هيكلة كود C++ القديم: من التحليل إلى تطبيق الممارسات الحديثة.
\n\n1. التحضير لإعادة الهيكلة: التحليل والاختبار
\n\n1.1. جرد قاعدة الكود
\nالخطوة الأولى هي فهم ما تتعامل معه. استخدم أدوات التحليل الثابت، مثل Clang-Tidy و PVS-Studio و Cppcheck، لتحديد الأماكن التي تعاني من مشاكل: تسرب الذاكرة، السلوك غير المحدد، الأنماط القديمة.
// مثال على تشغيل Clang-Tidy\nclang-tidy main.cpp -- -std=c++17 -I./include\nأنشئ خريطة لتبعيات الوحدات. غالبًا ما يعاني الكود القديم من التبعيات الدائرية و"الفئات المتضخمة".
\n\n1.2. تغطية الاختبارات
\nبدون اختبارات، إعادة الهيكلة هي لعبة روليت روسية. اكتب اختبارات توصيفية (characterization tests) تثبت السلوك الحالي للنظام. استخدم أطر العمل Google Test أو Catch2.
#include <gtest/gtest.h>\n\nTEST(LegacyModule, ProcessData_ShouldReturnExpected) {\n LegacyClass obj;\n int result = obj.ProcessData(42);\n EXPECT_EQ(result, 84); // تثبيت السلوك الحالي\n}\nحتى إذا بدا الكود "خاطئًا"، يجب أن يعكس الاختبار عمله الفعلي. بعد إعادة الهيكلة، يجب أن تجتاز الاختبارات إذا لم تقم بتغيير المنطق.
\n\n2. التحديث التدريجي: من الماكرو إلى constexpr
\n\n2.1. استبدال الماكرو و #define
\nالماكرو هو آفة الكود القديم. ليس له نوع، ولا يخضع لمساحات الأسماء، ويعقد عملية التصحيح. استبدلها بـ constexpr ووظائف inline أو enum class.
// قبل إعادة الهيكلة\n#define BUFFER_SIZE 256\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\n\n// بعد إعادة الهيكلة\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. تحسين إدارة الذاكرة
\nإدارة الذاكرة اليدوية باستخدام new/delete هي مصدر لتسرب الذاكرة والتحرير المزدوج. انتقل إلى المؤشرات الذكية (std::unique_ptr، std::shared_ptr) وحاويات STL.
// قبل إعادة الهيكلة\nvoid process() {\n Widget* w = new Widget();\n // ... كود ...\n delete w; // من السهل نسيانه عند حدوث استثناء\n}\n\n// بعد إعادة الهيكلة\nvoid process() {\n auto w = std::make_unique<Widget>();\n // ... كود ...\n // حذف تلقائي عند الخروج من النطاق\n}\n\n2.3. التخلص من المؤشرات الأولية والمصفوفات
\nاستبدل مصفوفات النمط C بـ std::array أو std::vector، والسلاسل النصية بـ std::string.
// قبل إعادة الهيكلة\nvoid copy_data(char* dest, const char* src, size_t len) {\n memcpy(dest, src, len); // خطر تجاوز سعة المخزن المؤقت\n}\n\n// بعد إعادة الهيكلة\nvoid copy_data(std::span<char> dest, std::span<const char> src) {\n std::copy(src.begin(), src.end(), dest.begin());\n}\n\n3. التغييرات الهيكلية: التقسيم والنمطية
\n\n3.1. تقسيم "الفئات المتضخمة"
\nالفئات التي تفعل كل شيء (God Object) هي علامة أكيدة على سوء التصميم. استخدم مبدأ المسؤولية الواحدة (SRP). قم بفصل فئات منفصلة للعمل مع الملفات، وتسجيل الدخول، ومنطق الأعمال.
\n// قبل: فئة واحدة تفعل كل شيء\nclass MonsterManager {\n void LoadFromFile(const std::string& path);\n void Render();\n void UpdateAI();\n void SaveToDatabase();\n /