مقدمة: لماذا يحتاج الكود القديم بلغة Go إلى إعادة هيكلة؟
\nتشتهر لغة Go (Golang) ببساطتها وأدائها وأدواتها المدمجة للتوازي. ومع ذلك، فإن أي مشروع يتراكم عليه مع مرور الوقت "ديون تقنية": متغيرات عامة، عدم وجود واجهات، أرقام سحرية، دوال غير قابلة للقراءة، وغياب الاختبارات. يُسمى هذا الكود قديماً (legacy). إعادة الهيكلة ليست إعادة كتابة كل شيء من الصفر، بل تحسين تدريجي للهيكل دون تغيير السلوك الخارجي. في هذه المقالة، سنشرح خطة خطوة بخطوة لإعادة هيكلة الكود القديم بلغة Go، من التحليل إلى التنفيذ.
\n\nالخطوة 1: تشخيص الكود القديم
\nقبل تغيير أي شيء، يجب أن نفهم ما نتعامل معه. "الأعلام الحمراء" الرئيسية في مشاريع Go:
\n- \n
- المتغيرات العامة (
var db *sql.DB) — تعقّد الاختبار والعمل المتوازي. \n - الدوال العملاقة (أكثر من 50-100 سطر) — تجعل الكود غير قابل للقراءة. \n
- غياب الواجهات — ارتباط صارم بتطبيقات محددة. \n
- النصوص/الأرقام السحرية — تعقّد البحث والتعديل. \n
- غياب الاختبارات — إعادة الهيكلة بدون اختبارات هي بمثابة المشي على الجليد الرقيق. \n
أدوات التحليل: استخدم go vet و staticcheck للبحث عن الإنشاءات المشبوهة. كما أن gocyclo مفيد لحساب التعقيد الحلقي.
مثال على كود إشكالي:
\npackage main\n\nimport "fmt"\n\nvar config map[string]string // متغير عام\n\nfunc main() {\n config = map[string]string{"port": "8080"}\n startServer()\n}\n\nfunc startServer() {\n // النص السحري "port"\n fmt.Println("Starting on port:", config["port"])\n // ... 100 سطر من الكود\n}\n\nالخطوة 2: إنشاء شبكة حماية من الاختبارات
\nبدون اختبارات، تتحول إعادة الهيكلة إلى تخمين. مهمتك هي تغطية المنطق الأساسي بالاختبارات قبل التغييرات. استخدم نهج Golden Files (الملفات المرجعية) للمخرجات المعقدة أو اختبارات التكامل.
\nمثال على إضافة اختبار لدالة من الكود القديم:
\n// legacy.go (الأصلي)\nfunc CalculateDiscount(price float64, isPremium bool) float64 {\n if isPremium {\n return price * 0.9\n }\n return price * 0.95\n}\n\n// legacy_test.go\npackage main\n\nimport "testing"\n\nfunc TestCalculateDiscount_Premium(t *testing.T) {\n result := CalculateDiscount(100.0, true)\n expected := 90.0\n if result != expected {\n t.Errorf("Expected %f, got %f", expected, result)\n }\n}\n\nfunc TestCalculateDiscount_Regular(t *testing.T) {\n result := CalculateDiscount(100.0, false)\n expected := 95.0\n if result != expected {\n t.Errorf("Expected %f, got %f", expected, result)\n }\n}\nنصيحة: استخدم go test -cover لتقييم التغطية. حاول تغطية ما لا يقل عن 70% من منطق الأعمال قبل بدء إعادة الهيكلة.
الخطوة 3: التفكيك التدريجي واستخراج الواجهات
\nالهدف الرئيسي من إعادة الهيكلة هو جعل الكود قابلاً للاختبار والمرونة. ابدأ باستخراج واجهات للتبعيات الخارجية (قواعد البيانات، عملاء HTTP، أنظمة الملفات).
\n3.1 استخراج واجهة بدلاً من الاستخدام المباشر لقاعدة البيانات
\nلنفترض أن لدينا دالة تعمل مباشرة مع *sql.DB:
// كود قديم\nfunc GetUser(db *sql.DB, id int) (*User, error) {\n row := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id)\n // ...\n}\nإعادة الهيكلة: إنشاء واجهة UserRepository:
type UserRepository interface {\n GetByID(id int) (*User, error)\n}\n\ntype userRepo struct {\n db *sql.DB\n}\n\nfunc (r *userRepo) GetByID(id int) (*User, error) {\n row := r.db.QueryRow("SELECT id, name FROM users WHERE id = ?", id)\n