تتصف البرامج التي تتسم بالمتانة بأنها قادرة على التعامل مع الأخطاء المتوقعة وغير المتوقعة التي قد تحدث عند استخدام البرنامج، فهناك أخطاء ناتجة عن مدخلات غير صحيحة من المستخدِم أو حدوث خطأ في عملية الاتصال بالشبكة، …إلخ.
نقصد بمعالجة الأخطاء Error handling التقاط الأخطاء التي تولّدها برامجنا وإخفائها عن المستخدم، فرسائل الأخطاء لا تخيف المبرمجين وإنما يمكن توقعها أحيانًا، إلا أن المستخدِم لا يتوقع رؤيتها وهي تربكه وتسبب له الحيرة، فإن كان سيرى رسالة خطأ للضرورة، فلتكن رسالةً سهلة الفهم، وحتى في هذه الحالة سيرغب المستخدِم في أن يحل المبرمج المشكلة، وهنا يأتي دور التعامل مع الأخطاء ومعالجتها، إذ توفِّر كل لغة تقريبًا آليةً لالتقاط الأخطاء عند حدوثها لمعرفة الأجزاء التي تعطلت واتخاذ الإجراء المناسب لإصلاح المشكلة.
لمعالجة الأخطاء في لغات البرمجة الأخرى، عادةً ما يتطلب الأمر من المُبرمجين استخدام بنية قواعد محدَّدة، إلا أنّ الأمر مُختلف في جو، إذ تُعَدّ الأخطاء قيمًا مع نوع الخطأ الذي يُعاد من الدالة مثل أيّ قيمة معادة أخرى، ولمعالجة الأخطاء في لغة جو، يجب عليك فحص هذه الأخطاء التي قد تُعيدها الدوال وتحديد ما إذا كان هناك خطأ فعلًا، واتخاذ الإجراء المناسب لحماية البيانات وإخبار المستخدِمين أو أجزاء البرنامج الأخرى بحدوث الخطأ.
إنشاء الأخطاء
قبل أن تبدأ بمعالجة الأخطاء عليك إنشاؤها أولًا، إذ توفر المكتبة القياسية دالتين مضمنتين لإنشاء أخطاء وهما ()errors.New
و ()fmt.Errorf
بحيث تتيح لك هاتان الدالتان تحديد رسالة خطأ مخصصة يمكنك تقديمها لاحقًا للمستخدِمين.
تأخذ الدالة ()errors.New
وسيطًا واحدًا يمثِّل سلسلةً تُمثّل رسالة الخطأ، بحيث يمكنك تخصيصها لتنبيه المستخدِمين بالخطأ، وسنستخدِم في المثال التالي الدالة ()errors.New
لإنشاء خطأ وستكون رسالة الخطأ هي "barnacles" ثم سنطبع هذا الخطأ من خلال الدالة ()fmt.Println
، ولاحظ أننا كتبنا جميع أحرف الرسالة بدون استخدام محارف كبيرة تقيّدًا بالطريقة التي تكتب بها الأخطاء في جو والتي تُكتب بمحارف صغيرة.
package main import ( "errors" "fmt" ) func main() { err := errors.New("barnacles") fmt.Println("Sammy says:", err) }
يكون الخرج كما يلي:
Sammy says: barnacles
تسمح لك الدالة ()fmt.Errorf
بإنشاء رسالة خطأ ديناميكيًا، بحيث يمثِّل الوسيط الأول لهذه الدالة سلسلةً تمثِّل رسالة الخطأ مع إمكانية استخدام العناصر النائبة مثل العنصر s%
لينوب عن سلسلة نصية والعنصر d%
لينوب عن عدد صحيح؛ أما الوسيط الثاني لهذه الدالة، فهو قيم العناصر النائبة بالترتيب كما في المثال التالي:
package main import ( "fmt" "time" ) func main() { err := fmt.Errorf("error occurred at: %v", time.Now()) fmt.Println("An error happened:", err) }
يكون الخرج كما يلي:
An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103
استخدمنا الدالة ()fmt.Errorf
لإنشاء رسالة خطأ تتضمن التوقيت الزمني الحالي، إذ تتضمن السلسلة المُعطاة إلى الدالة ()fmt.Errorf
العنصر النائب v%
والذي سيأخذ القيمة ()time.Now
لاحقًا عند استدعاء هذا الخطأ، وأخيرًا نطبع الخطأ من خلال دالة الطباعة كما فعلنا مع الدالة السابقة.
معالجة الأخطاء
ما سبق كان مُجرّد مثال لتوضيح كيفية إنشاء الأخطاء؛ أما الآن فستتعلم كيفية إنشائها وتوظيفها، فمن الناحية العملية، يكون من الشائع جدًا إنشاء خطأ وإعادته من دالة عندما يحدث خطأ ما، وبالتالي عند استدعاء هذه الدالة يمكن استخدام عبارة if
لمعرفة ما إذا كان الخطأ موجودًا أم أنه لايوجد خطأ، فعندما لا يكون هناك خطأً سنجعل الدالة تعيد قيمة nil
.
package main import ( "errors" "fmt" ) func boom() error { return errors.New("barnacles") } func main() { err := boom() if err != nil { fmt.Println("An error occurred:", err) return } fmt.Println("Anchors away!") }
يكون الخرج كما يلي:
An error occurred: barnacles
عرّفنا في هذا المثال دالة تُسمى boom()
تُعيد خطأً يُنشأ من خلال الدالة errors.New
لاحقًا عند استدعاء هذه الدالة والتقاط الخطأ في السطر err := boom
، فبعد إسناد الخطأ إلى المتغير err
سنتحقق مما إذا كان موجودًا من خلال التعليمة if err != nil
، وفي هذا المثال ستكون نتيجة الشرط دومًا true
لأننا نُعيد دومًا خطأً من الدالة، وطبعًا لن يكون الأمر دائمًا هكذا، لذلك يجب أن نحدد في الدالة الحالات التي يحدث فيها خطأ والحالات التي لا يحدث فيها خطأ وتكون القيمة المعادة nil
.
نستخدِم دالة الطباعة ()fmt.Println
لطباعة الخطأ كما في كل مرة عند وجود خطأ، ثم نستخدِم أخيرًا تعليمة return
لكي لا تُنفّذ تعليمة ("fmt.Println("Anchors away!
، فهذه التعليمة يجب أن تُنفّذ فقط عند عدم وجود خطأ.
ملاحظة: تُعَدّ التعليمة if err != nil
محورًا أساسيًا في عملية معالجة الأخطاء، فهي تنقل البرنامج إلى مسار مختلف عن مساره الأساسي في حال وجود خطأ أو تتركه يكمل مساره الطبيعي.
تتيح لك جو إمكانية استدعاء الدالة ومعالجة أخطاءها ضمن تعليمة الشرط if
مباشرةً، ففي المثال التالي سنكتب المثال السابق نفسه لكن من خلال الاستفادة من هذه السمة كما يلي:
package main import ( "errors" "fmt" ) func boom() error { return errors.New("barnacles") } func main() { if err := boom(); err != nil { fmt.Println("An error occurred:", err) return } fmt.Println("Anchors away!") }
يكون الخرج كما يلي:
An error occurred: barnacles
لاحظ أننا لم نغير كثيرًا عن الشيفرة السابقة، فكل ما فعلناه هو أننا استدعينا الدالة التي تُعيد الخطأ واختبرنا الشرط في السطر نفسه ضمن التعليمة if
.
تعلمنا في هذا القسم كيفية التعامل مع الدوال التي تعيد الخطأ فقط، وهذه الدوال شائعة، لكن من المهم أيضًا أن تكون قادرًا على معالجة الأخطاء من الدوال التي يمكن أن تُعيد قيمًا متعددةً.
إعادة الأخطاء والقيم
غالبًا ما تُستخدَم الدالات التي تُعيد قيمة خطأ واحدة في الحالات التي نحتاج فيها إلى إحداث تغييرات مُحددة مثل إدراج صفوف في قاعدة بيانات أي عندما نحتاج لمعرفة الحال الذي انتهت عليه العملية، ومن الشائع أيضًا كتابة دالات تُعيد قيمة إذا اكتملت العملية بنجاح أو خطأ مُحتمل إذا فشلت هذه العملية، كما تسمح جو للدالات بإعادة أكثر من نتيجة واحدة، وبالتالي إمكانية إعادة قيمة ونوع الخطأ في الوقت نفسه.
لإنشاء دالة تُعيد أكثر من قيمة واحدة، يجب تحديد أنواع كل قيمة مُعادة داخل أقواس ضمن ترويسة الدالة، فالدالة capitalize
مثلًا، تُعيد سلسلة string
وخطأ error
، وقد صرّحنا عن ذلك بكتابة شيفرة كتلية كما يلي:
func capitalize(name string) (string, error) {}
يخبر الجزء (سلسلة، خطأ) مُصرّف جو أنّ هذه الدالة ستعيد سلسلةً نصيةً وخطأً بهذا الترتيب، ويمكنك تشغيل البرنامج التالي لرؤية الخرج من هذه الدالة التي تُعيد قيمتين وهما سلسلة نصية وخطأ:
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, error) { if name == "" { return "", errors.New("no name provided") } return strings.ToTitle(name), nil } func main() { name, err := capitalize("sammy") if err != nil { fmt.Println("Could not capitalize:", err) return } fmt.Println("Capitalized name:", name) }
يكون الخرج كما يلي:
Capitalized name: SAMMY
عرّفنا الدالة ()capitalize
التي تأخذ سلسلة نصية على أساس وسيط وهي السلسلة التي نريد تحويل محارفها إلى محارف كبيرة، وتُعيد سلسلةً نصيةً وقيمة خطأ.
استدعينا في الدالة الرئيسية ()main
الدالة ()capitalize
وأسندنا القيم التي تُعيدها إلى المتغيرين name
و err
من خلال الفصل بينهما بفاصلة، ثم استخدمنا التعليمة الشرطية if err != nil
للتحقق من وجود خطأ والذي سنطبعه في حال وجوده ونخرج من خلال التعليمة return
وإلا سنكمل في المسار الطبيعي ونطبع (fmt.Println("Capitalized name:", name
.
مرَّرنا في المثال السابق الكلمة sammy
للدالة ()capitalize
، لكن إذا حاولت تمرير سلسلة فارغة ""
، فستحصل مباشرةً على رسالة الخطأ Could not capitalize: no name provided
، أي عند تمرير سلسلة فارغة، ستُعيد الدالة خطأً، وعند تمرير سلسلة عادية، ستستخدِم الدالة ()capitalize
الدالة strings.ToTitle
لتحويل السلسلة الممرَّرة إلى محارف كبيرة ثم تُعيدها، كما تُعيد في هذه الحالة nil
أيضًا لإشارة إلى عدم وجود خطأ.
هناك بعض الاصطلاحات الدقيقة التي اتبعها هذا المثال والتي تُعَدّ نموذجيةً في شيفرات جو ولكن ليست إجباريةً من قِبَل مصرِّف جو، فعندما تكون لدينا دالة تُعيد عدة قيم مثلًا، يُشاع أن تكون قيمة الخطأ المُعادة هي القيمة الأخيرة، أيضًا عندما تُعاد قيمة خطأ ما، فستُسنَد القيمة الصفرية إلى كل قيمة لا تمثّل خطأ، والقيم الصفرية مثلًا هي القيمة 0 في حالة الأعداد الصحيحة أو السلسلة الفارغة في حالة السلاسل النصية أو السجل الفارغ في حالة نوع البيانات struct
أو القيمة nil
في حالة المؤشر والواجهة interface، وقد تحدّثنا عن ذلك سابقًا بالتفصيل في مقال المتغيرات والثوابت.
تقليل استخدام الشيفرة المتداولة
يمكن أن يصبح الالتزام بهذه الاصطلاحات مملًا في المواقف التي تكون لدينا فيها العديد من القيم المُعادة من دالة، إذ يمكننا استخدام مفهوم الدالة مجهولة الاسم anonymous function للمساعدة في تقليل الشيفرة المتداولة boilerplate.
تُعَدّ الدوال مجهولة الاسم إجرائيات مسنَدَة إلى المتغيرات، على عكس الدوال التي عرَّفناها في الأمثلة السابقة، فهي متوفرة فقط ضمن الدوال التي تُصرّح عنها، وهذا يجعلها مثاليةً لتعمل على أساس أجزاء منطقية قصيرة مُساعدة وقابلة لإعادة الاستخدام.
سنعدّل المثال السابق بحيث نضيف له طول الاسم الذي نريد تحويل حالة المحارف فيه إلى الحالة الكبيرة، وبما أنه لدينا ثلاث قيم تجب إعادتها، فقد يصبح التعامل مع الأخطاء مرهقًا بدون دالة مجهولة الاسم تُساعدنا:
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, int, error) { handle := func(err error) (string, int, error) { return "", 0, err } if name == "" { return handle(errors.New("no name provided")) } return strings.ToTitle(name), len(name), nil } func main() { name, size, err := capitalize("sammy") if err != nil { fmt.Println("An error occurred:", err) } fmt.Printf("Capitalized name: %s, length: %d", name, size) }
يكون الخرج كما يلي:
Capitalized name: SAMMY, length: 5
نستقبل 3 وسائط مُعادة من الدالة ()capitalize
داخل الدالة ()main
، وهي name
و size
و err
على التوالي، ثم نختبر بعد ذلك فيما إذا كانت الدالة ()capitalize
قد أعادت خطأً أم لا وذلك من خلال فحص قيمة المتغير err
إذا كان nil
أم لا، فمن المهم فعل ذلك قبل محاولة استخدام أيّ من القيم الأخرى المُعادة من الدالة capitalize
لأن الدالة مجهولة الاسم handle
يمكن أن تضبطها على قيم صفرية، وفي هذا المثال لم نُمرِّر سلسلةً فارغةً، لذا أكمل البرنامج عمله وفقًا للمسار الطبيعي، ويمكنك تمرير سلسلة فارغة لترى أنّ الخرج سيكون رسالة خطأ An error occurred: no name provided
.
عرّفنا المتغير handle
داخل الدالة capitalize
وأسندنا إليه دالة مجهولة الاسم، أي أنّ هذا المتغير أصبح يُمثّل دالةً مجهولة الاسم، وستأخذ خطأ error
على أساس وسيط وتُعيد قيمًا تُطابق القيم التي تُعيدها الدالة capitalize
وبالترتيب نفسه لكن تجعل قيمها صفريّة، كما تُعيد الدالة الخطأ الذي مرِّر لها أيضًا كما هو، وبناءً على ذلك سيكون بإمكاننا إعادة أي أخطاء تحدث في الدالة capitalize
باستخدام تعليمة return
يليها استدعاء الدالة مجهولة الاسم handle
مع تمرير الخطأ على أساس وسيط.
تذكَّر أنّ الدالة capitalize
يجب أن تُعيد دومًا ثلاث قيم فهكذا عرّفناها، ولكن في بعض الأحيان لا نريد التعامل مع جميع القيم التي يمكن أن تُعيدها الدالة، لذا لحسن الحظ يمكننا التعامل مع هذا الأمر من خلال استخدام الشَرطة السفلية _
كما سترى بعد قليل.
معالجة الأخطاء في الدوال التي تعيد عدة قيم
عندما تُعيد الدالة العديد من القيم، ينبغي علينا إسناد كل منها إلى متغير وهذا ما فعلناه في المثال السابق مع الدالة capitalize
، كما يجب فصل هذه المتغيرات بفواصل.
لا نحتاج في بعض الأحيان إلا لقيم محددة منها، فقد لا نحتاج مثلًا إلا لقيمة الخطأ error
، ففي هذه الحالة يمكنك استخدام الشرطة السفلية _
لتجاهل القيم الأُخرى المُعادة، وقد عدّلنا المثال الأول عن الدالة ()capitalize
في المثال التالي ومرّرنا لها سلسلةً فارغةً لكي تُعطينا خطأً واستخدمنا الشرطة السفلية لتجاهل القيمة الأولى التي تُعيدها الدالة كما يلي:
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, error) { if name == "" { return "", errors.New("no name provided") } return strings.ToTitle(name), nil } func main() { _, err := capitalize("") if err != nil { fmt.Println("Could not capitalize:", err) return } fmt.Println("Success!") }
يكون الخرج كما يلي:
Could not capitalize: no name provided
استدعينا الدالة ()capitalize
في المثال أعلاه داخل الدالة الرئيسية ()main
وأسندنا القيم المُعادة منها إلى المتغير _
والمتغير err
على التوالي، وبذلك نكون قد تجاهلنا القيمة الأولى المُعادة من الدالة واحتفظنا بقيمة الخطأ داخل المتغير err
، وما تبقى شرحناه سابقًا.
تعريف أنواع أخطاء جديدة مخصصة
نحتاج في بعض الأوقات إلى تعريف أنواع أكثر تعقيدًا من الأخطاء من خلال تنفيذ الواجهة error
، إذ لا تكفينا دوال المكتبة القياسية ()errors.New
و ()fmt.Errorf
في بعض الأحيان لالتقاط ما حدث والإبلاغ عنه بالطريقة المناسبة، لذا تكون البنية التي نحتاج إلى تحقيقها كما يلي:
type error interface { Error() string }
تتضمّن الواجهة error
تابعًا وحيدًا هو ()Error
والذي يُعيد سلسلةً نصيةً تمثّل رسالة خطأ، فبهذا التابع ستتمكن من تعريف الخطأ بالطريقة التي تناسبك، وفي المثال التالي سننفِّذ الواجهة error
كما يلي:
package main import ( "fmt" "os" ) type MyError struct{} func (m *MyError) Error() string { return "boom" } func sayHello() (string, error) { return "", &MyError{} } func main() { s, err := sayHello() if err != nil { fmt.Println("unexpected error: err:", err) os.Exit(1) } fmt.Println("The string:", s) }
سنرى الخرج التالي:
unexpected error: err: boom exit status 1
عرّفنا نوع بيانات عبارة عن سجل struct
فارغ واسميناه MyError
، كما عرّفنا التابع ()Error
داخله بحيث يُعيد الرسالة "boom"
.
نستدعي الدالة sayHello
داخل الدالة الرئيسية ()main
التي تُعيد سلسلةً فارغةً ونسخةً جديدةً من MyError
، وبما أنّ sayHello
ستُعطي خطأً دومًا، فسيُنفَّذ استدعاء ()fmt.Println
الموجود داخل التعليمة الشرطية دومًا وستُطبع رسالة الخطأ.
لاحظ أنه لا نحتاج إلى استدعاء التابع ()Error
مُباشرةً لأن الحزمة fmt
قادرة تلقائيًا على اكتشاف أنّ هذا تنفيذ للواجهة error
، وبالتالي يُستدعى التابع ()Error
تلقائيًّا وتُطبع رسالة الخطأ.
الحصول على معلومات تفصيلية عن خطأ
يكون الخطأ المخصص custom error عادةً هو أفضل طريقة لالتقاط معلومات تفصيلية عن خطأ، فلنفترض مثلًا أننا نريد التقاط رمز الحالة status code عند حدوث أخطاء ناتجة عن طلب HTTP
، لذا سننفِّذ الواجهة error
في البرنامج التالي بحيث يمكننا التقاط هكذا معلومات:
package main import ( "errors" "fmt" "os" ) type RequestError struct { StatusCode int Err error } func (r *RequestError) Error() string { return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err) } func doRequest() error { return &RequestError{ StatusCode: 503, Err: errors.New("unavailable"), } } func main() { err := doRequest() if err != nil { fmt.Println(err) os.Exit(1) } fmt.Println("success!") }
سنرى الخرج التالي:
status 503: err unavailable exit status 1
أنشأنا في هذا المثال نسخةً من RequestError
وزوّدناها برمز الحالة والخطأ باستخدام الدالة errors.New
من المكتبة القياسية، ثم نستخدِم بعد ذلك الدالة ()fmt.Println
لطباعتها كما هو الحال في الأمثلة السابقة. استخدمنا الدالة ()fmt.Sprintf
داخل التابع ()Error
في RequestError
لإنشاء سلسلة باستخدام المعلومات المقدَّمة عند إنشاء الخطأ.
توكيدات النوع والأخطاء المخصصة
تعرض الواجهة error
دالةً واحدةً فقط، لكن قد نحتاج إلى الوصول إلى دوال أخرى من تنفيذات أخرى للواجهة error
لمعالجة الخطأ بطريقة مناسبة، فقد يكون لدينا مثلًا العديد من التنفيذات للواجهة error
والتي تكون مؤقتةً ويمكن إعادة طلبها، إذ يُشار إليها بوجود التابع ()Temporary
.
توفِّر الواجهات رؤيةً ضيقةً لمجموعة أوسع من التوابع التي يمكن للأنواع أن توفرها، لذلك يجب علينا تطبيق عملية توكيد النوع type assertion لتغيير التوابع التي تُعرض أو إزالتها بالكامل، ويوسِّع المثال التالي النوع RequestError
بتضمينه التابع ()Temporary
والذي سيشير إذا كان يجب على من يستدعي الدالة إعادة محاولة الطلب أم لا:
package main import ( "errors" "fmt" "net/http" "os" ) type RequestError struct { StatusCode int Err error } func (r *RequestError) Error() string { return r.Err.Error() } func (r *RequestError) Temporary() bool { return r.StatusCode == http.StatusServiceUnavailable // 503 } func doRequest() error { return &RequestError{ StatusCode: 503, Err: errors.New("unavailable"), } } func main() { err := doRequest() if err != nil { fmt.Println(err) re, ok := err.(*RequestError) if ok { if re.Temporary() { fmt.Println("This request can be tried again") } else { fmt.Println("This request cannot be tried again") } } os.Exit(1) } fmt.Println("success!") }
يكون الخرج كما يلي:
unavailable This request can be tried again exit status 1
نستدعي الدالة ()doRequest
ضمن الدالة main
التي تُعيد لنا الواجهة error
، إذ نطبع أولًا رسالة الخطأ المُعادة من التابع ()Error
ثم نحاول كشف جميع التوابع من ()RequestError
باستخدام توكيد النوع (re, ok := err.(*RequestError
، فإذا نجح توكيد النوع، فإننا سنستخدِم التابع ()Temporary
لمعرفة ما إذا كان هذا الخطأ خطأً مؤقتًا.
بما أنّ المتغير StatusCode
الذي هُيئ من خلال الدالة ()doRequest
يحمل القيمة 503 والذي يتطابق مع http.StatusServiceUnavailable
، فإنّ ذلك يُعيد true
وبالتالي طباعة "This request can be tried again"
، كما يمكننا من الناحية العملية تقديم طلب آخر بدلًا من طباعة رسالة.
تغليف الأخطاء
سيكون منشأ الخطأ غالبًا خارجيًا أي من خارج برنامجك مثل قاعدة بيانات واتصال بالشبكة ومدخلات مستخدِم غير صحيحة، …إلخ، فرسائل الخطأ المقدَّمة من هذه الأخطاء لا تساعد أيّ شخص في العثور على أصل الخطأ.
سيُقدِّم تغليف الأخطاء بمعلومات إضافية في بداية رسالة الخطأ رؤيةً أفضل (معلومات عن السياق أو الطبيعة التي حدث فيها) لتصحيح الأخطاء بنجاح، ويوضِّح المثال التالي كيف يمكننا إرفاق بعض المعلومات السياقية لخطأ خفي من دالة أخرى:
package main import ( "errors" "fmt" ) type WrappedError struct { Context string Err error } func (w *WrappedError) Error() string { return fmt.Sprintf("%s: %v", w.Context, w.Err) } func Wrap(err error, info string) *WrappedError { return &WrappedError{ Context: info, Err: err, } } func main() { err := errors.New("boom!") err = Wrap(err, "main") fmt.Println(err) }
يكون الخرج كما يلي:
main: boom!
يحتوي السجل WrappedError
على حقلين هما رسالة عن السياق على هيئة سلسلة نصية والخطأ الذي يقدِّم عنه معلومات إضافيةً، فعندما يُستدعى التابع ()Error
، فإننا نستخدِم ()fmt.Sprintf
مرةً أخرى لطباعة رسالة السياق ثم الخطأ، إذ يستدعي ()fmt.Sprintf
التابع ()Error
ضمنيًا.
نستدعي الدالة errors.New
داخل الدالة ()main
ثم نغلِّف الخطأ باستخدام الدالة Wrap
التي عرّفناها، إذ يسمح لنا ذلك بالإشارة إلى أنّ هذا الخطأ قد أُنشئ في الدالة main
، وبما أنّ WrappedError
هي خطأ، لذا يمكننا تغليف العديد منها، إذ يسمح لنا ذلك بالحصول على سلسلة تمكننا من تتبع مصدر الخطأ، كما يمكننا أيضًا تضمين كامل مسار المكدس في الأخطاء التي تحدث مع القليل من المساعدة من المكتبة القياسية.
بما أن الواجهة error
لا تحتوي إلا تابعًا واحدًا، فسيمنحنا ذلك مرونةً كبيرةً في تقديم أنواع مختلفة من الأخطاء لمواقف مختلفة، ويمكن أن يشمل ذلك كل شيء بدءًا من وصل أجزاء متعددة من المعلومات على أساس جزء من الخطأ الأساسي وصولًا إلى تحقيق التراجع الأسي Exponential backoff.
الخاتمة
رأينا في هذا المقال العديد من الطرق لإنشاء الأخطاء باستخدام المكتبة القياسية وكيفية إنشاء دوال تُعيد الأخطاء بطريقة اصطلاحية، وتمكَّنا أيضًا من إنشاء العديد من الأخطاء بنجاح باستخدام دوال المكتبة القياسية ()errors.New
و ()fmt.Errorf
، كما تعلّمت أيضًا كيفية إنشاء أنواع أخطاء مُخصصة وكيفية تتبع الأخطاء التي تحدث في البرنامج من خلال تغليفها.
وبطبيعة الحال، الأخطاء البرمجية موجودة وشائعة في مجال البرمجة ولغات البرمجة عمومًا، لذا من أجل التعرف على الأخطاء البرمجية عامةً والتعرف على كيفية التعامل معها، ندعوك لمشاهدة الفيديو الأتي:
ترجمة -وبتصرف- للمقال Handling Errors in Go وللمقال Creating Custom Errors in Go لصاحبه Gopher Guides.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.