إعادة هيكلة الكود القديم بلغة Go: دليل خطوة بخطوة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

إعادة هيكلة الكود القديم بلغة Go: دليل خطوة بخطوة



العمل مع الكود القديم بلغة Go هو جزء لا مفر منه في حياة المطور. غالبًا ما يُكتب هذا الكود على عجل، بدون اختبارات، مع متغيرات عامة ووظائف ضخمة. ومع ذلك، فإن إعادة هيكلة الكود القديم بلغة Go ليست مجرد إعادة كتابة، بل هي عملية استراتيجية تتطلب الحذر والمنهجية. في هذه المقالة، سنشرح كيفية تحسين الكود الحالي بأمان وفعالية دون كسر المنتج.



لماذا تعتبر إعادة هيكلة الكود القديم بلغة Go صعبة؟



غالبًا ما يعاني الكود القديم بلغة Go من مشاكل مميزة:

  • المتغيرات العامة — تعقد الاختبار والتنفيذ المتوازي.
  • الوظائف الضخمة — وظائف بطول مئات الأسطر تفعل كل شيء دفعة واحدة.
  • غياب الواجهات — ارتباط صارم بتطبيقات محددة.
  • عدم وجود اختبارات — إعادة الهيكلة بدون اختبارات تشبه المشي في حقل ألغام.


لكن Go يوفر أدوات قوية لتحسين الكود تدريجيًا. المهم هو التحرك بخطوات صغيرة، مع الحفاظ على عمل النظام في كل مرحلة.



الخطوة 1: التحضير والاختبار



قبل تغيير الكود، من الضروري إنشاء "شبكة أمان" — الاختبارات. حتى لو لم يتم اختبار الكود، يمكنك كتابة اختبارات توصيفية (characterization tests). فهي تثبت السلوك الحالي للنظام، بحيث يمكنك التأكد من أن شيئًا لم ينكسر بعد إعادة الهيكلة.



package main

import "testing"

func TestCalculateTotal(t *testing.T) { // لنفترض أن لدينا دالة قديمة CalculateTotal result := CalculateTotal(100, 20) expected := 120.0

if result != expected { t.Errorf("CalculateTotal(100, 20) = %f; want %f", result, expected) }}


قم بتشغيل الاختبارات وتأكد من نجاحها. الآن لديك أساس للتغييرات الآمنة.



الخطوة 2: استخراج الواجهات



إحدى المشاكل الرئيسية في الكود القديم بلغة Go هي الارتباط القوي بين المكونات. الحل هو استخراج الواجهات. هذا يسمح باستبدال التبعيات الحقيقية بـ mocks أثناء الاختبار، كما يبسط استبدال التطبيق في المستقبل.



لننظر إلى مثال. لنفترض أن لدينا دالة تحفظ البيانات مباشرة في قاعدة البيانات:



package main

import "database/sql"

type User struct { ID int Name string}

func SaveUser(db *sql.DB, user User) error { _, err := db.Exec("INSERT INTO users (name) VALUES ($1)", user.Name) return err}


المشكلة: لا يمكننا اختبار هذه الدالة بدون قاعدة بيانات حقيقية. الحل — استخراج واجهة:



package main

// UserStorage يحدد عقدًا لتخزين المستخدمينtype UserStorage interface { Save(user User) error}

type UserSaver struct { storage UserStorage}

func NewUserSaver(storage UserStorage) *UserSaver { return &UserSaver{storage: storage}}

func (s *UserSaver) SaveUser(user User) error { return s.storage.Save(user)}


الآن يمكننا إنشاء mock للاختبار:



package main

import "testing"

type mockStorage struct{}

func (m *mockStorage) Save(user User) error { return nil // دائمًا ناجح}

func TestUserSaver_SaveUser(t *testing.T) { saver := NewUserSaver(&mockStorage{}) err := saver.SaveUser(User{ID: 1, Name: "Alice"}) if err != nil { t.Errorf("unexpected error: %v", err) }}


الخطوة 3: حقن التبعيات



بعد استخراج الواجهات، الخطوة التالية هي حقن التبعيات (Dependency Injection, DI). بدلاً من إنشاء التبعيات داخل الدالة (مثل فتح اتصال بقاعدة البيانات)، نقوم بتمريرها من الخارج.



لننظر إلى كود قديم نموذجي:



package                

المدونات

توصيات الكتب