نظرة عامة على 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 التي تتطلب تطبيقات متكررة عبر العديد من الأنواع، غالبًا ما يُوفر توليد الشيفرة أفضل توازن بين الأمان والسهولة.
التحدي
سهللننشئ مولّدًا للتعليمات البرمجية ينشئ أساليب 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
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت