MAISON CODE .
/ Architecture · XState · React · Testing · Engineering Patterns

آلات الدولة: جعل الدول المستحيلة مستحيلة

لماذا تولد القيم المنطقية 'isLoading' الأخطاء. نظرة عميقة على أجهزة الحالة المحدودة (FSM) وStatecharts وXState لمنطق واجهة المستخدم المضاد للرصاص.

AB
Alex B.
آلات الدولة: جعل الدول المستحيلة مستحيلة

دعونا نلقي نظرة على مكون نموذجي كتبه مطور مبتدئ.

const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [data, setData] = useState(null);

const fetchData = async () => {
  setIsLoading(true);
  حاول {
    const res = انتظار api.get();
    setData(res);
  } مسك (يخطئ) {
    setIsError(true);
  }
  setIsLoading(false); // خطأ: ماذا لو حدث خطأ؟ الانقسام المنطقي.
};

الخطأ: إذا تم تشغيل كتلة catch، يصبح isError صحيحًا. ثم يتم تشغيل setIsLoading(false). ولكن ماذا لو قام المستخدم بالنقر فوق “إعادة المحاولة”؟ استخدم بدء الجلب مرة أخرى. “isLoading” صحيح. isError صحيح أيضًا (من التشغيل السابق). تعرض واجهة المستخدم أداة Spinner ورسالة خطأ في وقت واحد. هذه الدولة المستحيلة.

يمكنك إصلاحه عن طريق إضافة setIsError(false) في البداية. ثم تقوم بإضافة “isSuccess”. الآن لديك 3 منطقيات. $2^3 = 8$ مجموعات محتملة. 4 فقط صالحة. 50% من مساحة دولتك عبارة عن أخطاء.

في Maison Code Paris، نعمل على تحسين الموثوقية. نحن نرفض “الحساء البوليني”. نحن نستخدم آلات الدولة.

لماذا تتحدث Maison Code عن هذا

في Maison Code Paris، نعمل كضمير معمari لعملائنا. غالبًا ما نرث حزمًا “حديثة” تم بناؤها دون فهم أساسي للحجم.

نناقش هذا الموضوع لأنه يمثل نقطة تحول حاسمة في النضج الهندسي. التنفيذ الصحيح يميز MVP الهش عن منصة مؤسسية مرنة يمكنها التعامل مع حركة مرور الجمعة السوداء.

لماذا تقوم Maison Code بنماذج المنطق بشكل رسمي

نحن نبني لوحات معلومات مالية حيث لا يكون “الخلل” مزعجًا فحسب؛ إنها مسؤولية. نحن نستخدم أجهزة الحالة (XState) لضمان الصحة:

  • السلامة: الحالات المستحيلة (التحميل + الخطأ) غير قابلة للتمثيل رياضيًا.
  • الوثائق: الكود هو الرسم التخطيطي. نقوم بتصدير مخططات الحالة لإظهار أصحاب المصلحة بالضبط كيف يعمل تدفق الخروج.
  • قابلية الاختبار: نقوم تلقائيًا بإنشاء اختبارات تغطية المسار بنسبة 100% من تعريف الجهاز. لا نأمل أن ينجح الأمر؛ نحن نثبت أنه يعمل.

آلة الحالة المحدودة (FSM)

آلة الدولة هي نموذج للسلوك. يتكون من:

  1. الحالات: (على سبيل المثال، خامل، تحميل، نجاح، فشل).
  2. الأحداث: (على سبيل المثال، جلب، إعادة المحاولة، إلغاء).
  3. الانتقالات: (على سبيل المثال، خامل + جلب -> تحميل).

ومن الأهمية بمكان أن تكون الآلة في حالة واحدة فقط في كل مرة. إذا كنت في حالة “التحميل” فعليًا، وحدث حدث “FETCH” (نقر المستخدم نقرًا مزدوجًا)، فسيتجاهله الجهاز (ما لم تسمح بذلك صراحةً). تختفي ظروف السباق.

XSstate: المكتبة

نحن نستخدم XState. إنه المعيار الخاص بـ FSMs في JavaScript. إنها تطبق Statecharts (معيار W3C SCXML)، والذي يسمح بما يلي:

  • الحالات الهرمية (الوالد/الطفل).
  • الدول الموازية (المناطق المتعامدة).
  • حالات التاريخ (تذكر المكان الذي توقفت فيه).

التنفيذ

استيراد { createMachine، تعين } من 'xstate'؛

