واجهة error
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 52 من 107.
في لغة Go، يتم التعامل مع الأخطاء من خلال واجهة برمجية (interface) مدمجة تسمى error. هذه الواجهة بسيطة للغاية—فهي تتطلب طريقة (method) واحدة فقط:
type error interface {
Error() string
}أي نوع يقوم بتنفيذ طريقة Error() تعيد سلسلة نصية يحقق تلقائياً واجهة error. هذا هو نظام الواجهات في لغة Go قيد العمل - لا حاجة لإعلان صريح.
الطريقة الأكثر شيوعاً لإنشاء الأخطاء هي استخدام حزمة errors:
import "errors"
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}عادةً ما تُرجع دوال Go خطأً كقيمة إرجاع أخيرة لها. عندما لا يحدث خطأ، فإنك تُرجع nil. ثم يتحقق المستدعي مما إذا كان الخطأ غير فارغ (non-nil) قبل المتابعة:
result, err := Divide(10, 0)
if err != nil {
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result)يمكنك أيضاً استخدام fmt.Errorf لإنشاء رسائل خطأ منسقة:
func GetUser(id int) (string, error) {
if id <= 0 {
return "", fmt.Errorf("invalid user id: %d", id)
}
return "Alice", nil
}هذا النمط من إرجاع الأخطاء بدلاً من طرح الاستثناءات هو أمر أساسي في لغة Go. فهو يجعل معالجة الأخطاء صريحة ويجبرك على التفكير فيما يحدث عندما تسوء الأمور - وهو جانب رئيسي في كتابة كود قوي وموجه للكائنات.
التحدي
سهللنقم ببناء نظام حساب بنكي يوضح نمط معالجة الأخطاء في لغة Go. ستقوم بإنشاء حساب بسيط يتحقق من صحة العمليات ويعيد أخطاءً ذات معنى عند حدوث خطأ ما.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
account.go: قم بإنشاء هيكلBankAccountيحتوي على حقلOwnerمُصدر (string) وحقلbalanceغير مُصدر (float64). قم بتنفيذ ما يلي:NewBankAccount(owner string, initialDeposit float64) (*BankAccount, error)- منشئ (constructor) يعيد خطأً بالرسالة"initial deposit must be positive"إذا كان الإيداع الأولي صفراً أو سالباً. بخلاف ذلك، قم بإنشاء الحساب وإعادته.Deposit(amount float64) error- يضيف المال إلى الرصيد. أعد خطأً بالرسالة"deposit amount must be positive"إذا كان المبلغ صفراً أو سالباً.Withdraw(amount float64) error- يسحب المال من الرصيد. أعد خطأً بالرسالة"withdrawal amount must be positive"إذا كان المبلغ صفراً أو سالباً، أو"insufficient funds"إذا كان الرصيد أقل من مبلغ السحب.Balance() float64- يعيد الرصيد الحالي.
main.go: اقرأ تفاصيل الحساب ومبالغ العمليات من المدخلات. حاول إنشاء حساب وإجراء العمليات. بعد كل عملية، اطبع إما رسالة النجاح أو رسالة الخطأ.
سيتم توفير المدخلات التالية:
- السطر 1: اسم المالك (Owner name)
- السطر 2: مبلغ الإيداع الأولي
- السطر 3: مبلغ الإيداع
- السطر 4: مبلغ السحب
لكل عملية، اطبع النتيجة:
- بعد إنشاء الحساب:
Account created for [Owner] with balance: $[balance]أوError: [error message] - بعد الإيداع:
Deposited successfully. New balance: $[balance]أوError: [error message] - بعد السحب:
Withdrew successfully. New balance: $[balance]أوError: [error message]
قم بتنسيق جميع قيم الرصيد بمنزلتين عشريتين. إذا فشل إنشاء الحساب، فتخطى العمليات المتبقية.
على سبيل المثال، إذا كانت المدخلات Alice، و 100، و 50، و 200، يجب أن يكون مخرجك:
Account created for Alice with balance: $100.00
Deposited successfully. New balance: $150.00
Error: insufficient fundsوإذا كانت المدخلات Bob، و -50، و 25، و 10، يجب أن يكون مخرجك:
Error: initial deposit must be positiveورقة مرجعية
تتعامل لغة Go مع الأخطاء من خلال واجهة error المدمجة، والتي تتطلب طريقة (method) واحدة فقط:
type error interface {
Error() string
}قم بإنشاء الأخطاء باستخدام حزمة errors:
import "errors"
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}تعيد الدوال الأخطاء كقيمة إرجاع أخيرة. قم بإرجاع nil عند عدم حدوث خطأ:
result, err := Divide(10, 0)
if err != nil {
fmt.Println("Error:", err.Error())
return
}
fmt.Println("Result:", result)استخدم fmt.Errorf لرسائل الخطأ المنسقة:
func GetUser(id int) (string, error) {
if id <= 0 {
return "", fmt.Errorf("invalid user id: %d", id)
}
return "Alice", nil
}جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// قراءة اسم المالك
ownerName, _ := reader.ReadString('\n')
ownerName = strings.TrimSpace(ownerName)
// قراءة مبلغ الإيداع الأولي
initialDepositStr, _ := reader.ReadString('\n')
initialDeposit, _ := strconv.ParseFloat(strings.TrimSpace(initialDepositStr), 64)
// قراءة مبلغ الإيداع
depositStr, _ := reader.ReadString('\n')
depositAmount, _ := strconv.ParseFloat(strings.TrimSpace(depositStr), 64)
// قراءة مبلغ السحب
withdrawStr, _ := reader.ReadString('\n')
withdrawAmount, _ := strconv.ParseFloat(strings.TrimSpace(withdrawStr), 64)
// TODO: إنشاء حساب بنكي جديد باستخدام NewBankAccount
// إذا حدث خطأ، اطبع "Error: [error message]" وقم بالإنهاء (return)
// إذا نجحت العملية، اطبع "Account created for [Owner] with balance: $[balance]"
// TODO: محاولة إيداع مبلغ الإيداع
// إذا حدث خطأ، اطبع "Error: [error message]"
// إذا نجحت العملية، اطبع "Deposited successfully. New balance: $[balance]"
// TODO: محاولة سحب مبلغ السحب
// إذا حدث خطأ، اطبع "Error: [error message]"
// إذا نجحت العملية، اطبع "Withdrew successfully. New balance: $[balance]"
// تذكر تنسيق قيم الرصيد بمنزلتين عشريتين باستخدام fmt.Printf("%.2f", balance)
_ = ownerName
_ = initialDeposit
_ = depositAmount
_ = withdrawAmount
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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 Pool