C++ / المتوسط / الدرس 12
البناء والاختبار والتصحيح المنهجي
أنشئ بناء C++20 قابلًا للتكرار، واختبر العقود الملحوظة، واستخدم التشخيص لفهم الفشل دون اعتباره برهان صحة.
في هذه الصفحة
أهداف الدرس
- فصل الترويسات وملفات التنفيذ والأهداف المرتبطة.
- كتابة اختبارات للحدود تُبلغ عن الفشل بموثوقية.
- استخدام التحذيرات والمطهّرات وتصحيح المكررات والمنقّح في مواضعها.
بناء البرنامج المقصود اختباره
يشكل كل ملف مصدري وترويساته وحدة ترجمة. تصرّح الترويسات عادة بالدوال العادية وتعرّف القوالب والمكوّنات inline، وتوفر المصادر التعريفات العادية. تمنع حواجز التضمين تكرار الإدراج داخل وحدة واحدة ولا تصلح التعريفات المتكررة غير inline عبر الملفات. يحتاج الرابط كل كائن أو مكتبة مطلوبة. أعد بناء الهدف بعد التعديل وسجل إصدار المترجم ووضع المعيار ومسارات التضمين واعتمادات الربط. افصل مخرجات البناء عن المصدر لتسهل معرفة الآثار القديمة وتعديلات الملفات المولدة غير المقصودة.
اختبار حد دون تكرار التنفيذ
يفحص الاختبار المفيد سلوكًا مصرحًا به عبر المدخلات والنتائج. تستخدم دالة عدد الدفعات خارج القسمة وبقيتها بدل count + width - 1، فتتجنب جمعًا غير لازم معرّضًا للفيض. يقبل عقدها عددًا غير سالب وعرضًا موجبًا. يستخدم البرنامج الأول مقارنات صريحة وحالات إرجاع لتبقى الفحوص فعّالة إذا عطّل NDEBUG الدالة assert. يفحص العمل الفارغ والقسمة التامة والبقية والرفض. تستطيع حزمة فعلية تسمية الحالات منفردة، ويبقي الملف الصغير المثال مكتملًا.
#include <iostream>
#include <stdexcept>
int batches(int count, int width) {
if (count < 0 || width <= 0) { throw std::invalid_argument("invalid batch input"); }
return count / width + (count % width != 0);
}
int main() {
if (batches(0, 4) != 0 || batches(8, 4) != 2 || batches(9, 4) != 3) { return 1; }
bool rejected{false};
try { (void)batches(2, 0); }
catch (const std::invalid_argument&) { rejected = true; }
if (!rejected) { return 1; }
std::cout << "4 cases passed\n";
}
4 cases passed
تُفحص ثلاث نتائج وحالة رفض. تعيد أي مخالفة حالة فشل للعملية قبل رسالة النجاح.
إعادة الإنتاج والاختزال والفحص
يبدأ التصحيح بحالة فشل قابلة للإعادة ونتيجة متوقعة. صغّر المدخلات واعزل أصغر دالة تبقي الفشل. ابنِ بمعلومات تصحيح وتحسين محدود أثناء تعلم تتبع المتغيّرات، ثم أكد الإصلاح بالبناء المعتاد أيضًا. يفحص المثال الثاني العضوية عند موضع إدراج؛ وشرطه المهم المساواة بعد lower_bound. الاكتفاء باختلاف المكرر عن end سيعلن وجود ستة خطأ. يساعد المنقّح على فحصه، ويحفظ اختبار الحد الاكتشاف للتغييرات المقبلة.
#include <algorithm>
#include <iostream>
#include <vector>
bool contains(const std::vector<int>& sorted, int target) {
const auto position = std::lower_bound(sorted.begin(), sorted.end(), target);
return position != sorted.end() && *position == target;
}
int main() {
const std::vector<int> values{2, 5, 8};
if (contains(values, 6) || !contains(values, 5) || contains({}, 1)) { return 1; }
std::cout << "3 searches passed\n";
}
3 searches passed
تغطي قيمة داخلية مفقودة وقيمة موجودة ونطاق فارغ فروعًا مختلفة دون فك مرجع end. يقدّم المستدعي إدخالًا مرتبًا.
استخدام كل أداة ضمن نطاقها الفعلي
مع GCC على المنصات المدعومة يمكن تفعيل AddressSanitizer وUndefinedBehaviorSanitizer عبر -fsanitize=address,undefined في بناء تصحيح منفصل. تكشفان أخطاء ذاكرة وحساب كثيرة نُفّذت، لا كل عيب ممكن. تحتاج ThreadSanitizer إلى إعداد منفصل ولا تُجمع مع AddressSanitizer. يضيف وضع libstdc++ للتصحيح فحوص مكررات عبر -D_GLIBCXX_DEBUG؛ وحافظ على اتساق استخدام الحاويات المؤثر في التوافق الثنائي بين المكونات المرتبطة. تكشف تحذيرات -Wall -Wextra -Wpedantic شيفرة مريبة مبكرًا. لا تثبت الأدوات أمان المسارات غير المنفّذة أو مطابقة سياسة التطبيق.
التصريح بالمتطلبات على الهدف
ينبغي أن يربط نظام البناء متطلبات اللغة والاعتمادات بالهدف المحتاج إليها. يستطيع CMake طلب cxx_std_20 عبر target_compile_features، وتمنع CXX_EXTENSIONS OFF الاعتماد على امتدادات المترجم لذلك الهدف. يعتمد مقطع البناء متعدد الملفات على مشروع موجود، ولذلك لا يُوسم مثالًا مستقلًا قابلًا للتشغيل. شغّل الاختبارات من مجلد بناء نظيف مرة قبل الثقة بالإصلاح. اجمع التشخيص والمدخل الفاشل والإخراج المتوقع ليعيد شخص آخر المشكلة دون تخمين إعدادات محررك.
الأخطاء الشائعة وإصلاحها
اختبارات تعلن النجاح بعد تعطيل التأكيدات.
- لماذا يحدث؟
- اعتمدت الفحوص كلها على assert مع NDEBUG.
- كيف تصلحه؟
- استخدم إطار اختبارات أو فحوص فشل صريحة.
- كيف تتجنب تكراره؟
- تحقق من سلوك الاختبارات في إعداد البناء الفعلي.
ظهور رمز مكرر أثناء الربط.
- لماذا يحدث؟
- وُضع تعريف دالة غير inline في ترويسة تستخدمها وحدات متعددة.
- كيف تصلحه؟
- انقل التعريف إلى مصدر واحد أو استخدم تصميم inline صالحًا.
- كيف تتجنب تكراره؟
- اختبر الترويسات في بناء متعدد الوحدات.
اعتبار تشغيل بأدوات الفحص برهانًا.
- لماذا يحدث؟
- لم تُفحص إلا المسارات المنفّذة.
- كيف تصلحه؟
- أضف حالات حدود وراجع الملكية والشروط السابقة.
- كيف تتجنب تكراره؟
- اجمع الاختبارات والتحليل وفهم العقود.
اختبر فهمك
1. أضف حالة لمنع عودة الخطأ في batches(1, 4)، واشرح لماذا لا تكشف اختبارات المضاعفات التامة تنفيذًا بالقسمة المقربة لأسفل.
اعرض الحل المشروح
النتيجة المتوقعة واحد لأن عنصرًا واحدًا يحتاج إلى دفعة. يعيد تنفيذ count / width وحده صفرًا فيفشل هذه الحالة، بينما ينجح batches(8, 4). اختبر بقية قرب الطرفين مثل واحد وسبعة مع عرض أربعة لاختبار التقريب لأعلى صراحة.
2. يظهر عيب في بناء محسّن فقط. ضع تحقيقًا عمليًا يحفظ دليل الفشل دون إعلان خلل المحسّن فورًا.
اعرض الحل المشروح
احتفظ بالإدخال الفاشل وإصدار المترجم والأعلام كاملة وإصدار المصدر الدقيق. أعد المشكلة ببناء نظيف وشغّل المطهّرات المناسبة بإعدادات منفصلة وراجع الأعمار والقراءات غير المهيأة والسباقات والشروط المخالفة. اختزل المثال مع إبقاء الفشل؛ فكثيرًا ما يكشف التحسين سلوكًا غير معرّف أخفاه بناء التصحيح مصادفة.
أهم ما تعلمته
- اجعل البناء وحالات الفشل قابلة للتكرار.
- اختبر العقود عند الحدود وأبقِ الفحوص فعالة.
- تقدم أدوات التشخيص أدلة ضمن حدود موثقة.