مقدمة: لماذا يتطلب الكود القديم بلغة Go نهجًا خاصًا
تشتهر Go (Golang) ببساطتها وأدائها، ولكن حتى معها يظهر مع مرور الوقت كود قديم: حزم ضخمة، متغيرات عامة، غياب الواجهات والاختبارات. إعادة هيكلة هذا الكود ليست مجرد "إعادة كتابة بشكل جميل"، بل هي مهمة استراتيجية يعتمد عليها استقرار وسرعة التطوير.
في هذه المقالة، سنستعرض تقنيات متقدمة لإعادة هيكلة الكود القديم بلغة Go: من فصل الوحدات إلى إدخال الواجهات بشكل آمن. ستتعلم كيفية تحويل "كود السباغيتي" إلى بنية قابلة للصيانة دون إيقاف الإنتاج.
1. التشخيص ورسم خرائط الكود القديم
قبل إعادة الهيكلة، من الضروري فهم الهيكل الحالي. في Go، هناك أدوات وأساليب مدمجة لذلك:
- تحليل التبعيات: استخدم
go mod graphوgo list -jsonلتصور العلاقات بين الحزم. - البحث عن المتغيرات العامة:
grep -r "var " *.go— غالبًا ما تكون هي جذر المشاكل. - تحديد "روائح الكود": الدوال الطويلة، التكرار، غياب معالجة الأخطاء.
// مثال: البحث عن جميع المتغيرات العامة في المشروعpackage main
import ( "fmt" "go/parser" "go/token" "os" "path/filepath")
func findGlobals(root string) { filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || filepath.Ext(path) != ".go" { return nil } fset := token.NewFileSet() f, _ := parser.ParseFile(fset, path, nil, parser.AllErrors) for _, decl := range f.Decls { if genDecl, ok := decl.(*ast.GenDecl); ok && genDecl.Tok == token.VAR { fmt.Printf("متغير عام في %s: %v", path, genDecl.Specs) } } return nil })}هام: قبل إعادة الهيكلة، قم بإنشاء لقطة كاملة للاختبارات (حتى لو لم تكن موجودة — اكتب على الأقل اختبارات دخان للمسارات الحرجة).
2. الفصل الاستراتيجي للوحدات: من الكتلة الواحدة إلى الحزم
غالبًا ما يحتوي الكود القديم على حزمة واحدة عملاقة main أو utils. تبدأ إعادة الهيكلة المتقدمة بالفصل التفصيلي:
- الفصل حسب المسؤولية: قسم الكود إلى طبقات (handler, service, repository).
- إنشاء حزم داخلية: استخدم
internal/لإخفاء التنفيذ. - الواجهات كعقود: لا تنشئ واجهات مسبقًا — استخرجها من التطبيقات الملموسة.
// قبل إعادة الهيكلة: كل شيء في ملف واحدpackage main
type User struct { ID int Name string}
func SaveUser(db *sql.DB, u User) error { _, err := db.Exec("INSERT INTO users ...", u.ID, u.Name) return err}
// بعد إعادة الهيكلة: الفصل إلى حزم// internal/repository/user.gopackage repository
type User struct { ID int Name string}
type UserRepository struct { db *sql.DB}
func (r *UserRepository) Save(u User) error { _, err := r.db.Exec("INSERT INTO users ...", u.ID, u.Name) return err}نصيحة: استخدم نمط "Strangler Fig" — استبدل الدوال القديمة تدريجيًا بأخرى جديدة، دون حذف الكود القديم فورًا.
3. التخلص من الحالات العامة وإدخال التبعيات
المتغيرات العامة في Go هي السبب الرئيسي لعدم الاستقرار وعدم القدرة على الاختبار. التقنية المتقدمة هي عكس التحكم (IoC) من خلال المنشئات والواجهات.
// المشكلة: مسجل عام وإعدادات عامةvar logger *log.Loggervar config *Config
func Init() { logger = log.New(os.Stdout, "", log.LstdFlags) config = loadConfig()}
// الحل: إدخال ال