const fetchMachine = createMachine({
  المعرف: "جلب"،
  الأولي: "خاملاً" ،
  السياق: {
    البيانات: فارغة،
    الخطأ: فارغ،
  },
  تنص على: {
    خاملاً: {
      على: { جلب: 'تحميل' }
    },
    التحميل: {
      // استدعاء الوعد (الخدمة)
      استدعاء: {
        src: "جلب البيانات"،
        تم: {
          الهدف: "النجاح"،
          الإجراءات: تعيين({ البيانات: (السياق، الحدث) => بيانات الحدث })
        },
        خطأ: {
          الهدف: "الفشل"،
          الإجراءات: تعيين ({ خطأ: (السياق، الحدث) => بيانات الحدث })
        }
      }
    },
    النجاح: {
      // الحالة الطرفية؟ أو ربما السماح بالتحديث
      في: { تحديث: 'جاري التحميل' }
    },
    الفشل: {
      عند: { إعادة المحاولة: 'جارٍ التحميل' }
    }
  }
});

لاحظ الوضوح. هل يمكنك “إعادة المحاولة” عند “النجاح”؟ لا، لم يتم تعريف عملية الانتقال. هل يمكنك “الجلب” عند “التحميل”؟ لا. المنطق صارم حسب التصميم.

الحراس والسياق

في بعض الأحيان، تكون التحولات مشروطة. “لا يمكن للمستخدم الانتقال إلى حالة الدفع إلا إذا كانت قيمة formIsValid صحيحة.”

// الحرس
على: {
  التالي: {
    الهدف: "الدفع"،
    كوند: (السياق) => context.formIsValid
  }
}

يؤدي هذا إلى نقل “منطق الأعمال” بشكل فعال من طبقة العرض (مكونات React) إلى طبقة النموذج (الجهاز). يصبح مكون React غبيًا. إنه يعرض الحالة ويرسل الأحداث فقط. machine.send('NEXT'). لا يهمه إذا ذهب بعد ذلك. الآلة تقرر.

الولايات الموازية (المناطق المتعامدة)

التطبيقات الحقيقية معقدة. تخيل أداة التحميل.

  1. يتم تحميل ملف (0% -> 100%).
  2. يمكن للمستخدم تصغير/تكبير القطعة.

هذه مستقلة. يمكنك “التحميل” و”التصغير”. يتعامل XState مع هذا عبر الحالات الموازية.

تنص على: {
  عملية التحميل: {
    الأولي: "معلق"،
    الحالة: { معلق: {}، التحميل: {}، كامل: {} }
  },
  واجهة المستخدم: {
    الأولي: "موسعة"،
    ينص على: { موسع: {}، مصغر: {} }
  }
}

المتخيل والتواصل

أفضل ميزة في XState هي Visualizer. يمكنك نسخ ولصق الكود الخاص بك في “stately.ai/viz” ويقوم بإنشاء رسم تخطيطي تفاعلي. نحن نستخدم هذا للتواصل مع مديري المنتجات. PM: “يجب ألا يتمكن المستخدم من الإلغاء بمجرد بدء الدفع.” المطور: “انظر إلى الرسم التخطيطي. لا يوجد سهم إلغاء من حالة معالجة_الدفع.” إنه يحاذي النموذج العقلي مع النموذج الرمزي.

الاختبار القائم على النموذج

نظرًا لأن التنفيذ الحالي عبارة عن رسم بياني، فيمكننا إنشاء اختبارات تلقائيًا. يمكن لـ @xstate/test حساب أقصر مسار لكل ولاية. سيتم إنشاء خطة اختبار:

  1. ابدأ من “الخمول”.
  2. إطلاق النار على “الجلب”.
  3. توقع “التحميل”.
  4. حل الوعد.
  5. توقع “النجاح”.

فهو يضمن حصولك على تغطية 100% للتدفقات المنطقية الخاصة بك.

10. نموذج الممثل (ممثلو XState)

آلة واحدة عظيمة. ولكن ماذا لو كان لديك 10 أدوات تحميل؟ أنت لا تريد “uploadMachine” عملاقًا واحدًا. تريد إنتاج 10 ممثلين صغار. يتواصل الجهاز الأصل (الصفحة) مع الممثل الطفل (القطعة) عبر الرسائل (send({ type: 'UPLOAD_COMPLETE' })). هذا هو نموذج الممثل (الذي شاع بواسطة Erlang/Elixir). ويوفر العزلة. إذا تعطل أحد الممثلين، فلن يؤدي ذلك إلى تعطل التطبيق بأكمله. يجعل XState هذا أمرًا تافهًا باستخدام spawn().

