لماذا يتعطل كود Go الخاص بك، وكيف تصلحه دون صداع
\n\nلقد كتبت كودًا مثاليًا في Go حسب رأيك. مرت عملية التجميع بنجاح، والاختبارات خضراء... ولكن في الإنتاج ينهار كل شيء. مألوف؟ مرحبًا بك في عالم التطوير الحقيقي، حيث الأخطاء ليست أخطاء المبتدئين، بل جزء من النمو المهني. اليوم سنتناول تصحيح أخطاء كود Go بحيث لا تجد الأخطاء فحسب، بل تمنع ظهورها في مرحلة الكتابة.
\n\nGo هي لغة تمنح المطور أدوات قوية لتصحيح الأخطاء مباشرة "من الصندوق". لكن الكثيرين يستخدمون فقط fmt.Println ويتساءلون بصدق لماذا تتصرف goroutines الخاصة بهم مثل القطط على سطح ساخن. دعنا نصلح هذا مرة واحدة وإلى الأبد.
التقنية رقم 1: Print ذكي - عندما لا تحتاج إلى log.Fatal
\n\nنعم، أعرف ما ستقوله: "لماذا أحتاج delve بينما يمكنني طباعة المتغير؟" وستكون محقًا في 80% من الحالات. لكن المشكلة ليست في fmt.Println، بل في كيفية استخدامك له.
الانتقال من Println إلى التسجيل المنظم (structured logging)
\n\nبدلاً من لصق عشرات الأوامر الطباعية، استخدم الحزمة log أو slog (المتاحة منذ Go 1.21):
package main\n\nimport (\n "log/slog"\n "os"\n)\n\nfunc main() {\n logger := slog.New(slog.NewTextHandler(os.Stdout, nil))\n \n // بدلاً من fmt.Println("خطأ:", err)\n if err := doSomething(); err != nil {\n logger.Error("فشلت العملية", \n "error", err,\n "module", "data_processor",\n "attempt", 3,\n )\n }\n}\n\n\nهذا الأسلوب يمنحك سجلات منظمة يسهل تحليلها وتصفيتها وفحصها. في الإنتاج، سينقذك هذا من المكالمات الليلية.
\n\nحيلة: الترجمة الشرطية لسجلات التصحيح
\n\nلتجنب إرباك الإنتاج، استخدم build tags:
\n\n//go:build debug\n\npackage main\n\nvar debug = true\n\n\n//go:build !debug\n\npackage main\n\nvar debug = false\n\n\nتشغيل مع العلم -tags debug - تحصل على سجلات مفصلة. بدون العلم - إنتاج نظيف.
التقنية رقم 2: Delve - محققك الشخصي لـ Go
\n\nإذا كان تصحيح الأخطاء بالطباعة هو دراجة هوائية، فإن Delve هو Tesla. هذا المصحح مصمم خصيصًا لـ Go ويفهم goroutines والواجهات وحتى generics. التثبيت بسيط:
\n\ngo install github.com/go-delve/delve/cmd/dlv@latest\n\n\nنقاط توقف تفكر
\n\nيتيح لك Delve وضع ليس فقط breakpoint، بل نقاط شرطية:
\n\n(dlv) break main.go:25 if userID == 42\n(dlv) continue\n\n\nسيتوقف المصحح فقط عندما يكون userID يساوي 42. تخيل كم من الوقت يوفر هذا عند تصحيح أخطاء الحلقات.
استكشاف goroutines
\n\nأكثر ما يخيف في Go هو deadlock و race condition. يعرض Delve جميع goroutines النشطة:
\n\n(dlv) goroutines\n Goroutine 1 - User: ./main.go:10 main.main (0x109a5b6)\n Goroutine 2 - User: ./main.go:15 main.worker (0x109a6c8)\n* Goroutine 3 - User: ./main.go:22 main.worker (0x109a6c8)\n\n\nالنجمة تشير إلى goroutine الحالية. تنقل بينها:
\n\n(dlv) goroutine 2\n(dlv) stack\n\n\nالعمل مع الذاكرة: نرى ما بالداخل
\n\nالأمر locals يعرض جميع المتغيرات المحلية، و print يطبع قيمها مع الأنواع:
(dlv) locals\ndata = []int len: 100000, cap: 100000\nresult = map[string]int ["error": 5, "success": 99995]\n(dlv) print data[0:5]\n[]int len: 5, cap: 100000, [42, 17, 89, 3, 256]\n\n\nالتقنية رقم 3: التنميط باستخدام pprof - نجد نقاط الضعف
\n\nكودك يعمل لكنه بطيء؟ pprof هو ملف تعريف الأداء المدمج في Go. سيظهر أين "يتباطأ" البرنامج وأين يستهلك الذاكرة.
\n\n