Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

نظرة عامة على Code Generation

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 84 من 107.

توليد الكود هو بديل للانعكاس (reflection) يقوم بإنشاء كود مصدر Go في وقت التجميع بدلاً من فحص الأنواع في وقت التشغيل. يمنحك هذا النهج أماناً للأنواع وأداءً أفضل مع تقليل الكود المتكرر النمطي.

توفر Go الأمر go generate لأتمتة توليد الكود. تضع توجيه تعليق خاص في ملف المصدر الخاص بك، ويؤدي تشغيل go generate إلى تنفيذ الأداة المحددة:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

يؤدي تشغيل go generate ./... إلى استدعاء أداة stringer، والتي تنشئ ملفاً جديداً يحتوي على تابع String() لنوع Status. الكود المُنشأ هو كود Go عادي يتم تجميعه مع برنامجك.

تشمل أدوات توليد الأكواد الشائعة في نظام Go البيئي stringer لطرق سلاسل التعداد (enum)، و mockgen لإنشاء نماذج اختبار (mocks) من الواجهات (interfaces)، و protoc لتوليد الأكواد من تعريفات Protocol Buffer. كما تستخدم العديد من أدوات ORM وأطر عمل API التوليد لإنشاء استعلامات قاعدة بيانات آمنة من حيث النوع (type-safe) أو أكواد للعملاء.

الميزة الرئيسية التي يتفوق بها توليد الكود على الانعكاس (reflection) هي أن الكود المولد يتم التحقق منه بواسطة المترجم (compiler). حيث يتم اكتشاف الأخطاء في وقت البناء (build time)، وليس في وقت التشغيل (runtime). المقايضة هنا هي وجود خطوة بناء إضافية وملفات مولدة يجب إدارتها في مستودع الكود (repository) الخاص بك. بالنسبة لأنماط البرمجة كائنية التوجه (OOP) التي تتطلب تنفيذات متكررة عبر أنواع عديدة، غالباً ما يوفر توليد الكود أفضل توازن بين الأمان والراحة.

challenge icon

التحدي

سهل

لنقم ببناء مولد أكواد (code generator) ينشئ طرق String() لأنواع تشبه الـ enum! بينما لا يمكننا تشغيل go generate الفعلي في هذه البيئة، ستقوم بمحاكاة ما تفعله أدوات توليد الأكواد — إنتاج كود مصدر Go برمجياً بناءً على تعريفات الأنواع.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • generator.go: أنشئ منطق توليد الكود الخاص بك.

    قم ببناء هيكل EnumType يمثل تعريف نوع يشبه الـ enum مع Name (سلسلة نصية) و Values (شريحة من السلاسل النصية تمثل أسماء الثوابت).

    أنشئ دالة GenerateStringMethod(e EnumType) string تنتج كود مصدر Go لطريقة String(). يجب أن يتبع الكود المولد هذا النمط:

    func (e TypeName) String() string {
        switch e {
        case Value1:
            return "Value1"
        case Value2:
            return "Value2"
        default:
            return "Unknown"
        }
    }

    أنشئ أيضاً دالة GenerateConstants(e EnumType) string تنتج إعلانات الثوابت باستخدام iota:

    const (
        Value1 TypeName = iota
        Value2
        Value3
    )

    أخيراً، أنشئ GenerateFullCode(e EnumType) string التي تجمع بين المخرجين مع تعليق رأسي يشير إلى أن هذا كود مولد:

    // Code generated by enum generator. DO NOT EDIT.
    
    [constants]
    
    [string method]
  • main.go: استخدم المولد الخاص بك لإنتاج كود لأنواع enum مختلفة.

    اقرأ اسم نوع الـ enum، ثم اقرأ عدد القيم متبوعاً باسم كل قيمة. مرر هذه المعلومات إلى المولد الخاص بك واطبع الكود المولد بالكامل.

سيتم توفير المدخلات التالية:

  • السطر 1: اسم نوع الـ Enum
  • السطر 2: عدد القيم
  • الأسطر التالية: اسم كل قيمة

على سبيل المثال، إذا كان المعطى:

Status
3
Pending
Active
Completed

يجب أن يكون مخرجك:

// Code generated by enum generator. DO NOT EDIT.

const (
    Pending Status = iota
    Active
    Completed
)

func (e Status) String() string {
    switch e {
    case Pending:
        return "Pending"
    case Active:
        return "Active"
    case Completed:
        return "Completed"
    default:
        return "Unknown"
    }
}

وإذا كان المعطى:

Priority
2
Low
High

يجب أن يكون مخرجك:

// Code generated by enum generator. DO NOT EDIT.

const (
    Low Priority = iota
    High
)

func (e Priority) String() string {
    switch e {
    case Low:
        return "Low"
    case High:
        return "High"
    default:
        return "Unknown"
    }
}

يوضح هذا التمرين المفهوم الأساسي وراء أدوات مثل stringer — توليد كود متكرر ولكنه آمن من حيث النوع (type-safe) في وقت التجميع بدلاً من استخدام الانعكاس (reflection) في وقت التشغيل. ينتج المولد الخاص بك كود Go صالحاً يمكن حفظه في ملف وتجميعه مع بقية المشروع.

ورقة مرجعية

يقوم توليد الكود (Code generation) بإنشاء كود مصدر بلغة Go في وقت التجميع بدلاً من فحص الأنواع في وقت التشغيل، مما يوفر أماناً للأنواع وأداءً أفضل مع تقليل الكود المتكرر (boilerplate).

يقوم الأمر go generate بأتمتة توليد الكود باستخدام توجيهات تعليقات خاصة:

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

يؤدي تشغيل go generate ./... إلى استدعاء الأداة المحددة (مثل stringer)، والتي تنشئ ملفاً جديداً يحتوي على الكود المولد الذي يتم تجميعه مع برنامجك.

تشمل أدوات توليد الكود الشائعة ما يلي:

  • stringer - يولد دوال String() لأنواع التعداد (enum)
  • mockgen - ينشئ كائنات وهمية للاختبار (test mocks) من الواجهات (interfaces)
  • protoc - يولد كوداً من تعريفات Protocol Buffer

المزايا الرئيسية مقارنة بالانعكاس (reflection):

  • يتم فحص الكود المولد بواسطة المترجم (compiler)
  • يتم اكتشاف الأخطاء في وقت البناء، وليس في وقت التشغيل
  • أداء أفضل

تشمل المقايضات خطوة بناء إضافية وملفات مولدة يجب إدارتها في مستودع الكود الخاص بك.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// قراءة اسم نوع التعداد (enum)
	typeName, _ := reader.ReadString('\n')
	typeName = strings.TrimSpace(typeName)

	// قراءة عدد القيم
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// قراءة اسم كل قيمة
	values := make([]string, count)
	for i := 0; i < count; i++ {
		value, _ := reader.ReadString('\n')
		values[i] = strings.TrimSpace(value)
	}

	// TODO: إنشاء EnumType باستخدام البيانات المقروءة

	// TODO: إنشاء وطباعة الكود الكامل باستخدام GenerateFullCode
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming