مقدمة: لماذا إعادة هيكلة الكود القديم (Legacy Code) ضرورة
يواجه كل مطور عاجلاً أم آجلاً كوداً قديماً (Legacy Code) - كود متقادم، ضعيف التوثيق، وغالباً غير قابل للقراءة. في عالم JavaScript و Node.js، هذا الأمر مهم بشكل خاص: المشاريع تنمو بسرعة، التقنيات تتغير، والأجزاء القديمة من الكود تتحول إلى "مستنقع" يعيق التطوير. إعادة هيكلة الكود القديم ليست مجرد نزوة، بل ضرورة للحفاظ على أداء التطبيق وأمانه وقابليته للتوسع.
في هذه المقالة، سنشرح كيفية التعامل مع إعادة هيكلة الكود القديم في JavaScript (Node.js) بأمان وفعالية. ستتعرف على المراحل الرئيسية: التحليل، التقسيم إلى وحدات، استبدال التركيبات القديمة، والاختبار. جميع الأمثلة مكتوبة بلغة JavaScript الحديثة (ES6+) وموجهة لبيئة Node.js.
الخطوة 1: تحليل وفهم الكود القديم (Legacy Code)
قبل تغيير الكود، يجب فهمه. غالباً ما يكون الكود القديم مكتوباً بأسلوب "المعكرونة": دوال بطول مئات الأسطر، متغيرات عامة، وخلط في المنطق. ابدأ بالتدقيق.
1.1. إنشاء خريطة التبعيات
استخدم أدوات مثل madge أو dependency-cruiser لتصور العلاقات بين الوحدات. سيساعدك هذا في اكتشاف التبعيات الدائرية و"الكائنات الإلهية".
// مثال: تثبيت madgenpm install -g madgemadge --image graph.svg src/1.2. تحديد "مناطق الخطر"
ابحث عن أجزاء الكود التي تتغير كثيراً أو تسبب أخطاء. استخدم تاريخ الالتزامات في Git:
git log --oneline --follow src/legacyModule.jsأيضاً، انتبه إلى:
- الدوال التي يزيد طولها عن 50 سطراً
- المتغيرات العامة (مثل
varبدونlet/const) - Callback hell (استدعاءات غير متزامنة متداخلة)
1.3. كتابة اختبارات للسلوك الحالي
بدون اختبارات، إعادة الهيكلة هي قنبلة موقوتة. استخدم jest أو mocha لتغطية الدوال الرئيسية. يجب أن تختبر الاختبارات السلوك الحالي، وليس السلوك "المثالي".
// مثال اختبار لدالة قديمةconst sum = require('./legacyModule').sum;test('sum adds two numbers', () => { expect(sum(1, 2)).toBe(3);});الخطوة 2: تحسين الهيكل تدريجياً
لا تحاول إعادة كتابة كل شيء دفعة واحدة. استخدم استراتيجية "Strangler Fig" - استبدال أجزاء الكود تدريجياً مع الحفاظ على التوافق العكسي.
2.1. تقسيم الكتلة الواحدة (Monolith) إلى وحدات
قم بعزل الكتل المرتبطة منطقياً في ملفات منفصلة. على سبيل المثال، انقل العمل مع قاعدة البيانات إلى db.js، ومعالجة الطلبات إلى handlers.js.
// قبل: ملف واحد بطول 500 سطرfunction handleRequest(req, res) { // 100 سطر منطق // 50 سطر عمل مع قاعدة البيانات // 30 سطر تنسيق الرد}
// بعد: تقسيم إلى وحدات// db.jsexports.getUser = (id) => { /* ... */ };
// handlers.jsconst db = require('./db');exports.handleRequest = (req, res) => { const user = db.getUser(req.params.id); // تنسيق الرد};2.2. استبدال var بـ let/const
هذه خطوة بسيطة ولكنها مهمة. var له نطاق دالة، مما يؤدي غالباً إلى أخطاء. استخدم let للمتغيرات و const للثوابت.
// كانvar x = 10;if (true) { var x = 20; // يعيد تعريف المتغير الخارجي}
// أصبحlet x = 10;if (true) { let x = 20; // متغير محلي}الخطوة 3: تحديث الكود غير المتزامن
الكود القديم في Node.js غالباً ما يعاني من "Callback Hell". استبدل الاستدعاءات المتداخلة بـ Promises أو async/await.
3.1. تحويل Callbacks إلى Promises
استخدم util.promisify للدوال التي تتبع نمط Node.js (الوسيط الأول هو الخطأ).
const fs = require('fs');const { promisi