مقدمة: لماذا تعد إعادة هيكلة الكود القديم (Legacy Code) في C# ضرورة
كل مطور .NET يواجه عاجلاً أم آجلاً كوداً قديماً (Legacy Code). ليس بالضرورة أن يكون مشروعاً عمره 20 عاماً على .NET Framework 1.1 — فغالباً ما يصبح الكود "قديماً" إذا كُتب على عجل قبل عام، بدون اختبارات، وبانتهاك مبادئ SOLID. العمل مع مثل هذا الكود يتحول إلى كابوس: أي تغيير يكسر الوظائف في أماكن غير متوقعة، ولا يمكن فهم المنطق إلا من خلال التصحيح (Debug).
إعادة الهيكلة (Refactoring) ليست إعادة كتابة من الصفر. إنها تحسين البنية الداخلية للكود دون تغيير سلوكه الخارجي. في هذه المقالة، سنستعرض تقنيات عملية لإعادة هيكلة الكود القديم في C# يمكن تطبيقها فوراً مع تقليل المخاطر.
1. التحضير: القاعدة الذهبية لإعادة الهيكلة
قبل تغيير الكود، تأكد من وجود "شبكة أمان" — مجموعة من الاختبارات الآلية. بدونها، تتحول إعادة الهيكلة إلى تخمين.
إنشاء اختبارات التوصيف (Characterization Tests)
إذا لم تكن هناك اختبارات، فاكتب اختبارات توصيف (تُعرف أيضاً باسم Golden Master أو Snapshot Tests). الفكرة بسيطة: تشغيل الدالة بمدخلات مختلفة، تسجيل النتيجة، ثم التحقق من أن النتيجة لم تتغير بعد إعادة الهيكلة.
// مثال على اختبار توصيف لدالة قديمة[Test]public void CalculateTotal_ShouldReturnSameResult_AfterRefactoring(){ // Arrange var order = new Order { Items = new List<Item> { new Item { Price = 100, Quantity = 2 } } }; var expected = 200; // نتيجة معروفة مسبقاً
// Act var result = LegacyOrderCalculator.CalculateTotal(order);
// Assert Assert.AreEqual(expected, result);}نصيحة: استخدم مكتبات مثل Verify أو ApprovalTests للاختبار الآلي للقطات (Snapshot Testing).
2. التقنية رقم 1: استخراج الدالة (Extract Method)
أبسط وأكثر التقنيات فعالية. عندما ترى دالة بطول 200 سطر مع تعليقات مثل "// هنا حساب الخصم" — فهذا مرشح مثالي.
مثال: دالة كبيرة
public decimal ProcessOrder(Order order){ decimal total = 0; foreach (var item in order.Items) { total += item.Price * item.Quantity; }
// حساب الخصم if (order.Customer.IsPremium) { total *= 0.9m; // خصم 10% } else if (order.TotalAmount > 1000) { total *= 0.95m; // خصم 5% }
// تطبيق الضريبة total *= 1.2m; // ضريبة 20%
return total;}بعد إعادة الهيكلة
public decimal ProcessOrder(Order order){ decimal subtotal = CalculateSubtotal(order.Items); decimal afterDiscount = ApplyDiscount(subtotal, order.Customer); return ApplyTax(afterDiscount);}
private decimal CalculateSubtotal(IEnumerable<Item> items){ return items.Sum(item => item.Price * item.Quantity);}
private decimal ApplyDiscount(decimal total, Customer customer){ if (customer.IsPremium) return total * 0.9m; if (total > 1000) return total * 0.95m; return total;}
private decimal ApplyTax(decimal amount){ return amount * 1.2m;}المزايا:
- كل دالة لها مسؤولية واحدة
- يصبح الكود قابلاً للقراءة والاختبار
- يمكن إعادة استخدام منطق الخصم في أماكن أخرى
3. التقنية رقم 2: استبدال المنطق الشرطي بتعدد الأشكال (Polymorphism)
الكود القديم غالباً ما يحتوي على سلاسل طويلة من if-else أو switch. هذا ينتهك مبدأ الانفتاح/الإغلاق (OCP).
مثال: Switch حسب نوع الشحن
public decimal CalculateShippingCost(Order order){ switch (order.ShippingType) { case "Standard": return 5; case "Express": return 15; case "Overnight