اذهب إلى المحتوى

معالجة الأخطاء في لغة جو Go


هدى جبور

تتصف البرامج التي تتسم بالمتانة بأنها قادرة على التعامل مع الأخطاء المتوقعة وغير المتوقعة التي قد تحدث عند استخدام البرنامج، فهناك أخطاء ناتجة عن مدخلات غير صحيحة من المستخدِم أو حدوث خطأ في عملية الاتصال بالشبكة، …إلخ.

نقصد بمعالجة الأخطاء 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.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...