مقدمة: لماذا تعد إعادة هيكلة الكود القديم (legacy) إلى TypeScript ضرورة
العمل مع الكود القديم (legacy) هو أحد أكثر المهام شيوعًا وتعقيدًا للمطور. غالبًا ما يكون هذا الكود مكتوبًا بلغة JavaScript خالصة بدون كتابة صارمة (strict typing)، ويحتوي على تبعيات معقدة ويفتقر إلى هيكل معياري. الانتقال إلى TypeScript لا يضيف أنواعًا فقط - بل هو تحسين أساسي لجودة الكود يقلل من عدد الأخطاء (bugs)، ويبسط إعادة الهيكلة، ويسرع عملية دمج المطورين الجدد.
في هذه المقالة، سنقوم بتحليل عملية خطوة بخطوة لإعادة هيكلة الكود القديم (legacy) إلى TypeScript. ستتعلم كيفية إدخال الأنواع بأمان، وتقسيم الوظائف المتجانسة (monolithic functions)، واختبار التغييرات.
الخطوة 1. تحليل وإعداد الكود القديم (legacy)
قبل كتابة الكود، من الضروري فهم البنية الحالية. بدون ذلك، ستتحول إعادة الهيكلة إلى فوضى.
أنشئ خريطة التبعيات
ابدأ بتحليل الملفات والوحدات (modules). حدد:
- أي الوظائف "نقية" (pure) وأيها لها آثار جانبية.
- أين توجد المتغيرات والحالات العامة (global variables and states).
- أي أجزاء الكود مكررة.
اكتب اختبارات للأجزاء الحرجة
إعادة الهيكلة بدون اختبارات هي لعبة روليت روسية. اكتب على الأقل اختبارات وحدة (unit tests) للوظائف الرئيسية. مثال لاختبار بلغة TypeScript:
// الكود القديم الأصلي (JavaScript)
function calculatePrice(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i].price * items[i].quantity;
}
return total;
}
// اختبار بلغة TypeScript (Jest)
describe('calculatePrice', () => {
it('should calculate total price correctly', () => {
const items = [
{ price: 10, quantity: 2 },
{ price: 5, quantity: 3 }
];
expect(calculatePrice(items)).toBe(35);
});
});
إذا نجحت الاختبارات - يمكنك البدء في إعادة الهيكلة.
الخطوة 2. الإدخال التدريجي للكتابة الصارمة (strict typing)
لا تحاول تغطية كل شيء بالأنواع مرة واحدة. استخدم استراتيجية "من الأسفل إلى الأعلى": ابدأ بأبسط الوظائف وأكثرها عزلة.
كتابة الأنواع للوظائف البسيطة
أضف أنواعًا للمعلمات والقيم المُعادة. مثال:
// كان (JavaScript)
function greet(name) {
return "Hello, " + name;
}
// أصبح (TypeScript)
function greet(name: string): string {
return `Hello, ${name}`;
}
العمل مع الكائنات والواجهات (interfaces)
لهياكل البيانات المعقدة، قم بإنشاء واجهات (interfaces). هذا يجعل الكود موثقًا ذاتيًا:
// نحدد واجهة لعنصر الطلب
interface OrderItem {
id: number;
name: string;
price: number;
quantity: number;
}
// دالة ذات معامل مكتوب (typed parameter)
function getTotalPrice(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
استخدم `any` فقط كحل مؤقت
النوع any هو عدو الكتابة الصارمة. إذا كنت لا تعرف النوع الدقيق، استخدم unknown وتحقق منه في وقت التشغيل (runtime):
function safeParse(data: unknown): string {
if (typeof data === 'string') {
return data.trim();
}
return '';
}
الخطوة 3. تقسيم الوظائف والوحدات المتجانسة (monolithic functions and modules)
غالبًا ما يحتوي الكود القديم (legacy) على "وظائف إلهية" (god functions) تفعل كل شيء دفعة واحدة. قم بتقسيمها إلى وحدات صغيرة قابلة لإعادة الاستخدام.
استخلاص المنطق النقي
مثال: دالة تتحقق من صحة البريد الإلكتروني وترسل طلبًا. سنقوم بتقسيمها:
// كان (متجانس - monolithic)
function processUser(email) {
if (!email || !email.includes('@')) {
throw new Error('Invalid email');
}
// ... إرسال الطلب
console.log('Sending request...');
}
// أصبح (فصل المسؤوليات)
function validateEmail(email: string): boolean {
return email
```