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

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

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 84 من 107.

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

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

//go:generate stringer -type=Status

type Status int

const (
    Pending Status = iota
    Active
    Completed
)

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

تتضمن أدوات توليد الشيفرة الشائعة في منظومة Go أداة stringer لإنشاء أساليب string الخاصة بـ enum، وأداة mockgen لإنشاء نماذج اختبار من الواجهات، وأداة protoc لتوليد الشيفرة من تعريفات Protocol Buffer. كما تستخدم العديد من أطر عمل ORM وواجهات API التوليدَ لإنشاء استعلامات قواعد بيانات آمنة من حيث الأنواع أو شيفرة العملاء.

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

challenge icon

التحدي

سهل

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

ستنظّم تعليماتك البرمجية عبر ملفين:

  • generator.go: أنشئ منطق توليد التعليمات البرمجية.

    أنشئ بنية EnumType تمثل تعريف نوع شبيه بالتعداد، مع 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: استخدم المولّد لإنتاج تعليمات برمجية لأنواع تعداد مختلفة.

    اقرأ اسم نوع التعداد، ثم اقرأ عددًا من القيم يتبعه اسم كل قيمة. مرّر هذه المعلومات إلى المولّد واطبع التعليمات البرمجية المُولَّدَة بالكامل.

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

  • السطر 1: اسم نوع التعداد
  • السطر 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: توليد تعليمات برمجية متكررة لكنها آمنة من حيث النوع أثناء الترجمة، بدلًا من استخدام التأمل أثناء وقت التشغيل. ينتج مولّدك تعليمات Go برمجية صالحة يمكن حفظها في ملف وترجمتها مع بقية المشروع.

جرّب بنفسك

package main

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

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

	// اقرأ اسم نوع التعداد
	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اختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت