סקירה כללית של יצירת קוד
חלק מהיחידה תכנות מונחה עצמים במסלול ה-GO של Coddy. שיעור 84 מתוך 107.
יצירת קוד היא חלופה לשיקוף, שיוצרת קוד מקור של 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 ליצירת אובייקטים מדומים לבדיקות מממשקים, ואת protoc ליצירת קוד מהגדרות של Protocol Buffer. גם ORMs ומסגרות API רבים משתמשים ביצירת קוד כדי ליצור שאילתות למסד נתונים בטוחות מבחינת טיפוסים או קוד לקוח.
היתרון המרכזי על פני רפלקציה הוא שהקוד שנוצר נבדק על ידי המהדר. שגיאות מתגלות בזמן הבנייה, ולא בזמן הריצה. המחיר הוא שלב בנייה נוסף וקבצים שנוצרו שיש לנהל במאגר הקוד שלך. עבור דפוסי תכנות מונחה עצמים שדורשים מימושים חוזרים בסוגים רבים, יצירת קוד מספקת לעיתים קרובות את האיזון הטוב ביותר בין בטיחות לנוחות.
אתגר
קלבואו נבנה מחולל קוד שיוצר מתודות 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: יצירת קוד חזרתי אך בטוח מבחינת טיפוסים בזמן הקומפילציה, במקום שימוש ב-reflection בזמן ריצה. המחולל שלכם מפיק קוד Go תקין שאפשר לשמור בקובץ ולקמפל יחד עם שאר הפרויקט.
נסו בעצמכם
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
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP ב-Go
קבצים חיצונייםסביבת העבודה והמודולים של Goחבילות וייבואשמות מיוצאים לעומת שמות לא מיוצאיםמבוא ל-OOP ב-GoStructs כמחלקותהגדרת מתודות עבור Structsמקבלי Pointer לעומת Valueאתחול Structפונקציות Constructorחזרה – מחשבון פשוט4ממשקים
מבוא לממשקיםמימוש משתמעממשק כחוזהממשק ריק (any)אימות טיפוסמתג טיפוסיםהרכבת ממשקיםממשקי Stringer ו-Errorסיכום – מחשבון צורות7כימוס
שדות מיוצאים לעומת שדות לא מיוצאיםכימוס ברמת החבילהמתודות Getter ו-Setterהסתרת מידע ב-Goסיכום – רשומות תלמידים10Generics (Go 1.18 ומעלה)
מבוא ל־Genericsפרמטרים של טיפוסיםאילוצי טיפוסיםמבנים גנרייםפתרון עוקף למתודות גנריותסיכום – אוסף גנרי2העמקה בסוגים וב-Structs
סוגים בסיסיים ומורכביםהגדרות של סוגים מותאמים אישיתתגיות StructStructs אנונימייםStructs מקונניםערכי אפס וברירות מחדלסיכום – ספר אנשי קשר5קומפוזיציה במקום ירושה
למה ב-Go אין ירושהיסודות הטמעת Structקידום מתודותהטמעת כמה Structsהטמעה לעומת אגרגציההסתרת מתודות מוטמעותסיכום – היררכיית עובדים8טיפול בשגיאות ו-OOP
ממשק errorסוגי שגיאות מותאמים אישיתעטיפת שגיאות (fmt.Errorf)שגיאות Sentinelerrors.Is() ו-errors.As()Panic, Defer ו-Recoverסיכום – מנתח קבצים11ספרייה סטנדרטית ותכנות מונחה עצמים
io.Reader ו-io.Writersort.Interfaceהממשק fmt.Stringerencoding/json עם מבניםהממשק http.Handlerסיכום – מודלים של REST API3מצביעים וזיכרון
יסודות המצביעים ב-Goמצביעים למבניםהעברה לפי ערך לעומת לפי הפניההפונקציה new()איסוף אשפה ב-Goסיכום - בונה רשימה מקושרת6פולימורפיזם ב-Go
פולימורפיזם באמצעות ממשקיםטיפוס ברווז ב-Goכללי התאמה לממשקאוספים פולימורפייםהזרקת תלויותסיכום - מעבד תשלומים9מקביליות ו-OOP
יסודות GoroutinesChannels ותקשורתChannel עם חוצץ לעומת ללא חוצץפקודת Selectsync.Mutex ו-sync.RWMutexsync.WaitGroupתכנון Struct בטוח לשימוש במקבילסיכום - מאגר עובדים12מושגים מתקדמים בתכנות מונחה עצמים
דפוס האפשרויות הפונקציונליותדפוס Builder ב-Goשרשור מתודותכינויי טיפוסים לעומת הגדרותיסודות Reflectionסקירה כללית של יצירת קודתרגלו בעצמכם: קומפיילר Go אונליין