
لا تحتاج إلى تصميم مثالي من البداية. تحتاج إلى اتجاه، وإلى استعداد لمواصلة التحسين حتى تتقارب الأشياء.
هذا ما تعلّمته بعد أن بنيت التطبيق نفسه اثنتي عشرة مرة، وهدمته، وأعدت بناءه، إلى أن علّمني النظام ما يريد أن يكون.
كل مشروع برمجي يبدأ بالطريقة نفسها: يرسم أحدهم مربعات على السبّورة ويعلن: «هذه هي البنية». وغالبًا ما يكثر الإيماء بالموافقة. وأحيانًا تظهر سبّورة ثانية.
ثم تبدأ البناء فعلًا، فيحضر الواقع دون دعوة.
المربعات لا تتسع. البيانات تتدفق في الاتجاه الخطأ. الخدمة التي ظننتها بسيطة لها سبع عشرة حالة حدّية. بحلول الأسبوع الثالث تكتب شيفرة تعتذر بهدوء للسبّورة.
الحقيقة القاسية هي: لا يمكنك أن تعرف الشكل الكامل لنظامك قبل أن تبني أجزاء منه. المتطلبات تكذب. المستخدمون يفاجئونك. والحجم الكبير يكشف أشياء لم يتوقعها أي مخطط.
وهذا لا بأس به. المشكلة ليست أن تصميمك كان خاطئًا. المشكلة هي أن تعامل التصميم الأول كأنه مقدّس.
إن عملت مع الأنظمة الموزّعة فأنت تعرف مفهوم الاتساق النهائي (eventual consistency): قد تكون لدى العُقد المختلفة في النظام رؤى مختلفة للبيانات في لحظة معينة، لكن مع وقت كافٍ وتواصل كافٍ تتقارب كلها إلى الحالة نفسها.
تصميم برمجياتك يعمل بالطريقة نفسها.
تبدأ بمعلومات ناقصة. في بنيتك فجوات. وفهمك للمجال جزئي. لكن إن واصلت التكرار، وجعلت كل دورة التصميم أكثر صدقًا قليلًا وأقرب إلى الواقع قليلًا، فإنك تتقارب نحو شيء يناسب فعلًا.
الهدف ليس أن تصيب من أول مرة. الهدف أن تواصل الخطأ بقدر أقل.
سمّها التطوير بمبدأ «اكتشفها وأنت تسير» (FIAYG). ليست جذابة مثل TDD أو DDD، لكنها أصدق بكثير في وصف كيف تُبنى البرمجيات فعلًا.
والمفتاح هو: كلما تعلّمت شيئًا جديدًا، من خلل، أو من ملاحظات المستخدمين، أو من اصطدام بحدّ الحجم، ترمّز ذلك الدرس في البنية. لا تعالج العَرَض فقط. تصلح النظام بحيث لا يمكن للعَرَض أن يتكرر.
المدار إطار تصريحي يحوّل مخططات آلات الحالة إلى تطبيقات متكاملة. ملف .orb واحد يصف كياناتك وسلوكياتك وواجهتك، ويولّد المترجم واجهة React أمامية تعمل، وخلفية Express، ونماذج قاعدة البيانات.
هذا ما هو عليه الآن. وهكذا وصلنا إليه.
بدأ الأمر بسيطًا. كان لدينا مشروع واحد، عميل يحتاج إلى تطبيق أعمال يعتمد على عمليات CRUD بكثافة. كتبناه بيدنا بالطريقة المعتادة: مكوّنات React، ومسارات Express، وكيانات TypeORM. شيفرة نمطية في كل مكان.
لاحظنا أننا نكتب الأنماط نفسها مرة بعد مرة: جدول لعرض السجلات، ونافذة منبثقة لإنشائها، ونموذج لتعديلها، وتأكيد لحذفها. نسخ. لصق. إعادة تسمية. وتكرار.
لا بد من طريقة أفضل.
فجرّبنا شيئًا: ماذا لو وصفنا البيانات والسلوك في مخطط JSON، وولّدنا الشيفرة النمطية منه؟
كان الأمر خشنًا. المخطط بالكاد كان منظّمًا. والمولّد قالب نصي مع استدعاءات fs.writeFileSync. لكنه عمل. العميل كان سعيدًا. وأصبحنا نسلّم أسرع.
ما تعلّمناه: نسبة الـ 80% من كل تطبيق التي هي شيفرة نمطية يمكن وصفها تصريحيًا. أما الـ 20% الفريدة فهي ما تريد فعلًا أن تقضي وقتك فيه.
كان للعميل الثاني متطلبات مختلفة. كان مولّدنا مرتبطًا بإحكام شديد بأنماط العميل الأول. كنا قد ثبّتنا افتراضات في كل مكان: اصطلاحات التسمية، وبنية الملفات، وتسلسل المكوّنات.
كان بإمكاننا أن نرقّعه. لكننا توقفنا وسألنا: كيف ستبدو نسخة عامة من هذا؟
هنا بدأت البنية تتبلور. أدخلنا مفهوم Orbitals، وحدات ميزات مكتفية بذاتها تجمع كيانًا (شكل البيانات)، وسمة (trait: سلوك على هيئة آلة حالة منتهية)، وصفحة (مسارًا). وفصلنا تعريف المخطط عن توليد الشيفرة.
ما تعلّمناه: في اللحظة التي تصبح فيها لديك حالة استخدام ثانية، تكشف تجريداتك عن نفسها. إن لم يتعمّم الشيء فلم يكن تجريدًا. كان مصادفة.
مع ازدياد عدد المشاريع ازدادت الفوضى. كان لكل مشروع عميل نظام تصميمه الخاص، واختلافاته الخاصة في المكوّنات، وفكرته الخاصة عن شكل «الزر». كنا نصون عشر نكهات من المكوّن نفسه.
فأعدنا هيكلة كل شيء في مستودع واحد (monorepo) بقواعد صريحة:
@almadar/ui، مشتركة، ومرقّمة الإصدارات، ولا تُكرَّر أبدًاprojects/{name}/design-system/، توسّع الأساس ولا تستبدلهorbital-rust/، مقدّس، لا يُعدَّل أبدًا دون نقاشprojects/{name}/app/، ولا تُحرَّر مباشرةوالأهم أننا أضفنا قيودًا. قواعد صارمة يرمّزها الفريق في ملف CLAUDE.md يجب أن يتبعها كل مطوّر (بشري أو ذكاء اصطناعي):
.orb وأعد الترجمة، ولا تلمس الشيفرة المولّدةوفرضنا تلك القيود بـ إضافة ESLint مخصّصة (@almadar/eslint). ليست إرشادات اختيارية، بل أخطاء حقيقية تمنع خطّاف ما قبل الإيداع (pre-commit hook). لا تحذيرات مسموحة. بعض القواعد التي بنيناها:
| القاعدة | ما الذي تلتقطه |
|---|---|
no-raw-dom-elements | تمنع <div>, <span>, <button> وغيرها. استخدم VStack وTypography وButton بدلًا منها |
no-as-any | تمنع تحويلات النوع as any. أصلح مشكلة النوع الحقيقية |
no-import-generated | تمنع الاستيراد من projects/*/app/. الشيفرة المولّدة ليست واجهة برمجية |
require-closed-circuit-props | يجب أن يقبل كل مكوّن className وisLoading وerror وentity |
require-event-bus | يجب أن تستخدم التفاعلات useEventBus() لا onClick الخام |
require-display-name | يجب أن يضبط كل مكوّن displayName لأغراض التصحيح |
require-stories | يجب أن يُسلَّم كل مكوّن جديد مع ملف .stories.tsx |
event-naming-convention | يجب أن تتبع أحداث الواجهة النمط UI:EVENT_NAME |
template-no-hooks | مكوّنات القوالب نقية. لا useState ولا useEffect |
template-no-iteration | القوالب لا تكرّر. الكائن العضوي (organism) هو من يتولى التكرار |
organism-no-callback-props | تتواصل الكائنات العضوية عبر ناقل الأحداث، لا عبر دوال الاستدعاء في الخصائص |
يشغّل خطّاف ما قبل الإيداع ESLint على كل ملف .ts/.tsx في منطقة الإعداد. إن انطلقت أي قاعدة مُنع الإيداع. بلا استثناءات. كل قاعدة بمستوى الخطورة 2 (خطأ)، لا تحذيرًا. التحذيرات طموحات. أما الأخطاء فهي البنية.
والنتيجة: لا يستطيع مطوّر جديد، بشريًا كان أو ذكاءً اصطناعيًا، أن ينتهك مبادئ التصميم عن غير قصد. المدقّق يقول لا قبل أن يُفتح طلب الدمج أصلًا.
القيود تبدو مقيِّدة. لكنها في الحقيقة محرِّرة. حين تكون القواعد واضحة تصبح القرارات أسرع، وتُلتقط التناقضات أبكر، ويستطيع أعضاء الفريق الجدد المساهمة دون أن يكسروا شيئًا.
ما تعلّمناه: المستودع الواحد بلا قيود ليس سوى كتلة واحدة مهذّبة. القيود هي البنية، والتدقيق (linting) هو ما يجعلها حقيقية.
هنا يصبح الأمر مثيرًا.
حين وصلت مساعدات البرمجة بالذكاء الاصطناعي، تبيّن أن بنيتنا القائمة على القيود هي بالضبط ما تحتاجه لتعمل جيدًا. نموذج لغوي كبير (LLM) معه مخطط واضح ليحرّره، وسجل أنماط يتحقق منه، ومدقّق يمنع الأنماط السيئة، ومترجم يقول «لا» للمخرجات المعطوبة؟ هذا وكيل ذكاء اصطناعي منتج.
ونموذج لغوي كبير يعمل في قاعدة شيفرة حرّة بلا قواعد؟ هذا وكيل واثق جدًا يفعل الشيء الخطأ بثقة.
صغنا هذا رسميًا: يحصل الذكاء الاصطناعي على القيود في CLAUDE.md، ويفحص سجل الأنماط قبل إضافة المكوّنات، ويشغّل ESLint على كل ملف يغيّره، ويتحقق من المخطط بمترجم Orbital قبل أن يعلن إنجاز أي شيء. المترجم هو مراجع الشيفرة عند الذكاء الاصطناعي، ولا يتعب أبدًا، ولا تفوته حالة، ويعمل في أقل من 50 مللي ثانية.
البنية التي تعثرنا بها عبر التكرار تبيّن أنها البنية الصحيحة للتطوير بمساعدة الذكاء الاصطناعي. لم نخطط لذلك. تقاربنا إليها.
ما تعلّمناه: الذكاء الاصطناعي يضخّم أي بنية (أو فوضى) موجودة أصلًا في قاعدة شيفرتك. القيود ليست بيروقراطية. هي الفرق بين ذكاء اصطناعي يساعد وذكاء اصطناعي يكسر الأشياء بثقة.
إن كنت مبتدئًا، فهكذا تطبّق هذا دون أن تبني إطارًا من الصفر:
يجب أن تفعل نسختك الأولى شيئًا واحدًا. ليس الشيء مع معالجة الأخطاء مع التخزين المؤقت مع نظام إعدادات. الشيء فقط. ستتعلم من إطلاق شيء صغير أكثر مما تتعلم من تصميم شيء كبير.
حين تكتب الشيفرة نفسها للمرة الثالثة، فهذه قاعدة الشيفرة تخبرك أن شيئًا ما يريد أن يُجرَّد. لا تجرّد عند التكرار الأول (استعجال). ولا تتجاهل الثالث (إهمال). المرة الثانية هي الإشارة، والثالثة هي التأكيد.
حين تجد نمط خلل، مثل نافذة منبثقة قد تبقى مفتوحة عالقة، فلا تصلح هذه النافذة وحدها. اكتشف أي قاعدة كانت ستمنع هذا الصنف من الخلل كليًا. دوّنها. وافرضها. في حالة المدار حوّلنا «يجب أن يكون لكل نافذة منبثقة انتقال CANCEL» إلى قاعدة تحقق في المترجم، و«لا تستخدم عناصر HTML الخام أبدًا» إلى خطأ في المدقّق. وفي مشروعك قد تكون قاعدة ESLint مخصّصة، أو خطّاف ما قبل الإيداع، أو قائمة مراجعة للشيفرة. الوسيلة لا تهم. المهم أن تكون القاعدة تلقائية. إن احتاجت إلى إنسان ليتذكرها فستُنسى في النهاية.
كلما رصدت نمطًا وعمّمته، ينبغي أن تكتب شيفرة أقل لإنجاز الشيء نفسه. إن كانت قاعدة شيفرتك تنمو بالوتيرة نفسها إلى الأبد فأنت تراكم التعقيد ولا تديره. الهدف وظائف أكثر لكل سطر، لا أسطر أكثر.
أعدنا تسمية أشياء. وحذفنا حزمًا كاملة. ونقلنا نظام التصميم مرتين. وفي كل مرة كان النظام يصبح أنظف وتصبح القواعد أوضح. التحوّل ليس فشلًا. إنه دليل على أن فهمك تحسّن.
بعد عدة سنوات ومشاريع عملاء كثيرة، هذا ما تقاربنا إليه:
أفضل بنية برمجية ليست تلك التي تصممها قبل أن تبدأ. إنها تلك التي تنبثق من الإصغاء إلى ما يحاول نظامك أن يقوله لك، عبر الأخطاء، وعبر التكرار، وعبر الاحتكاك الذي تشعر به حين يكون شيء ما خاطئًا.
اكتشفها وأنت تسير. رمّز ما تتعلمه. واصل الخطأ بقدر أقل.
الاتساق نهائي. أطلق مع ذلك.