11. مصممو الحالة المرئية

لماذا كتابة التعليمات البرمجية على الإطلاق؟ يتيح لك Stately.ai سحب المربعات وإفلاتها لتصميم المنطق. نظرًا لأن الكود هو الرسم التخطيطي (متماثل)، يقوم المصمم بتصدير JSON الذي يستورده الكود الخاص بك. وهذا يفتح الباب أمام منطق الكود المنخفض الذي يحتفظ به كبار المهندسين. فهو يضمن أن تكون “قواعد العمل” مرئية لأصحاب المصلحة، وليست مخفية في معكرونة useEffect.

13. الحالات الهرمية (الحالات المركبة)

إن Checkout ليس مجرد قائمة واحدة من الحالات. لها مراحل.

  • الخروج:
    • الشحن: (العنوان، الطريقة)
    • الدفع: (إدخال_البطاقة، التحقق_3ds) إذا قمت بإلغاء “الدفع”، هل ستعود إلى “الشحن”؟ باستخدام الحالات الهرمية، يمكنك الانتقال إلى “checkout.shipping.history”. وهذا يسمح لنا بنمذجة التدفقات المعقدة دون “انفجار الحالة”. نقوم بتجميع الحالات ذات الصلة في عقدة أصل. يتعامل الأصل مع الأحداث العالمية (على سبيل المثال، انتقالات تسجيل الخروج إلى الصفحة الرئيسية من أي حالة فرعية).

14. الاختبار: النموذج هو الاختبار

مع @xstate/test، لا نكتب اختبارات E2E يدوية لكل نقرة على الزر. نكتب ** تأكيدات المسار **.

  • في حالة “الشحن”، قم بتأكيد “getByText(“عنوان الشحن”)`.
  • في حالة الدفع، قم بتأكيد getByText("بطاقة الائتمان"). تقوم المكتبة بعد ذلك بتشغيل رسم بياني موجه (أقصر مسار) وتنفيذ محرك الدمى/الكاتب المسرحي. يقوم بإنشاء مئات الاختبارات تلقائيًا. إذا قمت بإضافة حالة جديدة، فسيتم تحديث الاختبارات من تلقاء نفسها.

15. التعامل مع انفجار الحالة

أكبر انتقاد لولايات ميكرونيزيا الموحدة هو “انفجار الدولة”. إذا كان لديك 10 قيم منطقية، فلديك $2^{10} = 1024$. إدراجهم جميعا في الجهاز أمر مستحيل. الحل: الحالات المتوازية (التعامد). بدلاً من loading_and_modal_open، load_and_modal_loced، success_and_modal_open… لديك منطقتان: data: { جارٍ التحميل، النجاح } وui: { modal_open, modal_closed }. وهذا يقلل من التعقيد من المضاعف ($M * N$) إلى المضاف ($M + N$).

16. التحقق الرسمي (السلامة)

نظرًا لأن XState عبارة عن رسم بياني رياضي، فيمكننا إثبات الأشياء رياضيًا. “هل يمكن الوصول إلى حالة “الدفع” دون المرور عبر “الشحن”؟” يمكننا تشغيل خوارزمية الرسم البياني للتحقق من وجود المسار. إذا حدث ذلك، يفشل البناء. هذا التحقق الرسمي. عادة ما يكون محجوزًا لـ NASA/Avionics، لكن XState يجلبه إلى نماذج React. وهذا يمنحنا الثقة في أن منطق “البوابة” الخاص بنا غير قابل للكسر.

17. الاستنتاج

تضيف آلات الحالة الإسهاب. تستغرق كتابة الآلة وقتًا أطول من useState. ولكن بالنسبة للتدفقات المعقدة (الخروج، الإعداد، المعالجات)، يكون عائد الاستثمار هائلاً. أنت تستبدل “سرعة التنفيذ” بـ “سرعة الصيانة” و”الموثوقية”.

نحن نؤمن بأن منطق واجهة المستخدم هو تصميم خوارزمي. إنه يستحق النمذجة الرسمية.


الخروج عربات التي تجرها الدواب؟

هل لديك ظروف سباق في تدفق الدفع الخاص بك؟

إعادة تصنيع أجهزة الحالة. قم بتوظيف مهندسينا.