نظرة عامة على 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) التي تتطلب تنفيذات متكررة عبر أنواع عديدة، غالباً ما يوفر توليد الكود أفضل توازن بين الأمان والراحة.
التحدي
سهللنقم ببناء مولد أكواد (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
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool12مفاهيم OOP المتقدمة
نمط Functional Optionsنمط Builder في Goتسلسل الدوال (Method Chaining)الأسماء المستعارة للأنواع مقابل التعريفاتأساسيات الـ Reflectionنظرة عامة على Code Generation