من المستند إلى التنفيذ: مصفوفة التتبع
كيف توجّه المستندات المعتمدة عملية التطوير، وكيف تتأكد أن الكود يطابق المواصفات؟ ⏱ نحو 50 دقيقة.
- تستخدم المستندات المعتمدة للإجابة عن أسئلة التطوير.
- تبني مصفوفة تتبع من المتطلب إلى التصميم والكود والاختبار.
- تحوّل معايير القبول إلى اختبارات، وتطبق تعريف «منجز».
- تنفذ ميزة كاملة من مواصفتها المعتمدة.
١المستند المعتمد يقود التطوير
من الخطة: «كيفية استخدام المستندات المعتمدة للمواصفات في توجيه عملية تطوير التطبيق وتنفيذه، وضمان التناسب بين التطوير والمواصفات المحددة». بعد الاعتماد لا يسأل المطور «ماذا أبني؟» بل يفتح المستند: المتطلب يقول ماذا، والمستند التقني يقول كيف، ومعايير القبول تقول متى ننتهي.
| سؤال المطور | جوابه في |
|---|---|
| ما الميزة التالية؟ | أولويات MoSCoW وترتيب الميزات في SRS |
| ما سلوكها بالضبط؟ | متطلبات الميزة بصيغة EARS وقواعد العمل |
| ما المسار والجسم والردود؟ | جدول API في المستند التقني |
| من يحق له استخدامها؟ | مصفوفة الصلاحيات |
| متى أعتبرها منتهية؟ | معايير القبول + تعريف «منجز» |
٢مصفوفة التتبع
مصفوفة التتبع Traceability Matrix تربط كل متطلب بما يحققه في التصميم والكود، وبالاختبار الذي يثبت تحققه. تجيب في أي لحظة: هل نُفّذ كل شيء؟ وما الذي يتأثر إن تغيّر متطلب؟
| المتطلب | التصميم | الكود | الاختبار | الحالة |
|---|---|---|---|---|
| FR-02.2 التسجيل | POST /enroll، جدول enrollments | enrollController.store | AC-02.1, AC-02.2 | مُختبَر |
| FR-03.2 الاعتماد | PATCH /status | enrollController.status | AC-03.1 | مُختبَر |
| FR-15.1 قائمة الانتظار | POST /enroll (202) | — | AC-15.1 | قيد التنفيذ |
| NFR-01 300 مستخدم | فهارس + تخزين مؤقت | migration 0007 | اختبار حمل | لم يبدأ |
ويمكن رسم التتبع بمخطط المتطلبات الذي تعرفنا عليه في الدرس 5: العناصر التي تحقق satisfies المتطلب، والاختبار الذي يتحقق verifies منه:
requirementDiagram functionalRequirement FR15 { id: "FR-15.1" text: "قائمة انتظار للدورة المكتملة" risk: medium verifymethod: test } element api { type: "POST /api/courses/:id/enroll" } element model { type: "جدول waitlist" } element acceptance { type: "اختبار القبول AC-15.1" } api - satisfies -> FR15 model - satisfies -> FR15 acceptance - verifies -> FR15
المتطلب بلا عنصر يحققه لم يُنفّذ، وبلا اختبار يتحقق منه لم يُثبت. هذان السؤالان كافيان لمعرفة نسبة اكتمال المشروع بصدق.
٣معايير القبول تصبح اختبارات
كل معيار قبول «عندما… فإن…» يتحول مباشرة إلى حالة اختبار: الموقف طلبات تهيئة، والحدث طلب، والنتيجة فحص للاستجابة. هذا ما يفعله التحقق الآلي في تحديات المنصة منذ الدرس 13. ومع تعريف «منجز» يتضح متى ننتقل للميزة التالية:
- كل معايير القبول للميزة تنجح في اختبارات آلية.
- الصلاحيات مطابقة لمصفوفة الصلاحيات، مع اختبار حالة سالبة واحدة على الأقل.
- مراجعة كود من زميل تقارن التنفيذ بالمتطلب.
- تحديث مصفوفة التتبع، وتوثيق أي انحراف عن المستند.
توقّع وتحقّق
ما الذي تربطه مصفوفة التتبع؟
متطلب بلا اختبار يتحقق منه يعني:
أضاف المطور ميزة لم يطلبها أحد. هذا يسمّى:
المطور اكتشف أن المواصفة ناقصة. ماذا يفعل؟
الخلاصة
- المتطلب يقول ماذا، والمستند التقني كيف، ومعايير القبول متى ننتهي.
- مصفوفة التتبع: متطلب ← تصميم ← كود ← اختبار ← حالة.
- في
requirementDiagram: satisfiesلما يحقق، وverifiesلما يختبر. - كل «عندما… فإن…» حالة اختبار، و«منجز» = المعايير تنجح + الصلاحيات + مراجعة + تحديث التتبع.
- الانحراف عن المواصفة يُوثّق ويُرفع، لا يُنفّذ بصمت.
تحدٍّ برمجي: نفّذ قائمة الانتظار من مواصفتها
متقدمالمواصفة المعتمدة (الإصدار 1.1): FR-15.1 عندما تكون الدورة مكتملة، يجب أن يضيف النظام المتدرب إلى قائمة الانتظار ويرد بالرمز 202 وترتيبه. FR-15.2 عندما يُلغى تسجيل، يجب أن ينقل النظام أول منتظر إلى المسجلين. BR-06 لا يُسجّل المتدرب ولا ينتظر مرتين (409).
- التسجيل في دورة لها مقاعد ← 201
{"status":"enrolled"}. - في دورة مكتملة ← 202
{"status":"waitlisted","position":n}. - التكرار (مسجل أو منتظر) ← 409.
- الإلغاء يرد
{"cancelled":true,"promoted":…}بمعرّف من نُقل من الانتظار (أو null).
👀 اعرض الاستجابات المطلوبة
POST /api/courses/1/enroll🔑POST /api/courses/1/enroll🔑POST /api/courses/1/enroll🔑POST /api/courses/1/enroll🔑POST /api/courses/1/enroll🔑DELETE /api/courses/1/enroll🔑GET /api/courses/1
- الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
- AC: الأول يسجل ← 201
- AC: الثاني يسجل ← 201
- AC-15.1: الدورة مكتملة ← 202
- AC-15.1: الترتيب 1
- الرابع ترتيبه 2
- BR-06: المنتظر لا ينتظر مرتين ← 409
- AC-15.2: الإلغاء ينقل أول منتظر
(3) - الحالة النهائية: المسجلون 2 و3، والمنتظر 4
💡 تلميح 1
(BR-06).💡 تلميح 2
course.waitlist.push(...) ثم res.status(202) مع position: course.waitlist.length.💡 تلميح 3
course.waitlist.shift() يأخذ الأول ويحذفه من القائمة.حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
const express = require('express');const app = express();app.use(express.json()); const tokens = { 'tok-1': 1, 'tok-2': 2, 'tok-3': 3, 'tok-4': 4 };const course = { id: 1, title: 'تصميم الويب', capacity: 2, enrolled: [], waitlist: [] }; function auth(req, res, next) { req.userId = tokens[(req.headers.authorization || '').replace('Bearer ', '')]; if (!req.userId) return res.status(401).json({ error: 'سجّل الدخول' }); next();} app.get('/api/courses/1', (req, res) => { res.json({ enrolled: course.enrolled, waitlist: course.waitlist });}); // FR-02.2 + FR-15.1 + BR-06app.post('/api/courses/1/enroll', auth, (req, res) => { if (course.enrolled.includes(req.userId) || course.waitlist.includes(req.userId)) return res.status(409).json({ error: 'مسجل أو منتظر مسبقًا' }); // BR-06 if (course.enrolled.length >= course.capacity) { // FR-15.1 course.waitlist.push(req.userId); return res.status(202).json({ status: 'waitlisted', position: course.waitlist.length }); } course.enrolled.push(req.userId); res.status(201).json({ status: 'enrolled' });}); // FR-02.5 + FR-15.2app.delete('/api/courses/1/enroll', auth, (req, res) => { course.enrolled = course.enrolled.filter(id => id !== req.userId); let promoted = null; if (course.enrolled.length < course.capacity && course.waitlist.length) { promoted = course.waitlist.shift(); course.enrolled.push(promoted); } res.json({ cancelled: true, promoted });}); app.listen(3000);