كود Go الخاص بك: من "يعمل" إلى "مقبول"
\n\nلقد كتبت كودًا بلغة Go. إنه يترجم. الاختبارات خضراء. أنت فخور. ولكن تأتي مراجعة الكود من مطور خبير، وبعد خمس دقائق، يصبح طلب السحب الخاص بك مغطى بتعليقات حمراء. هل هذا مألوف؟
\n\nGo هي لغة حيث "البساطة" خادعة. وراء السهولة الظاهرية تكمن عشرات المزالق التي تراها العين الخبيرة على الفور. في هذه المقالة، سنحلل الملاحظات الأكثر شيوعًا التي يتركها الخبراء في مراجعة كود Go. لن تتعلم فقط ما هو الخطأ، ولكن أيضًا كيف تصلحه، بحيث يمر كودك بالمراجعة من المرة الأولى.
\n\nهيا بنا نحلل الأخطاء النموذجية في مراجعة كود Golang.
\n\n1. معالجة الأخطاء: ليس فقط if err != nil
\n\nالمشكلة: تجاهل الأخطاء أو "return" عارية
\n\nالمشكلة الأكثر شيوعًا للمبتدئين هي معالجة الأخطاء غير الغنية بالمعلومات. مجرد إعادة الخطأ إلى الأعلى هو 50٪ من المهمة. الخبير سيسأل دائمًا: "ما الخطأ الذي حدث؟ أين؟".
\n\n// سيئ: الخبير سيقول "أضف سياقًا"\nfunc ReadConfig(path string) (*Config, error) {\n data, err := os.ReadFile(path)\n if err != nil {\n return nil, err // ❌ لا يوجد سياق\n }\n // ...\n}\n\n\nالنهج الصحيح هو تغليف الأخطاء باستخدام fmt.Errorf والعلامة %w (للتحقق اللاحق عبر errors.Is أو errors.As).
// جيد: الخبير سيوافق\nfunc ReadConfig(path string) (*Config, error) {\n data, err := os.ReadFile(path)\n if err != nil {\n return nil, fmt.Errorf("read config file %s: %w", path, err) // ✅ سياق + سلسلة\n }\n // ...\n}\n\n\nالمشكلة: تكرار if err != nil
\n\nتشتهر Go بالإسهاب في معالجة الأخطاء. لكن هذا لا يعني أنه يجب عليك قبول النسخ واللصق. سيقترح الخبير استخراج الفحوصات المتكررة إلى دوال مساعدة أو استخدام نمط "errgroup" للـ goroutines.
\n\n- \n
- لا تفعل هذا: 10 مرات متتالية
if err != nil { return ... }في دالة واحدة أمر طبيعي، ولكن إذا تكرر النمط في دوال مختلفة، فقد حان وقت إعادة الهيكلة. \n - نصيحة الخبير: استخدم
deferلإغلاق الموارد والتحقق من أخطاء الإغلاق (نعم،file.Close()يعيد خطأ أيضًا). \n
2. التزامن: سباقات البيانات والقنوات غير الصحيحة
\n\nالمشكلة: الوصول إلى متغير مشترك دون مزامنة
\n\nGo هي لغة حيث الـ goroutines رخيصة وسباقات البيانات باهظة الثمن. سيلاحظ الخبير على الفور إذا كنت تكتب في map أو slice من عدة goroutines دون mutex.
\n\n// سيئ: سباق بيانات\nvar counter int\nfor i := 0; i < 100; i++ {\n go func() {\n counter++ // ❌ سباق!\n }()\n}\n\n\nالحل: استخدام sync.Mutex أو العمليات الذرية. لكن الخبير سيقول: "أليس من الأسهل استخدام القنوات؟".
// جيد: القنوات للمزامنة\nch := make(chan int, 100)\nfor i := 0; i < 100; i++ {\n go func() {\n ch <- 1\n }()\n}\nvar counter int\nfor i := 0; i < 100; i++ {\n counter += <-ch // ✅ بدون سباقات\n}\n\n\nالمشكلة: تسرب الـ goroutines
\n\nهل نسيت إغلاق القناة؟ الـ goroutine ستعلق إلى الأبد. سيتحقق الخبير في مراجعة كود Go بالتأكيد مما إذا كان لديك goroutine معلقة بدون select مع ctx.Done().
// سيئ: قد تعلق الـ goroutine إلى الأبد\ngo func() {\n for msg := range ch {\n process(msg)\n }\n}()\n// إذا لم تُغلق ch أبدًا - تسرب ذاكرة\n\n\nالنمط الصحيح هو دائمًا تمرير context.Context والاستماع إلى إشارة الإلغاء.
// جيد: حماية من التسرب\ngo func(ctx context.Context) {\n for {\n select {\n case msg, ok := <-ch:\n if !ok {\n return // القناة مغلقة\n }\n process(m