معالجة الأخطاء في Go: الفلسفة والتطبيق العملي
على عكس العديد من اللغات الحديثة، لا تستخدم Go (Golang) البنية التقليدية try/catch. بدلاً من ذلك، يلتزم مطورو Go بنهج "الأخطاء كقيم" (errors as values). وهذا يجعل الكود أكثر وضوحًا وقابلية للتنبؤ وأمانًا. في هذه المقالة، سنقوم بتحليل جميع جوانب معالجة الأخطاء في Go: من الأنماط الأساسية إلى التقنيات المتقدمة باستخدام defer و panic و recover.
1. الأساسيات: الأخطاء هي مجرد قيم
في Go، الخطأ هو واجهة error بطريقة واحدة Error() string. أي دالة قد تفشل تعيد خطأ كقيمة إرجاع أخيرة.
package main
import ( "errors" "fmt")
// دالة القسمة مع إرجاع خطأfunc divide(a, b float64) (float64, error) { if b == 0 { return 0, errors.New("القسمة على صفر غير ممكنة") } return a / b, nil}
func main() { result, err := divide(10, 0) if err != nil { fmt.Println("خطأ:", err) return } fmt.Println("النتيجة:", result)}النقطة الأساسية: التحقق if err != nil هو الطريقة القياسية لمعالجة الأخطاء في Go. لا توجد استثناءات مخفية قد "تظهر" بشكل غير متوقع.
2. إنشاء وتخصيص الأخطاء
توفر Go عدة طرق لإنشاء الأخطاء:
errors.New()— خطأ نصي بسيط.fmt.Errorf()— خطأ مع تنسيق (يدعم%wللتغليف).- أنواع مخصصة — لنقل سياق إضافي.
خطأ مخصص مع بيانات إضافية
type DivisionError struct { Dividend float64 Divisor float64 Message string}
func (e *DivisionError) Error() string { return fmt.Sprintf("%s: %f / %f", e.Message, e.Dividend, e.Divisor)}
func safeDivide(a, b float64) (float64, error) { if b == 0 { return 0, &DivisionError{ Dividend: a, Divisor: b, Message: "القسمة على صفر", } } return a / b, nil}
func main() { _, err := safeDivide(10, 0) if err != nil { // Type assertion للحصول على الحقول الإضافية var divErr *DivisionError if errors.As(err, &divErr) { fmt.Printf("خطأ مع %f على %f: %s", divErr.Dividend, divErr.Divisor, divErr.Message) } }}استخدام errors.As() يسمح بالتحقق مما إذا كان الخطأ من نوع معين، واستخراج البيانات منه. هذا مشابه لـ instanceof في لغات أخرى.
3. أنماط معالجة الأخطاء: من البسيط إلى المعقد
في Go، هناك عدة أنماط ثابتة للعمل مع الأخطاء:
3.1. أخطاء الحراسة (Sentinel errors)
هذه أخطاء محددة مسبقًا يمكن مقارنتها باستخدام ==. مثال: io.EOF.
import ( "errors" "fmt")
var ErrNotFound = errors.New("العنصر غير موجود")
func findUser(id int) (string, error) { if id != 1 { return "", ErrNotFound } return "Alice", nil}
func main() { _, err := findUser(2) if errors.Is(err, ErrNotFound) { fmt.Println("المستخدم غير موجود، يتم إنشاء مستخدم جديد...") }}3.2. تغليف الأخطاء (Wrapping errors)
بدءًا من Go 1.13، يمكن تغليف الأخطاء باستخدام %w في fmt.Errorf. هذا يحافظ على سلسلة الأخطاء.
func readConfig(path string) error { data, err := os.ReadFile(path) if err != nil { return fmt.Errorf("قراءة ملف الإعدادات %s: %w", path, err) } // ... معالجة البيانات