أهم أخطاء مطوري Go المبتدئين: من الألف إلى الياء

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا Go بسيطة ولكنها ليست سهلة

\n\n

غالبًا ما يُشاد بلغة Go (Golang) لبساطتها وإيجازها. ومع ذلك، فإن هذه البساطة غالبًا ما تخدع المبتدئين القادمين من لغات أكثر "ثراءً" مثل Python أو Java. في هذه المقالة، قمنا بتجميع أهم أخطاء مطوري Go المبتدئين - من المشكلات النموذجية في معالجة الأخطاء إلى المزالق غير الواضحة عند العمل مع goroutines. إذا كنت قد بدأت للتو رحلتك في عالم Golang، فإن هذه المجموعة ستساعدك على تجنب الوقوع في نفس الأخطاء التي وقع فيها الكثيرون قبلك.

\n\n

1. تجاهل الأخطاء والمعالجة غير الصحيحة

\n\n

الخطأ الأكثر شيوعًا في Go هو تجاهل الأخطاء المرتجعة. على عكس الاستثناءات (exceptions) في Python أو Java، يستخدم Go التحقق الصريح من الأخطاء من خلال القيمة المرتجعة. غالبًا ما يكتب المبتدئون:

\n\n
package main\n\nimport (\n    "fmt"\n    "os"\n)\n\nfunc main() {\n    // ❌ خطأ: تجاهل الخطأ\n    file, _ := os.Open("file.txt")\n    fmt.Println(file.Name())\n}\n
\n\n

هنا، الرمز _ (المعرف الفارغ) يقول حرفيًا للمترجم: "لا يهمني الخطأ". في مشروع حقيقي، سيؤدي هذا إلى حالة ذعر (panic) أو عمل غير صحيح. النهج الصحيح هو التحقق دائمًا من الخطأ:

\n\n
package main\n\nimport (\n    "fmt"\n    "os"\n)\n\nfunc main() {\n    file, err := os.Open("file.txt")\n    if err != nil {\n        // ✅ معالجة الخطأ\n        fmt.Println("خطأ في فتح الملف:", err)\n        return\n    }\n    defer file.Close()\n    fmt.Println("تم فتح الملف بنجاح:", file.Name())\n}\n
\n\n

نصيحة: لا تستخدم أبدًا _ للأخطاء، إلا إذا كنت متأكدًا بنسبة 100% من استحالة حدوث الخطأ. من الأفضل كتابة سطر إضافي if err != nil بدلاً من البحث عن خطأ (bug) في الليل قبل الموعد النهائي.

\n\n

2. مشاكل مع goroutines: سباق البيانات والتسريبات

\n\n

تشتهر Go بسهولة العمل مع التزامن (concurrency)، ولكن هذا يخلق أيضًا مزالق. غالبًا ما ينسى المبتدئون مزامنة الوصول إلى البيانات المشتركة.

\n\n

2.1. سباق البيانات (Data Race)

\n\n

تخيل أن عدة goroutines تكتب في نفس المتغير في نفس الوقت:

\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n)\n\nfunc main() {\n    counter := 0\n    var wg sync.WaitGroup\n\n    for i := 0; i < 1000; i++ {\n        wg.Add(1)\n        go func() {\n            // ❌ خطأ: سباق بيانات\n            counter++\n            wg.Done()\n        }()\n    }\n    wg.Wait()\n    fmt.Println("Counter:", counter) // النتيجة غير متوقعة\n}\n
\n\n

ستكون النتيجة مختلفة في كل مرة يتم تشغيلها. الحل: استخدام كائن المزامنة (mutex) أو العمليات الذرية (atomic operations):

\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n    "sync/atomic"\n)\n\nfunc main() {\n    var counter int64\n    var wg sync.WaitGroup\n\n    for i := 0; i < 1000; i++ {\n        wg.Add(1)\n        go func() {\n            // ✅ استخدام الجمع الذري\n            atomic.AddInt64(&counter, 1)\n            wg.Done()\n        }()\n    }\n    wg.Wait()\n    fmt.Println("Counter:", counter) // دائماً 1000\n}\n
\n\n

2.2. تسريب goroutines

\n\n

خطأ كلاسيكي آخر هو تشغيل goroutine ونسيانها. إذا كانت goroutine تنتظر بيانات من قناة (channel) لن تُغلق أبدًا، فستظل معلقة إلى الأبد، مما يستهلك الذاكرة:

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc worker(ch chan int) {\n    for msg := range ch {\n        fmt.Println(msg)\n    }\n}\n\nfunc main() {\n    ch := make(chan int)\n    go worker(ch) // ❌ خطأ: goroutine لن تنتهي أبدًا\n    ch <- 1\n    ch <- 2\n    // القناة غير مغلقة -> تسريب\n    time.Sleep(time.Second)\n}\n
\n\n

الحل: أغلق القناة دائمًا عند الانتهاء من إرسال البيانات، أو استخدم السياق (context) للإلغاء.

\n\n

3. الاستخدام غير الصحيح للشرائح nil والخرائط

\n\n

في Go، n

المدونات

توصيات الكتب