error
لا تتم معالجة الأخطاء في Go عبر الاستثناءات.
بل إن الأخطاء قيم عادية من أنواع تُطبّق الواجهة المدمجة error.
وواجهة error بسيطة للغاية.
فهي تحتوي على طريقة واحدة فقط هي Error()، التي تُرجع رسالة الخطأ على هيئة سلسلة نصية.
type error interface {
Error() string
}
كلما عرّفت دالة قد يحدث فيها خطأ أثناء التنفيذ ويحتاج إلى الوصول إلى المستدعي، عليك أن تُضمّن error ضمن أنواع الإرجاع.
وإذا كانت للدالة قيم إرجاع متعددة، فمن المتعارف عليه أن يأتي error دائمًا في المرتبة الأخيرة.
func DoSomething() (int, error) {
// ...
}
لا يلزمك دائمًا أن تُطبّق واجهة error بنفسك.
لإنشاء خطأ بسيط، يمكنك استخدام الدالة errors.New() من حزمة المكتبة القياسية errors.
كل ما تحتاج إلى تمريره هو رسالة الخطأ على هيئة سلسلة نصية، وستتولى errors.New() إنشاء قيمة تحتوي على رسالتك وتُطبّق واجهة error.
وإذا أرجعت الدالة خطأً، فمن الممارسات الجيدة إرجاع القيمة الصفرية لبقية معاملات الإرجاع:
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
لا ينبغي أن تفترض أن جميع الدوال تُرجع قيمًا صفرية لبقية قيم الإرجاع عند وجود خطأ. من أفضل الممارسات أن تفترض أنه لا يمكن الاعتماد على أي من قيم الإرجاع الأخرى إذا حدث خطأ. الاستثناء الوحيد هو الحالات التي يذكر فيها التوثيق بوضوح أن قيم الإرجاع الأخرى ذات معنى عند حدوث خطأ.
إذا أردت استخدام خطأ بسيط كهذا في مواضع متعددة، فبدلًا من استخدام errors.New في موضعها، عرّف متغيرًا للخطأ.
ومن المتعارف عليه أن يبدأ اسم المتغير بـ Err أو err (حسب ما إذا كان مُصدَّرًا أم لا).
غالبًا ما تُسمّى متغيرات الأخطاء هذه الأخطاء الحارسة.
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
أرجِع nil للخطأ لتشير إلى عدم حدوث أي أخطاء أثناء تنفيذ الدالة:
func Foo() (int, error) {
return 10, nil
}
إذا استدعيت دالة تُرجع خطأً، فمن الشائع تخزين قيمة الخطأ في متغير باسم err.
وقبل أن تستخدم النتيجة الفعلية للدالة، عليك أن تتحقق من عدم وجود خطأ.
ولتجنّب تعشيش «المسار السعيد» في كودك، ينبغي التعامل مع حالات الخطأ أولًا.
يمكننا استخدام == و!= لمقارنة الخطأ بـ nil، ونعرف أن هناك خطأ عندما لا يكون err مساويًا لـ nil.
func processUserFile() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
في معظم الحالات، سيُرجع الخطأ إلى أعلى مكدّس الدوال كما في المثال أعلاه. وثمة أسلوب آخر لمعالجة الخطأ، وهو تسجيله ثم المتابعة بعملية أخرى. ومن الممارسات الجيدة إما إرجاع الخطأ أو تسجيله، وليس كليهما.
ولأن معظم الدوال في Go تتضمن خطأً ضمن قيم الإرجاع، فسترى نمط if err != nil وتستخدمه في كل مكان في كود Go.
إذا أردت أن يتضمن خطؤك معلومات أكثر من مجرد سلسلة رسالة الخطأ، يمكنك إنشاء نوع خطأ مخصص.
وكما ذُكر سابقًا، فإن كل ما يُطبّق واجهة error (أي يمتلك طريقة Error() string) يمكن أن يكون خطأً في Go.
وعادةً ما تُستخدم بنية لإنشاء نوع خطأ مخصص.
ومن المتعارف عليه أن تنتهي أسماء أنواع الأخطاء المخصصة بـ Error.
ومن الأفضل أيضًا تعريف طريقة Error() string بمستقبِل مؤشّر، انظر تعليق Stackoverflow هذا لتعرف السبب.
لاحظ أن هذا يعني أنك تحتاج إلى إرجاع مؤشر إلى خطئك المخصص، وإلا فلن يُحتسب error لأن القيمة غير المؤشّرة لا توفّر طريقة Error() string.
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}
جاء أخيرًا اليوم الذي انتظرته طويلًا، وأصبحت الآن المالك الفخور بمزرعة جميلة في جبال الألب.
ما زلت لا تحب الاستيقاظ مبكرًا جدًا في الصباح لإطعام أبقارك.
ولأنك مهندس بارع، تصنع موزّعًا للطعام اسمه FEED-M-ALL.
آخر ما تحتاجه لإكمال مشروعك هو قطعة كود تحسب مقدار العلف الذي ينبغي أن تحصل عليه كل بقرة. من المهم أن تحصل كل بقرة على المقدار نفسه، فعليك تجنّب النزاعات. الأبقار حساسة جدًا.
لحسن الحظ، لست بحاجة إلى استنباط كل صيغ حساب كميات العلف بنفسك. أنت تستخدم مكتبة خارجية غامضة وجدتها على الإنترنت. من المفترض أن تؤدي إلى أسعد الأبقار. توفّر المكتبة نوعًا يحقق الواجهة التالية. ستعتمد على ذلك في الكود الذي تكتبه بنفسك.
وأثناء عملك على الكود، ستحسّن معالجة الأخطاء لتصبح أكثر متانة وأسهل في تصحيح الأخطاء لاحقًا عندما تستخدمه في حياتك اليومية في المزرعة.
type FodderCalculator interface {
FodderAmount(int) (float64, error)
FatteningFactor() (float64, error)
}
أولًا، تركّز على كتابة الكود اللازم لحساب مقدار العلف لكل بقرة.
نفّذ دالة DivideFood تستقبل FodderCalculator وعددًا صحيحًا يمثل عدد الأبقار كوسائط.
في هذه المهمة، افترض أن عدد الأبقار المُمرَّر دائمًا أكبر من صفر.
ينبغي أن تُرجع الدالة مقدار الطعام لكل بقرة كقيمة float64، أو خطأً إن حدث أحدها.
لإجراء الحساب، تحتاج أولًا إلى جلب المقدار الكلي للعلف الخاص بكل الأبقار.
يتم ذلك باستدعاء طريقة FodderAmount وتمرير عدد الأبقار.
بالإضافة إلى ذلك، تحتاج إلى معامل ضرب يجب ضرب هذا المقدار فيه.
تحصل على معامل الضرب هذا باستدعاء طريقة FatteningFactor.
بهاتين القيمتين وعدد الأبقار، يمكنك الآن حساب مقدار الطعام لكل بقرة (كقيمة float64).
وهذا ما ينبغي أن تُرجعه دالة DivideFood.
إذا أرجعت إحدى الطرق التي تستدعيها خطأً، فينبغي أن يتوقف التنفيذ وأن يُرجع ذلك الخطأ (كما هو) من دالة DivideFood.
// For this example, we assume FodderAmount returns 50
// and FatteningFactor returns 1.5.
DivideFood(fodderCalculator, 5)
// => 15 <nil>
// Now assuming FodderAmount returns an error with message "something went wrong".
DivideFood(fodderCalculator, 5)
// => 0 "something went wrong"
أثناء عملك على المهمة الأولى أعلاه، أدركت أن المكتبة الخارجية التي تستخدمها ليست بجودة عالية كما كنت تتوقع. فهي على سبيل المثال لا تستطيع التعامل بشكل صحيح مع المدخلات غير الصالحة. تريد التحايل على هذا القيد بإضافة تحقق من قيمة المدخلات في الكود الذي تكتبه بنفسك.
اكتب دالة ValidateInputAndDivideFood لها التوقيع نفسه الذي لدالة DivideFood أعلاه.
DivideFood وتُرجع نتائج ذلك الاستدعاء."invalid number of cows".ValidateInputAndDivideFood(fodderCalculator, 5)
// => 15 <nil>
ValidateInputAndDivideFood(fodderCalculator, -2)
// => 0 "invalid number of cows"
كان التحقق من عدد الأبقار قبل تمريره خطوة جيدة، لكنك لست راضيًا تمامًا عن رسالة الخطأ غير المحددة.
تقرر أن تفعل ما هو أفضل بإنشاء نوع خطأ مخصص اسمه InvalidCowsError.
ينبغي أن يحمل الخطأ المخصص عدد الأبقار (int) ورسالة مخصصة (string)، وينبغي أن تُسلسل طريقة Error البيانات بالصيغة التالية:
{number of cows} cows are invalid: {custom message}
وبعد أن أصبح لديك خطأك المخصص، نفّذ دالة ValidateNumberOfCows تستقبل عدد الأبقار كعدد صحيح وتُرجع خطأً (أو nil).
InvalidCowsError برسالة مخصصة قيمتها "there are no negative cows".InvalidCowsError برسالة مخصصة قيمتها "no cows don't need food".nil للإشارة إلى أن التحقق نجح.err := ValidateNumberOfCows(-5)
err.Error()
// => "-5 cows are invalid: there are no negative cows"
بعد العمل الشاق في إعداد دالة التحقق هذه، تلاحظ أن المساء قد حلّ، فتترك مكتبك لتستمتع بغروب الشمس فوق الجبال. وتؤجل إلى يوم آخر مهمة إضافة دالة التحقق الجديدة فعليًا إلى الكود.
سجّل في Exercism لتتعلّم وتتقن Go عبر 34 مفهومًا165 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.