يا جماعة الخير، السلام عليكم ورحمة الله. اسمحولي أحكيلكم قصة صارت معي ومع فريقي قبل كم سنة، قصة علّمتنا درس ما بننساه للموت.
كنا شغالين على نظام إدارة محتوى (CMS) لعميل مهم جداً، وكان باقي على موعد الإطلاق أقل من أسبوع. سهرانين ليل نهار، والقهوة صارت صديقتنا الصدوقة، والعيون حمرا من كثر التحديق في الشاشات. في ليلة من الليالي، واحنا بنعمل آخر الاختبارات، فجأة… النظام كله “ضرب”. الشاشة البيضاء المشؤومة، ورسائل خطأ غريبة بتطلع من كل مكان. يا ساتر!
صار كل واحد فينا يضرب أخماس بأسداس. “شو اللي صار؟”، “الكود كان شغال قبل شوي!”، “يمكن من السيرفر؟”. قضينا ساعات طويلة، وساعات مرعبة بمعنى الكلمة، واحنا بننبش في الكود سطر سطر. العميل على التلفون كل نص ساعة، والضغط النفسي وصل مراحل ما يعلم فيها إلا ربنا.
بعد بحث مضنٍ، اكتشفنا المصيبة. المشكلة كانت في دالة بسيطة جداً، دالة مسؤولة عن عرض اسم المستخدم في لوحة التحكم. أحد المختبرين، يمكن بمزح أو عن طريق الخطأ، أدخل في حقل “الاسم الأول” رمز تعبيري (emoji) 👩💻 مع شوية رموز غريبة. الكود تبعنا، بكل سذاجة وبراءة، أخذ هذا المدخل “زي ما هو” وحاول يمرره لدالة ثانية بتتعامل مع النصوص بطريقة معينة، وهاي الدالة ما كانت مستعدة لهيك نوع من المدخلات… فانهارت. وبما أنه هذا الاسم كان لازم ينعرض في كل مكان، انهار النظام كله وراها.
وقتها، وقفت صافن وحكيت جملة للفريق بعدها صارت شعارنا: “يا جماعة، كودنا أهبل! بيصدق كل شي بينعطى إله. لازم نعلّمه يشك ويدافع عن حاله”. ومن هنا، بدأت رحلتنا الحقيقية مع ما يسمى بـ “البرمجة الدفاعية”.
ما هي البرمجة الدفاعية (Defensive Programming)؟
ببساطة يا خال، البرمجة الدفاعية هي عقلية وأسلوب في كتابة الكود. هي أنك تبرمج وأنت حاطط في بالك أسوأ الاحتمالات. زي سواق السيارة المحترف، هو ما بيفترض إنه كل اللي حواليه سواقين محترفين زيه، بالعكس، هو بيتوقع إنه ممكن أي حدا يكسر عليه، أو يضرب بريك فجأة، أو ما يعطي إشارة. هو دايماً مستعد للخطأ من الآخرين.
في البرمجة، “الآخرون” هم: المستخدمين، واجهات برمجة التطبيقات (APIs) الخارجية، قواعد البيانات، أي مصدر بيانات خارج عن سيطرة الكود تبعك المباشرة. البرمجة الدفاعية تعني أنك تبني “حصوناً” و”دفاعات” داخل الكود تبعك لحمايته من هذه المدخلات غير المتوقعة أو الخبيثة.
فكر فيها كأنك بتبني قلعة. ما بتكتفي ببناء جدران قوية وبس، لأ، بتحفر خندق، وبتحط أبراج مراقبة، وحراس على البوابات. الحراس هدول هم الكود الدفاعي تبعك، اللي بفحصوا كل حدا بحاول يدخل القلعة.
ليش البرمجة الدفاعية مهمة لهالدرجة؟
ممكن حدا يحكي: “طيب يا أبو عمر، ليش هالوجع الراس كله؟ ما أنا بكتب كود شغال وزي الفل”. والجواب بسيط: الكود اللي “شغال” في الظروف المثالية هو قنبلة موقوتة في العالم الحقيقي. أهمية البرمجة الدفاعية بتتلخص في نقاط جوهرية:
- تقليل الأخطاء (Bugs): معظم الأخطاء الكارثية تأتي من حالات لم يتوقعها المبرمج. البرمجة الدفاعية تجبرك على التفكير في هذه الحالات ومعالجتها مسبقاً.
- زيادة صلابة النظام (Robustness): بدلاً من أن ينهار تطبيقك بالكامل عند حدوث خطأ غير متوقع، يمكنه التعامل مع الخطأ برشاقة، ربما بعرض رسالة لطيفة للمستخدم وتسجيل الخطأ للمطورين، ويكمل شغله.
- تحسين الأمان (Security): كثير من الثغرات الأمنية المشهورة مثل حقن SQL (SQL Injection) أو Cross-Site Scripting (XSS) هي نتيجة مباشرة للثقة العمياء في مدخلات المستخدم. التحقق من المدخلات هو خط الدفاع الأول ضد هذه الهجمات.
- توفير الوقت والجهد على المدى الطويل: قضاء ساعة إضافية في كتابة كود دفاعي اليوم، يوفر عليك أياماً من تصحيح الأخطاء في منتصف الليل وضغط العملاء في المستقبل. صدقني، وقت المبرمج أغلى من الذهب.
مبادئ أساسية في البرمجة الدفاعية (مع أمثلة)
طيب، كيف نطبق هالحكي على أرض الواقع؟ الموضوع مش تعقيد، هو مجموعة مبادئ وعادات بتتبناها وأنت بتكتب الكود.
1. لا تثق بأي مدخلات خارجية أبداً (The Golden Rule)
هذه هي القاعدة الذهبية. أي شيء يأتي من خارج نطاق دالتك الحالية هو “مشتبه به” حتى يثبت العكس. هذا يشمل:
- مدخلات المستخدم من النماذج (Forms).
- البيانات القادمة من استدعاءات API.
- البيانات التي تقرأها من قاعدة البيانات (نعم، حتى قاعدة البيانات!).
- البيانات من ملفات أو أي مصدر خارجي.
مثال: دالة حساب الخصم
لنفترض أن لدينا دالة بسيطة تحسب السعر بعد الخصم.
الكود الساذج (يثق بالمدخلات):
// JavaScript
function calculateDiscount(price, discountPercentage) {
// ماذا لو كانت discountPercentage نصاً أو قيمة سالبة؟
const discountAmount = price * (discountPercentage / 100);
return price - discountAmount;
}
// calculateDiscount(100, "خمسين"); // -> NaN (Not a Number) - مشكلة!
// calculateDiscount(100, 150); // -> -50 - مشكلة أكبر!
الكود الدفاعي:
// JavaScript
function calculateDiscount(price, discountPercentage) {
// 1. التحقق من الأنواع
if (typeof price !== 'number' || typeof discountPercentage !== 'number') {
throw new Error("المدخلات يجب أن تكون أرقاماً.");
}
// 2. التحقق من النطاق المنطقي
if (price <= 0 || discountPercentage 100) {
throw new Error("المدخلات خارج النطاق المنطقي.");
}
const discountAmount = price * (discountPercentage / 100);
return price - discountAmount;
}
try {
const finalPrice = calculateDiscount(100, 150);
console.log(finalPrice);
} catch (error) {
console.error("حدث خطأ: " + error.message); // تعامل مع الخطأ برشاقة
}
لاحظ كيف أصبح الكود الآن صلباً ومستعداً للتعامل مع المدخلات الخاطئة بدلاً من إرجاع نتائج كارثية بصمت.
2. تحقق من كل شيء وفشل مبكراً (Validate Everything & Fail Fast)
مبدأ “الفشل المبكر” يعني أنه من الأفضل أن يتوقف الكود وينهار فور اكتشاف مشكلة، بدلاً من أن يكمل عمله ببيانات فاسدة قد تسبب مشاكل أكبر وأكثر غموضاً في مكان آخر من النظام. هذا يجعل تتبع الأخطاء أسهل بكثير.
مثال: عرض بيانات المستخدم
الكود غير الدفاعي:
# Python
def display_user_greeting(user):
# ماذا لو كان الـ user هو None؟ سيحدث انهيار هنا
print(f"مرحباً, {user['name']}!")
# display_user_greeting(None) # -> TypeError: 'NoneType' is not subscriptable
الكود الدفاعي (Fail Fast):
# Python
def display_user_greeting(user):
# افشل فوراً إذا كان الشرط الأساسي غير متحقق
if not user or 'name' not in user:
print("لا يمكن عرض الترحيب: بيانات المستخدم غير مكتملة.")
return # أو ارمِ استثناءً (raise an exception)
print(f"مرحباً, {user['name']}!")
display_user_greeting(None) # يطبع رسالة واضحة ولا ينهار
display_user_greeting({'email': 'test@test.com'}) # يطبع رسالة واضحة ولا ينهار
3. تعامل مع الأخطاء بذكاء (Graceful Error Handling)
البرمجة الدفاعية لا تعني فقط رمي الأخطاء (throwing errors)، بل أيضاً التقاطها والتعامل معها بطريقة ذكية. أسوأ شيء يمكن أن تفعله هو “ابتلاع” الخطأ وكأنه لم يحدث.
تجنب تماماً هذا الكود الكارثي، اللي بنسميه “حط الوسخ تحت السجادة”:
// JavaScript
try {
// ... كود قد يسبب خطأ ...
} catch (error) {
// لا تفعل شيئاً! هذه جريمة برمجية.
}
بدلاً من ذلك، عند التقاط خطأ:
- سجّل الخطأ (Log the error): سجله في ملف أو خدمة تتبع أخطاء. هذا لك أنت كمطور لتصحيحه لاحقاً.
- أبلغ المستخدم (Inform the user): اعرض رسالة واضحة وسهلة الفهم للمستخدم (وليس رسالة الخطأ التقنية!).
- استعد الحالة (Recover if possible): هل يمكنك إعادة المحاولة؟ هل يمكنك استخدام قيمة افتراضية؟
نصائح من مطبخ أبو عمر البرمجي
عبر السنين، ومع كثرة الوقعات اللي وقعناها، تجمعت عندي شوية نصائح عملية بحب أشاركها معكم:
- قاعدة “الثلاثي المشبوه”: دائماً، ودائماً، ودائماً كن متشككاً من ثلاثة أشياء: مدخلات المستخدم، استجابات الـ API، والبيانات من قاعدة البيانات. لا تفترض أبداً أنها ستكون بالشكل أو النوع الذي تتوقعه.
- استخدم التأكيدات (Assertions): في لغات كثيرة، يوجد مفهوم الـ `assert`. هذا الأمر يشبه حارساً داخلياً يتأكد من صحة الافتراضات داخل الكود الخاص بك أثناء مرحلة التطوير. إذا كان الشرط خطأ، ينهار البرنامج فوراً. هذا ممتاز لاكتشاف الأخطاء المنطقية مبكراً.
- “الكود اللي ما بتفهمه، ما بتأمنله”: عند استخدام مكتبة برمجية خارجية، اقرأ توثيقها جيداً، خاصة الجزء المتعلق بمعالجة الأخطاء. لا تنسخ وتلصق حلولاً من الإنترنت بدون فهم كامل لما تفعله.
- اجعل القيم الافتراضية صديقك: عند تعريف الدوال أو التعامل مع الإعدادات، فإن توفير قيم افتراضية منطقية يمكن أن يمنع عدداً كبيراً من الأخطاء المتعلقة بالقيم الفارغة (`null` أو `undefined`).
الخلاصة: برمج كأن الجميع يحاول كسر كودك 🛡️
في النهاية، البرمجة الدفاعية ليست تشاؤماً أو وسوسة، بل هي علامة على الاحترافية والنضج البرمجي. هي التحول من عقلية “يا رب الكود يشتغل” إلى عقلية “أنا أعرف أن كودي سيصمد”. إنها الممارسة التي تميز بين من يكتب سطوراً برمجية، وبين من يبني أنظمة قوية وموثوقة.
لذلك في المرة القادمة التي تكتب فيها دالة، اسأل نفسك: “ما هي أغبى وأسوأ طريقة يمكن أن يستخدم بها أحدهم هذه الدالة؟”. ثم اكتب كودك ليصمد أمام هذه الطريقة. وقتها فقط، يمكنك أن تنام قرير العين، وأنت تعلم أن “قلعتك” البرمجية محمية جيداً.
بالتوفيق يا أبطال البرمجة! 💪