المواد الفنية
تستخدم برامج Go أمر os.Exit
أو log.Fatal*
للخروج على الفور (استخدام الـ panic
ليس وسيلة جيدة للخروج من البرنامج، يرجى عدم استخدام الـ panic).
استدعاء os.Exit
أو log.Fatal*
فقط في main()
. يجب على جميع الدوال الأخرى إرجاع الأخطاء إلى المُستدعين.
الطريقة غير الموصى بها:
func main() {
body := readFile(path)
fmt.Println(body)
}
func readFile(path string) string {
f, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
b, err := os.ReadAll(f)
if err != nil {
log.Fatal(err)
}
return string(b)
}
الطريقة الموصى بها:
func main() {
body, err := readFile(path)
if err != nil {
log.Fatal(err)
}
fmt.Println(body)
}
func readFile(path string) (string, error) {
f, err := os.Open(path)
if err != nil {
return "", err
}
b, err := os.ReadAll(f)
if err != nil {
return "", err
}
return string(b), nil
}
في الأساس، البرامج التي تحتوي على نقاط عديدة للخروج لديها عدة مشاكل:
- تدفق التحكم غامض: يمكن لأي دالة أن تخرج من البرنامج مما يجعل من الصعب فهم تدفق التحكم.
- صعوبة في الاختبار: الدوال التي تخرج من البرنامج تخرج أيضًا من الاختبارات التي تستدعيها. هذا يجعل الدوال صعبة الاختبار ويزيد من خطر تخطي اختبارات أخرى لم يتم تشغيلها بواسطة
go test
. - تخطي النظافة: عندما تخرج دالة من البرنامج، فإنها تتخطى أي دوال مؤجلة. هذا يزيد من خطر تجاهل المهام النظيفة الهامة.
الخروج مرة واحدة
إذا كان ممكنًا، يجب أن يكون هناك حد أقصى لا يزيد عن استدعاء واحد لـ os.Exit
أو log.Fatal
في دالة main()
الخاصة بك. إذا كان هناك سيناريوهات خطأ متعددة تتطلب إيقاف تنفيذ البرنامج، يجب وضع تلك المنطق في دالة منفصلة وإرجاع الخطأ من هناك. هذا سيقصر دالة main()
ويضع كامل منطق الأعمال الحرج في دالة منفصلة، يمكن اختبارها.
الطريقة غير الموصى بها:
package main
func main() {
args := os.Args[1:]
if len(args) != 1 {
log.Fatal("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()
// إذا قمنا باستدعاء log.Fatal بعد هذا السطر
// سيتم تنفيذ f.Close.
b, err := os.ReadAll(f)
if err != nil {
log.Fatal(err)
}
// ...
}
الطريقة الموصى بها:
package main
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("missing file")
}
name := args[0]
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()
b, err := os.ReadAll(f)
if err != nil {
return err
}
// ...
}
المثال السابق يستخدم log.Fatal
، ولكن هذا الدليل ينطبق أيضًا على os.Exit
أو أي كود مكتبة يستدعي os.Exit
.
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
قد تقوم بتغيير توقيع run()
حسب الحاجة. على سبيل المثال، إذا كان برنامجك يجب أن يخرج برمز فشل معين، فإن run()
يمكن أن ترجع رمز الخروج بدلاً من خطأ. هذا يسمح أيضًا للاختبارات الوحدوية بالتحقق مباشرة من هذا السلوك.
func main() {
os.Exit(run(args))
}
func run() (exitCode int) {
// ...
}
يرجى ملاحظة أن دالة run()
المستخدمة في هذه الأمثلة ليست إلزامية. اسم وتوقيع وإعداد دالة run()
مرنة. من بين أمور أخرى، يمكنك:
- قبول وسائط الأمر غير المحللة (مثل
run(os.Args[1:])
) - تحليل وسائط الأمر في
main()
وتمريرها إلىrun
- إرجاع رمز الخروج إلى
main()
باستخدام نوع خطأ مخصص - وضع منطق الأعمال على مستوى تجريدي مختلف
package main
هذا الدليل يتطلب فقط وجود مكان في main()
مسؤول عن تدفق الخروج الفعلي.