Menu
Coddy logo textTech

סקירה כללית של יצירת קוד

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 רבים משתמשים ביצירת קוד כדי ליצור שאילתות למסד נתונים בטוחות מבחינת טיפוסים או קוד לקוח.

היתרון המרכזי על פני רפלקציה הוא שהקוד שנוצר נבדק על ידי המהדר. שגיאות מתגלות בזמן הבנייה, ולא בזמן הריצה. המחיר הוא שלב בנייה נוסף וקבצים שנוצרו שיש לנהל במאגר הקוד שלך. עבור דפוסי תכנות מונחה עצמים שדורשים מימושים חוזרים בסוגים רבים, יצירת קוד מספקת לעיתים קרובות את האיזון הטוב ביותר בין בטיחות לנוחות.

challenge icon

אתגר

קל

בואו נבנה מחולל קוד שיוצר מתודות 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
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Go אונליין