طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 241 · تحليل وتصميم برمجيات الويب
الدرس 27 · الوحدة 12: استخدام مستندات المواصفات في التطوير والتنفيذ

من المستند إلى التنفيذ: مصفوفة التتبع

كيف توجّه المستندات المعتمدة عملية التطوير، وكيف تتأكد أن الكود يطابق المواصفات؟ ⏱ نحو 50 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تستخدم المستندات المعتمدة للإجابة عن أسئلة التطوير.
  • تبني مصفوفة تتبع من المتطلب إلى التصميم والكود والاختبار.
  • تحوّل معايير القبول إلى اختبارات، وتطبق تعريف «منجز».
  • تنفذ ميزة كاملة من مواصفتها المعتمدة.

١المستند المعتمد يقود التطوير

من الخطة: «كيفية استخدام المستندات المعتمدة للمواصفات في توجيه عملية تطوير التطبيق وتنفيذه، وضمان التناسب بين التطوير والمواصفات المحددة». بعد الاعتماد لا يسأل المطور «ماذا أبني؟» بل يفتح المستند: المتطلب يقول ماذا، والمستند التقني يقول كيف، ومعايير القبول تقول متى ننتهي.

سؤال المطورجوابه في
ما الميزة التالية؟أولويات MoSCoW وترتيب الميزات في SRS
ما سلوكها بالضبط؟متطلبات الميزة بصيغة EARS وقواعد العمل
ما المسار والجسم والردود؟جدول API في المستند التقني
من يحق له استخدامها؟مصفوفة الصلاحيات
متى أعتبرها منتهية؟معايير القبول + تعريف «منجز»

٢مصفوفة التتبع

مصفوفة التتبع Traceability Matrix تربط كل متطلب بما يحققه في التصميم والكود، وبالاختبار الذي يثبت تحققه. تجيب في أي لحظة: هل نُفّذ كل شيء؟ وما الذي يتأثر إن تغيّر متطلب؟

المتطلبالتصميمالكودالاختبارالحالة
FR-02.2 التسجيلPOST /enroll، جدول enrollmentsenrollController.storeAC-02.1, AC-02.2مُختبَر
FR-03.2 الاعتمادPATCH /statusenrollController.statusAC-03.1مُختبَر
FR-15.1 قائمة الانتظارPOST /enroll (202)—AC-15.1قيد التنفيذ
NFR-01 300 مستخدمفهارس + تخزين مؤقتmigration 0007اختبار حمللم يبدأ

ويمكن رسم التتبع بمخطط المتطلبات الذي تعرفنا عليه في الدرس 5: العناصر التي تحقق satisfies المتطلب، والاختبار الذي يتحقق verifies منه:

diagram.mmd
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

<<satisfies>>

<<satisfies>>

<<verifies>>

<<Functional Requirement>>

FR15

ID: FR-15.1

Text: قائمة انتظار للدورة المكتملة

Risk: Medium

Verification: Test

<<Element>>

api

Type: POST /api/courses/:id/enroll

<<Element>>

model

Type: جدول waitlist

<<Element>>

acceptance

Type: اختبار القبول AC-15.1

المتطلب بلا عنصر يحققه لم يُنفّذ، وبلا اختبار يتحقق منه لم يُثبت. هذان السؤالان كافيان لمعرفة نسبة اكتمال المشروع بصدق.

٣معايير القبول تصبح اختبارات

كل معيار قبول «عندما… فإن…» يتحول مباشرة إلى حالة اختبار: الموقف طلبات تهيئة، والحدث طلب، والنتيجة فحص للاستجابة. هذا ما يفعله التحقق الآلي في تحديات المنصة منذ الدرس 13. ومع تعريف «منجز» يتضح متى ننتقل للميزة التالية:

✅ تعريف «منجز» Definition of Done في مشروع «دورات»
  • كل معايير القبول للميزة تنجح في اختبارات آلية.
  • الصلاحيات مطابقة لمصفوفة الصلاحيات، مع اختبار حالة سالبة واحدة على الأقل.
  • مراجعة كود من زميل تقارن التنفيذ بالمتطلب.
  • تحديث مصفوفة التتبع، وتوثيق أي انحراف عن المستند.

توقّع وتحقّق

ما الذي تربطه مصفوفة التتبع؟

متطلب بلا اختبار يتحقق منه يعني:

أضاف المطور ميزة لم يطلبها أحد. هذا يسمّى:

المطور اكتشف أن المواصفة ناقصة. ماذا يفعل؟

الخلاصة

تحدٍّ برمجي: نفّذ قائمة الانتظار من مواصفتها

متقدم

المواصفة المعتمدة (الإصدار 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).
👀 اعرض الاستجابات المطلوبة
✓ التحقق الآلي: يشغّل المختبر خادمك من جديد ويرسل إليه هذه الطلبات بالترتيب ثم يفحص الردود
  1. POST /api/courses/1/enroll 🔑
  2. POST /api/courses/1/enroll 🔑
  3. POST /api/courses/1/enroll 🔑
  4. POST /api/courses/1/enroll 🔑
  5. POST /api/courses/1/enroll 🔑
  6. DELETE /api/courses/1/enroll 🔑
  7. 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
في التسجيل: افحص التكرار في enrolled و waitlist معًا أولًا (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);
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس