Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

أخطاء Sentinel

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 55 من 107.

يُعد خطأ الحارس (sentinel error) متغير خطأ معرفاً مسبقاً على مستوى الحزمة، ويمثل حالة خطأ محددة. وعلى عكس إنشاء أخطاء جديدة في كل مرة باستخدام errors.New، يتم التصريح عن أخطاء الحارس مرة واحدة وإعادة استخدامها في جميع أنحاء الكود الخاص بك.

تقوم بتعريف أخطاء الحارس (sentinel errors) كمتغيرات على مستوى الحزمة، وعادةً ما تبدأ أسماؤها بـ Err:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

تُرجع الدوال هذه الأخطاء المحددة مسبقاً عند حدوث شروط معينة:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

تكمن قوة أخطاء الحارس (sentinel errors) في قدرة المستدعين على التحقق من حالات خطأ معينة عبر مقارنة قيم الأخطاء مباشرة:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

تستخدم مكتبة Go القياسية أخطاء الحارس (sentinel errors) بشكل مكثف. على سبيل المثال، يشير io.EOF إلى نهاية الإدخال، ويشير sql.ErrNoRows إلى نتيجة استعلام فارغة. ينشئ هذا النمط عقداً واضحاً بين الحزم ومستخدميها حول الأخطاء المتوقع حدوثها.

ومع ذلك، فإن عمليات التحقق البسيطة من المساواة تتعطل عندما يتم تغليف الأخطاء. في الدرس القادم، ستتعلم كيف يقوم errors.Is() بحل هذه المشكلة عن طريق التحقق من سلسلة الأخطاء بالكامل.

challenge icon

التحدي

سهل

لنقم ببناء نظام لإدارة المخزون يستخدم أخطاء الحارس (sentinel errors) للإبلاغ عن حالات فشل محددة. ستقوم بتعريف متغيرات خطأ على مستوى الحزمة (package-level) بحيث يمكن للمستدعين التحقق منها للتعامل مع السيناريوهات المختلفة بشكل مناسب.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • inventory.go: أنشئ نظام مخزون مع أخطاء حارس محددة مسبقاً لحالات الفشل الشائعة.

    قم بتعريف ثلاثة أخطاء حارس على مستوى الحزمة:

    • ErrItemNotFound مع الرسالة "item not found in inventory"
    • ErrOutOfStock مع الرسالة "item is out of stock"
    • ErrInsufficientQuantity مع الرسالة "insufficient quantity available"

    أنشئ هيكل Inventory يحتوي على خريطة (map) لأسماء العناصر وكمياتها. قم بتنفيذ هذه الدوال (methods):

    • NewInventory() *Inventory - ينشئ مخزوناً فارغاً
    • AddItem(name string, quantity int) - يضيف أو يحدث كمية عنصر ما
    • GetQuantity(name string) (int, error) - يعيد كمية العنصر، أو ErrItemNotFound إذا لم يكن موجوداً
    • Purchase(name string, quantity int) error - يحاول شراء عناصر. أعد ErrItemNotFound إذا لم يكن العنصر موجوداً، أو ErrOutOfStock إذا كانت الكمية الحالية صفراً، أو ErrInsufficientQuantity إذا كانت الكمية المطلوبة تتجاوز المخزون المتاح. في حال النجاح، قم بتقليل المخزون وأعد nil.
  • main.go: اقرأ عمليات المخزون من المدخلات ووضح كيف يمكن للمستدعين التحقق من أخطاء حارس محددة باستخدام مقارنة المساواة المباشرة.

    اقرأ اسم العنصر وكمية الشراء من المدخلات. أنشئ مخزوناً، وأضف العنصر "laptop" بكمية 5، ثم حاول إجراء عملية الشراء. بناءً على الخطأ المعاد، اطبع رسالة محددة توضح أنك حددت أي خطأ حارس قد حدث.

سيتم توفير المدخلات التالية:

  • السطر 1: اسم العنصر المراد شراؤه
  • السطر 2: الكمية المراد شراؤها

تعامل مع النتيجة عن طريق التحقق من الخطأ المحدد الذي تمت إعادته:

  • إذا كان err == ErrItemNotFound: اطبع Error: Item '[name]' does not exist in our inventory
  • إذا كان err == ErrOutOfStock: اطبع Error: Item '[name]' is currently out of stock
  • إذا كان err == ErrInsufficientQuantity: اطبع Error: Cannot purchase [quantity] units of '[name]' - not enough in stock
  • في حال النجاح: اطبع Successfully purchased [quantity] units of '[name]'

على سبيل المثال، إذا تم إدخال laptop و 3، يجب أن يكون مخرجك:

Successfully purchased 3 units of 'laptop'

وإذا تم إدخال phone و 2، يجب أن يكون مخرجك:

Error: Item 'phone' does not exist in our inventory

وإذا تم إدخال laptop و 10، يجب أن يكون مخرجك:

Error: Cannot purchase 10 units of 'laptop' - not enough in stock

ورقة مرجعية

يُعد الخطأ المرجعي (sentinel error) متغيراً للخطأ محدد مسبقاً على مستوى الحزمة، ويمثل حالة خطأ محددة. يتم التصريح عن الأخطاء المرجعية مرة واحدة ويُعاد استخدامها في جميع أنحاء الكود الخاص بك.

قم بتعريف الأخطاء المرجعية كمتغيرات على مستوى الحزمة، وعادةً ما تبدأ أسماؤها بـ Err:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

تُرجع الدوال هذه الأخطاء المحددة مسبقاً عند حدوث حالات معينة:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

يمكن للمستدعين التحقق من حالات خطأ محددة عن طريق مقارنة قيم الأخطاء مباشرة:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

تستخدم مكتبة Go القياسية الأخطاء المرجعية بشكل مكثف، مثل io.EOF و sql.ErrNoRows.

جرّب بنفسك

package main

import (
	"fmt"
)

func main() {
	// قراءة المدخلات
	var itemName string
	var quantity int
	fmt.Scanln(&itemName)
	fmt.Scanln(&quantity)

	// إنشاء المخزون وإضافة "laptop" بكمية 5
	inv := NewInventory()
	inv.AddItem("laptop", 5)

	// TODO: محاولة شراء العنصر
	// استدعاء inv.Purchase(itemName, quantity)

	// TODO: التحقق من خطأ sentinel المحدد الذي تم إرجاعه باستخدام مقارنة المساواة المباشرة
	// استخدم عبارات if/else if للتحقق من:
	// - err == ErrItemNotFound
	// - err == ErrOutOfStock
	// - err == ErrInsufficientQuantity
	// - err == nil (نجاح)

	// TODO: طباعة الرسالة المناسبة بناءً على الخطأ
	// في حال ErrItemNotFound: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
	// في حال ErrOutOfStock: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
	// في حال ErrInsufficientQuantity: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
	// في حال النجاح: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming