مطالبات شرح الكود: من التعبير النمطي إلى أثر الخطأ
شرح الكود بالذكاء الاصطناعي لمن يقرؤه لا لمن كتبه: ست عشرة مطالبة جاهزة تحمل كل منها مقطعاً برمجياً أو نمطاً أو أمراً أو أثر خطأ، وتحدد لمن يُكتب الشرح وإلى أي عمق يمضي. ضع كودك مكان المثال واضغط «تطبيق».
فهم كود كتبه غيرك
شرح دالة برمجية بلا تعليق واحد، وقراءة كود سطراً سطراً بلغة لا تكتب بها، وفهم لماذا كُتب هذا الجزء هكذا بالذات بعدما غادرت أسبابه الشركة مع صاحبها الأول.
دالة بلا تعليقات
تحوّل الكود الموروث إلى جملة واحدة عما يحسبه فعلاً.
def recalc(items, rate): total = 0 for it in items: if it.status != "cancelled": total += it.price * it.qty if total > 10000: total = total * (1 - rate) return round(total, 2)# ورثتها عن المطور السابق بلا تعليق واحد — اشرح ماذا تحسب هذه الدالة وبأي شروط
الدالة تحسب إجمالي الطلب بعد الخصم.1. تضرب السعر في الكمية على كل البنود وتتجاوز الملغاة: أي بند قيمة status فيه "cancelled" لا يدخل الإجمالي أبداً.2. إذا تجاوز الإجمالي 10000 يُطبَّق عليه rate: القيمة 0.1 تعني خصماً بعشرة بالمئة.3. يُقرَّب الناتج إلى منزلتين عشريتين، أي إلى القروش.التفصيل الذي لا يقوله إلا الكود نفسه: الخصم يسري على الإجمالي كله لا على الجزء الزائد عن الحد. لذلك طلب بقيمة 10001 يصير بعد الخصم أرخص من طلب بقيمة 9999.
كود بلغة لا تكتب بها
يشرح تركيباً غريباً عنك عبر التركيب الذي تستعمله أصلاً في لغتك.
import functoolsdef retry(times): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): for attempt in range(times): try: return fn(*args, **kwargs) except TimeoutError: continue raise RuntimeError("all attempts failed") return wrapper return decorator# أنا أكتب بلغة PHP وأرى المزخرفات لأول مرة — اشرحها بمفاهيم أعرفها
هذا مزخرف يأخذ وسيطاً. أقرب ما تعرفه في PHP هو دالة تعيد إغلاقاً يلتف حول دالة أخرى.يُقرأ من الداخل إلى الخارج:- الاستدعاء retry(3) يعيد decorator؛- والدالة decorator تستقبل دالتك وتعيد wrapper؛- وسطر @retry(3) فوق الدالة يعني تماماً fn = retry(3)(fn) — ومنذ تلك اللحظة يشير الاسم إلى wrapper.أما wrapper فينادي الأصل ثلاث مرات على الأكثر ويبتلع TimeoutError بلا كلمة، فإذا نفدت المحاولات رمى RuntimeError. ووجود functools.wraps يحفظ للغلاف اسم الأصل وتوثيقه، ولولاه لأظهر المنقّح وكل أثر خطأ كلمة wrapper لا غير.
قراءة سطراً سطراً
يمشي في المقطع سطراً بعد سطر، حين لا يكون الغامض هو المجموع بل موضعاً بعينه.
def top_customers(orders, n): totals = dict() for order in orders: totals[order.customer_id] = totals.get(order.customer_id, 0) + order.total ranked = sorted(totals.items(), key=lambda pair: -pair[1]) return ranked[:n]# اشرحه سطراً سطراً، وخصوصاً سطر sorted وإشارة الناقص أمام pair
1. السطر totals = dict() ينشئ قاموساً فارغاً تُجمَع فيه المبالغ لكل عميل.2. الحلقة تمر على الطلبات. والاستدعاء totals.get(id, 0) يعيد المتراكم أو صفراً إذا ظهر العميل لأول مرة، ولهذا لا حاجة إلى فحص منفصل للطلب الأول.3. الدالة sorted فوق items() ترتب أزواج العميل والمبلغ. وإشارة الناقص أمام pair[1] تقلب الترتيب: تنازلياً بدل تصاعدي، فيصعد كبار العملاء إلى الأعلى.4. والقطع ranked[:n] يبقي أول n زوج فقط.إعادة الأزواج لا المعرّفات وحدها أمر مقصود: المستدعي يحتاج المبلغ لعرضه، فلا يضطر إلى جمعه مرة ثانية.
لماذا كُتب هكذا بالذات
يشرح لا ما يفعله الكود بل لماذا اختار كاتبه هذه الصيغة.
_cache = None_lock = threading.Lock()def get_config(): global _cache if _cache is None: with _lock: if _cache is None: _cache = load_config_from_disk() return _cache# لماذا كُتب فحص None مرتين، قبل القفل وداخله
هذا نمط double-checked locking، ولكل فحص من الاثنين سبب مختلف.الأول هو الطريق السريع. فبعد تحميل الإعدادات مرة واحدة ترى كل الاستدعاءات التالية القيمة جاهزة وتعود فوراً دون أن تمس القفل. وأخذ القفل يكلف وقتاً في كل استدعاء بينما الحاجة إليه تقع مرة واحدة في عمر العملية كلها.والثاني يخص السباق. فبينما يمسك الخيط الأول القفل ويقرأ الملف يكون خيط ثانٍ قد تجاوز الفحص الخارجي وانتظر خلفه. ولولا الفحص الداخلي لحمّل الإعدادات مرة ثانية وطمس نتيجة غيره.أما سطر global فيجعل الإسناد يغيّر متغير الوحدة بدل أن ينشئ متغيراً محلياً جديداً.
الأخطاء وأثر التنفيذ
النص الأحمر الذي يجب أن يُقرأ قبل أن يُصلَح: شرح رسالة خطأ عند انهيار التطبيق، وتثبيت حزمة لا يكتمل وينتهي بصفحة سجل، وفهم سطر من سجل الإنتاج لا يقول شيئًا بوضوح.
انهيار التطبيق
يقرأ أثر التنفيذ: أين انقطع، وأي سطر هو المتهم، وماذا يعني نص الخطأ نفسه.
Traceback (most recent call last): File "app/orders.py", line 42, in create_order total = calc_total(items) File "app/pricing.py", line 17, in calc_total return sum(item.price * item.qty for item in items)AttributeError: 'dict' object has no attribute 'price'ماذا حدث هنا وفي أي سطر أبحث عن السبب
الخطأ AttributeError يعني أن الكائن لا يملك خاصية باسم price.يُقرأ الأثر من أعلى إلى أسفل لتتبع مسار الاستدعاء ومن أسفل إلى أعلى لمعرفة السبب:- الدالة create_order في الملف orders.py عند السطر 42 نادت calc_total؛- والدالة calc_total في الملف pricing.py عند السطر 17 طلبت item.price فسقطت.السطر 17 هو موضع السقوط لكن المشكلة ليست فيه. فالرسالة تقول إن داخل items قواميس لا كائنات، والقاموس لا يقبل الوصول بالنقطة. أي أن المستدعي مرّر البيانات كما جاءت من JSON دون أن يبني منها نموذجاً. فالإصلاح يكون حيث تُركَّب items، أو بقراءة الحقل هكذا: item["price"].
التثبيت لا يكتمل
ينقل سجل تثبيت الاعتماديات من لغة مدير الحزم إلى لغة البشر.
ERROR: Could not find a version that satisfies the requirement pandas==2.9.1 (from versions: 2.0.0, 2.1.4, 2.2.2)ERROR: No matching distribution found for pandas==2.9.1لا أستطيع تثبيت الاعتماديات، ماذا تعني هذه الرسالة
هذه النسخة من المكتبة غير موجودة أصلاً.ذهب pip إلى فهرس الحزم فوجد pandas وعدّد النسخ المتاحة، وأحدثها 2.2.2. وأنت تطلب 2.9.1 ولا ينشرها أحد، ومن هنا جاءت عبارة «no matching distribution».وفي الغالب يكون السبب واحداً من ثلاثة: خطأ مطبعي في رقم النسخة، أو ملف requirements.txt منقول من مشروع أحدث، أو نسخة لا تصدر إلا لإصدار آخر من Python. والسطر الثاني ليس مشكلة مستقلة بل نتيجة الأول.
سطر من سجل الإنتاج
يشرح ماذا يبلّغ الخادم حقاً حين يظهر في السجل انتهاء مهلة وثلاثة أرقام.
2026-09-12T03:14:07Z ERROR db pool: timeout acquiring connection after 30s, active=20 idle=0 waiting=1372026-09-12T03:14:07Z ERROR api POST /orders 503 in 30012ms request_id=8c1f2aماذا يحاول الخادم أن يقول لي في هذين السطرين
نفدت الاتصالات بقاعدة البيانات.السطر الأول: انتظر المجمّع اتصالاً حراً ثلاثين ثانية ثم استسلم. فقيمة active=20 تعني أن العشرين كلها مشغولة، وقيمة idle=0 تعني أنه لم يبق حر واحد، وقيمة waiting=137 تعني أن مئة وسبعة وثلاثين طلباً تقف في الصف خلفها.والسطر الثاني نتيجة للأول: بقي الطلب POST /orders معلقاً الثلاثين ثانية نفسها ثم أعاد 503.والقراءة مجتمعةً: الطلبات تصل أسرع مما تحرر قاعدة البيانات اتصالاتها. المجمّع ليس معطلاً، بل يبلّغ بصدق أنه بلغ سقفه. والخطوة التالية هي النظر في الاستعلامات الطويلة التي تحتجز اتصالاً، وفي حجم المجمّع نسبةً إلى عدد العمال.
التعبيرات النمطية والأوامر
أسطر تعمل ما دام أحد لم يلمسها: شرح تعبير نمطي regex بالعربية، وتفكيك تعبير مأخوذ من ملف الإعدادات، وسلسلة أوامر طرفية منقولة من دليل غيرك قبل تشغيلها على الخادم.
ماذا يفحص التعبير النمطي
يفكك النمط إلى أجزائه ويقول أي العناوين يمر وأيها يُرفض بلا وجه حق.
^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z][A-Za-z]+$هذا موجود في التحقق من نموذجنا — ماذا يفحص بالضبط وماذا يمرر مما لا ينبغي
النمط يفحص شكل العنوان لا وجوده.- الرمزان ^ و $ يشترطان تطابق السلسلة كاملةً لا جزء منها.- الجزء السابق لعلامة @: حروف وأرقام ونقطة وشرطة سفلية وعلامة نسبة وزائد وشرطة، بمقدار محرف واحد على الأقل.- العلامة @ مرة واحدة تماماً.- النطاق: حروف وأرقام ونقاط وشرطات.- وفي النهاية نقطة وامتداد من حرفين فأكثر.ما يمر بلا داعٍ: نقطتان متتاليتان، ونقطة في أول الاسم، ونطاق لا وجود له. وما يُرفض ظلماً: العناوين المدوّلة والامتدادات بغير الحروف اللاتينية. وهذا يكفي عادةً في نموذج تسجيل، لأن وجود العنوان تؤكده رسالة التفعيل على أي حال.
تعبير مأخوذ من ملف الإعدادات
يبيّن أي أجزاء سطر السجل تقع في أي مجموعة وبأي ترتيب.
^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) - - \[([^\]]+)\] "([A-Z]+) ([^ ]+) [^"]+" ([0-9]+) ([0-9]+)$هذا يفكك سطراً من ملف access.log — ماذا يقع في كل مجموعة
التعبير يفكك سطر سجل خادم الويب إلى ست مجموعات.1. عنوان IP: أربعة أعداد تفصلها نقاط. والنقطة المسبوقة بشرطة مائلة تعني نقطة حقيقية لا «أي محرف».2. الوقت بين قوسين معقوفين: يُسمح داخلهما بأي محرف عدا قوس الإغلاق، وهذا بالذات ما يمنع المجموعة من التهام بقية السطر.3. طريقة الطلب: حروف كبيرة.4. المسار: كل شيء حتى أول مسافة.5. رمز الاستجابة.6. حجم الاستجابة بالبايت.ترتيب المجموعات هو ترتيب الحقول في الناتج: الأولى هي العنوان والخامسة هي الحالة. وأي سطر يحوي مساره مسافة لن يطابق النمط كاملاً وسيُتخطى ببساطة.
أمر منقول من دليل غيرك
يفكك سلسلة الطرفية قبل أن تضغط مفتاح الإدخال على خادم إنتاج.
tar -czf - ./data | ssh backup@nas "cat > /backups/data-$(date +%F).tar.gz"# وجدته في دليل النسخ الاحتياطي عندنا — اشرح ماذا سيحدث قبل أن أنفذه
الأمر ينسخ مجلد data احتياطياً إلى جهاز آخر دون أن يحفظ شيئاً على هذا الجهاز.- الأمر tar -czf - يبني الأرشيف ويضغطه لكنه يكتبه إلى المخرج القياسي بدل ملف: هذا معنى الشرطة المفردة.- والخط العمودي يسلّم ذلك التدفق إلى الأمر التالي.- والأمر ssh يتصل بالجهاز nas بالمستخدم backup ويشغّل هناك cat الذي يضع التدفق في ملف.- والتعبير $(date +%F) يضع تاريخ اليوم في اسم الملف فينتج data-2026-09-13.tar.gz.الأرشيف لا يهبط كاملاً في أي مكان: هو يسيل عبر الشبكة أثناء تكوينه، فلا حاجة إلى مساحة حرة على قرص الجهاز المصدر. وهناك حافة خطرة واحدة: إن كان ملف بهذا الاسم موجوداً فسيُكتب فوقه بلا كلمة تحذير.
SQL واستعلامات قاعدة البيانات
شرح استعلام SQL طويل يملأ نصف الشاشة كاملة، وتفكيك دالة نافذة خطوة بخطوة، ومعرفة أين يتعثر استعلام التقرير ولماذا يتصرف في الإنتاج على غير ما يفعل عندك.
استعلام تقرير
يحوّل استعلام SELECT الطويل إلى جملة واحدة عن الصفوف التي ستظهر في التقرير.
SELECT c.name, date_trunc('month', o.created_at) AS month, count(*) AS orders, sum(o.total) AS revenueFROM orders oJOIN customers c ON c.id = o.customer_idWHERE o.created_at >= now() - interval '1 year'GROUP BY 1, 2HAVING sum(o.total) > 1000ORDER BY revenue DESC-- اشرح ماذا يعيد هذا الاستعلام ولماذا لا يظهر جزء من العملاء في التقرير
الاستعلام يحسب الإيراد لكل عميل موزعاً على الأشهر خلال السنة الأخيرة.- الربط JOIN يصل الطلب بالعميل. وهو ربط عادي لا LEFT، ولهذا لا يظهر العملاء الذين لم يطلبوا قط.- والشرط WHERE يقطع كل ما هو أقدم من سنة من اللحظة الحالية.- والدالة date_trunc تقصّ التاريخ إلى أول الشهر، وبهذا تتجمع الطلبات المفردة في مجموعات شهرية.- والجملة GROUP BY 1, 2 تجمّع حسب أول تعبيرين في القائمة: اسم العميل والشهر.- والشرط HAVING يستبعد المجموعات دون 1000. وخلافاً لـ WHERE يُطبَّق هذا المرشّح بعد حساب المجموع.الصف الواحد في الناتج هو عميل في شهر واحد لا عميل بكامل تاريخه.
دالة النافذة
يشرح ماذا تحسب OVER وفيم تختلف النافذة عن التجميع المعتاد.
SELECT customer_id, created_at, total, sum(total) OVER (PARTITION BY customer_id ORDER BY created_at) AS running_total, row_number() OVER (PARTITION BY customer_id ORDER BY total DESC) AS rnFROM orders-- اشرح ماذا تحسب هاتان الـ OVER ولماذا لا تنطوي الصفوف
دالة النافذة تحسب على مجموعة دون أن تطوي الصفوف: يخرج من الاستعلام عدد الصفوف نفسه الذي دخله، وكل صف يكسب عمودين جديدين فقط.- الجملة PARTITION BY customer_id تقسم الجدول إلى أكوام حسب العميل، والكومة هي النافذة.- في العمود الأول يُحسب المجموع تراكمياً: فـ ORDER BY created_at يحدد الترتيب داخل النافذة، وافتراضياً تُحسب الصفوف من بداية النافذة حتى الصف الحالي. فعند الطلب الثالث للعميل يكون المجموع هو مجموع الطلبات الثلاثة الأولى.- وفي OVER الثانية ترتيب مختلف، تنازلياً حسب المبلغ، وتوزّع row_number المراتب: الرقم 1 لأكبر طلبات ذلك العميل.وهذا بالضبط ما يفرق النافذة عن GROUP BY: فالتجميع كان سيترك صفاً واحداً لكل عميل ويضيّع الطلبات المفردة.
أين يتعثر الاستعلام
يشرح الاستعلام ويشير في الوقت نفسه إلى ما سيصطدم به على بيانات حقيقية.
SELECT *FROM orders oLEFT JOIN order_items i ON i.order_id = o.idWHERE DATE(o.created_at) = '2026-09-01' AND o.status != 'cancelled'ORDER BY o.created_at DESC-- على قاعدة الاختبار فوري وفي الإنتاج يعلّق — اشرح الاستعلام وما الذي يصطدم به هنا
الاستعلام يجلب طلبات الأول من سبتمبر مع كل بنودها. و LEFT JOIN يعني أن الطلب الخالي من البنود يخرج هو أيضاً، بأعمدة order_items فارغة.أين يتعثر على بيانات حقيقية:- التعبير DATE(o.created_at) دالة موضوعة فوق العمود، وبسببها يصير الفهرس على created_at غير قابل للاستعمال: على قاعدة البيانات أن تحسب تاريخاً لكل صف في الجدول. لا يظهر ذلك على مئة صف اختبارية، أما على الملايين فهو مسح كامل.- والاستعلام SELECT * فوق علاقة واحد إلى متعدد يعيد كل طلب بعدد بنوده. فتخرج صفوف أكثر بكثير من عدد الطلبات، ويقع على التطبيق أن يطويها بنفسه.- والجدولان يحملان أعمدة بالأسماء نفسها (id و created_at)، فتختلط في الناتج ولا يعود الاسم يدل على الجدول الذي جاء منه الحقل.
الشرح للمبتدئ وفي مقابلة العمل
حين يكون الشرح أهم من الكود: شرح أول برنامج لطالب مبتدئ تمامًا، وتفكيك سؤال كود من مقابلة عمل قبل الجلسة، وشرح نص برمجي للمدير بكلمات غير تقنية في دقيقتين.
أول برنامج
شرح لمن يكتب الكود منذ أسبوعين: ماذا يفعل كل سطر ولماذا كُتب هكذا.
nums = [4, 8, 15, 16, 23, 42]total = 0for n in nums: if n % 2 == 0: total += nprint(total / len(nums))# بدأت تعلّم Python للتو — اشرح خطوة خطوة ماذا يجري هنا
البرنامج يجمع الأعداد الزوجية في القائمة ثم يقسم الناتج على عدد الأعداد كلها.1. القائمة nums فيها ستة أعداد.2. والسطر total = 0 هو الصندوق الذي سنجمع فيه، والصفر موجود ليكون هناك ما نبدأ منه.3. والسطر for n in nums هو الحلقة: يصير n بالتناوب كل عدد من القائمة.4. والشرط n % 2 == 0 يعني أن باقي القسمة على اثنين صفر، أي أن العدد زوجي.5. والسطر total += n يضيف ما وُجد إلى ما تراكم.6. والقسمة على len(nums) هي قسمة على طول القائمة، أي على ستة.ويبدو أن هنا خطأً في الفكرة نفسها: في القائمة أربعة أعداد زوجية مجموعها 70، بينما القسمة تجري على ستة. فإن كان المطلوب متوسط الأعداد الزوجية وجب القسمة على عددها هي.
سؤال من مقابلة عمل
تفكيك الخوارزمية على نحو يمكّنك من شرحها بصوت عالٍ أمام لوح.
def search(nums, target): lo, hi = 0, len(nums) - 1 while hi >= lo: mid = (lo + hi) // 2 if nums[mid] == target: return mid if nums[mid] > target: hi = mid - 1 else: lo = mid + 1 return -1# سُئلت عنها في مقابلة عمل — اشرح فكرة الخوارزمية ولماذا هي سريعة
هذا بحث ثنائي في مصفوفة مرتبة، وفكرته واحدة: في كل خطوة يُطرح نصف ما تبقى.- المتغيران lo و hi هما حدا المقطع الذي قد يكون الجواب فيه.- والمتغير mid هو المنتصف، والشرطتان المائلتان قسمة صحيحة، ولهذا يخرج دليل لا كسر.- فإن كان المطلوب في المنتصف انتهى الأمر.- وإن كان المنتصف أكبر فالجواب إلى اليسار ويتحرك hi.- وإلا فالجواب إلى اليمين ويتحرك lo.- وتنتهي الحلقة حين يتقاطع الحدان: القيمة غير موجودة فيعاد -1.الكلفة لوغاريتمية: مليون عنصر تعني نحو عشرين خطوة بدل مليون. والشرط الذي لا بديل عنه أن تكون المصفوفة مرتبة سلفاً، وإلا أعادت الخوارزمية جواباً خاطئاً في صمت.والسؤال المتفرع المعتاد في المقابلات: في اللغات ذات الأعداد الصحيحة محدودة الحجم قد يفيض المجموع lo + hi، ولهذا يُكتب هناك lo + (hi - lo) // 2.
شرحه للمدير
إعادة سرد النص البرمجي بلا مصطلح واحد، لحديث مع من لا يقرأ الكود.
import csv, smtplibrows = list(csv.DictReader(open("orders.csv")))late = [r for r in rows if r["shipped_at"] == ""]body = "طلبات لم تُشحن بعد: " + str(len(late))smtplib.SMTP("mail.local").sendmail("bot@shop.local", "ops@shop.local", body)# أحتاج أن أشرح لمدير غير تقني ماذا يفعل هذا النص البرمجي
النص البرمجي يتحقق من الطلبات التي لم تُشحن بعد ويبلّغ عددها برسالة بريد.خطوة خطوة وبلا مصطلحات:1. يفتح جدول الطلبات، وهو ملف عادي يفتحه برنامج Excel أيضاً.2. ويبقي الصفوف التي يكون فيها تاريخ الشحن فارغاً، أي طلب سُجّل ولم يخرج.3. ويعد كم صفاً من هذا النوع هناك.4. ويرسل رسالة إلى عنوان القسم فيها هذا الرقم وحده.وما ينبغي أن يعرفه المدير: النص لا يغيّر شيئاً ولا يعطّل أحداً، هو يقرأ ويبلّغ فقط، فيمكن تشغيله كل ساعة إن شئت. ونقطة ضعفه واحدة: يقرأ من ملف، أي أنه يعرض الصورة عند آخر تصدير لا صورة هذه اللحظة.