عادات البنية وجودة الكود وراء كل منتج من Tabaga Team، بالأرقام من مستودعاتنا نفسها.
الاستوديو الفردي لا يملك فريقًا ثانيًا يلتقط دمجًا سيئًا، ولا قسم اختبار، ولا أحدًا غيره يشرح انهيارًا لصيدلي في الثامنة صباحًا. لذا طريقتنا في البناء هي في الغالب مجموعة عادات تجعل المفاجآت نادرة. هذه التدوينة تسردها بصدق، بالأرقام من مستودعاتنا بدل الصفات، حتى تحكم بنفسك إن كانت العادات تصمد.
- 12
- منتجًا بشكل المجلدات نفسه
- 14
- نوع فشل في تسلسل مغلق واحد
- 468
- اختبارًا في تطبيق واحد، كثير منها للاتجاه والتباين
- 65
- ملاحظة في تدقيق كتبناه عن منتجنا نحن
هيكل واحد
كل تطبيق نشحنه له الهيكل نفسه: مجلد core/ لما يتشاركه التطبيق كله، ومجلد لكل ميزة في داخله data وdomain وpresentation. العرض هو الودجات والحالة، والمجال هو الكيانات وحالات الاستعمال وأنواع الفشل، والبيانات هي المستودعات والمحوّلات. الحالة بـ BLoC أو Cubit في معظم المنتجات وبـ Riverpod في اثنين؛ وحقن الاعتماديات بـ get_it مع injectable؛ والتنقل بـ GoRouter مع حراسة حسب الدور.
قاعدتان تجعلان الهيكل يستحق. الميزات تستورد النواة، والنواة لا تستورد ميزة أبدًا. العرض يعتمد على المجال، والمجال على البيانات، وليس العكس أبدًا. المنتج الجديد يبدأ بنسخ الشكل، لا بتقرير شكل.
الخادم تفصيل
القاعدة الأهم هي اتجاه الأسهم. لا شيء فوق طبقة البيانات يستورد نوعًا من Firebase أو Supabase أو Appwrite. حين يحتاج منتج إلى خادم، فهو يحتاج إلى واجهة في core/services/abstractions، والمحوّل الفعلي يُختار مرة واحدة، في الحاوية.
تلك القاعدة هي سبب قدرة منصة قطع غيار السيارات على مغادرة Firebase إلى Appwrite بلمس نحو 78 ملف Dart و18 دالة سحابية بدل التطبيق كله، وسبب أن منتجًا للبناء يعمل على Appwrite Cloud، أو على VPS في الجزائر، أو على جهاز في مكتب العميل بالكود نفسه وعنوان مختلف.
جدران، لا أعراف
الأعراف تتآكل؛ القيود لا. منصة طلب الطعام للمطاعم مستودع واحد بثلاث حزم داخلية: النواة والواجهة والإدارة. تطبيق العميل يعتمد على الأوليين لا الثالثة، فكود الإدارة لا يمكن أن ينتهي في ملف العميل حتى بالخطأ. الحد يسكن في pubspec.yaml، لا في مراجعة كود.
نطبّق الحدس نفسه في أماكن أخرى. منصة الأزياء تشحن ثلاثة تطبيقات من مستودع واحد كنكهات، تتشارك خادمًا واحدًا، ونقلت كل كتابة حساسة خلف دوال خادم حتى لا يستطيع العميل إلا أن يطلب، لا أن يقرر. منصة المعدات تفرض قواعد عملها في قاعدة البيانات: مدة الإعلان، والمراجعة قبل النشر، والمالك لا يستطيع تمييز إعلاناته، عبر triggers وقيود فحص. منصة وظائف الطلبة تحفظ رموز التحقق في جدول لا يملك العميل صلاحية قراءته أصلًا.
“إن كانت القاعدة مهمة، فاجعلها قيدًا في مكان لا يصل إليه التطبيق.
”
أخطاء تستطيع الإمساك بها
المستودعات تعيد Either<Failure, T>. الفشل نوع مغلق، أربعة عشر صنفًا في أكبر منتجاتنا: شبكة، مصادقة، تحقق، صلاحية، غير موجود، تعارض وهكذا، كلٌّ يحمل ما يكفي ليشرح نفسه بالعربية والفرنسية والإنجليزية. الـ bloc يحوّل Right إلى حالة محمّلة وLeft إلى حالة خطأ، والودجات لا ترى استثناءً أبدًا.
نتيجتان نحبهما. التزامن المتفائل يصير ConflictFailure تستطيع الواجهة التعامل معه برسالة حقيقية بدل انهيار. ومسارات الكود لحالة «الخادم قال لا» تُكتب وتُختبر من اليوم الأول، لأن نظام الأنواع لن يدعك تنساها.
قاعدة البيانات وثيقة تصميم
منتجات «بلا اتصال أولًا» تعامل القاعدة المحلية كالمخزن الأساسي: Drift على SQLite، وSQLCipher للتشفير، والترحيلات في مكان واحد مرقّم. أحد المخططات في الإصدار 51 بعد شهرين من الاستعمال اليومي. مدير الصيدلية يثبّت موقع تخزينه باختبار، لأن منتجًا أعيدت تسميته وجّه يومًا بناءً جديدًا إلى مجلد فارغ فبدت السجلات محذوفة. خادم التراخيص يملك اختبار انحراف مخطط يُسقط البناء حين تختلف القاعدة الحية والترحيلات، بعد أن تخطّى CREATE TABLE IF NOT EXISTS بصمت عمودًا جديدًا في الإنتاج بينما اجتاز كل اختبار وحدة ضد مستودع في الذاكرة.
الاختبارات ذاكرة
لا نطارد التغطية؛ نثبّت القرارات. منصة المعدات تملك 468 اختبارًا عبر نحو ثلاثين ملفًا، وحصة جيدة منها تفحص التدويل وتخطيط اليمين-إلى-اليسار وتباين الألوان بدل المنطق. منصة قطع غيار السيارات شغّلت نحو 398 اختبار وحدة مع اختبارات تكامل ضد خادم حقيقي قبل أن يثق أحد بهجرة. تطبيق الروضات يملك اختبارًا يفشل إن جُلب خط عند التشغيل بدل تضمينه، لأن التطبيق يُستعمل حيث لا Wi-Fi. مدير الصيدلية يملك اختبارًا لمعرّفات العلامة وآخر لتبنّي القاعدة القديمة.
كل واحد من تلك الاختبارات موجود لأن شيئًا ما ساء يومًا، أو كاد. هذا مقياس التغطية الوحيد الذي نتابعه.
من الـ commit إلى الطاولة
الـ commits تتبع الصيغة الاصطلاحية، بالإنجليزية أو الفرنسية حسب من سيقرأ سجل التغييرات. المحلّل يفرض قواعد الاتجاه: EdgeInsetsDirectional وAlignmentDirectional وstart وend، أبدًا left وright. CI تعمل عند كل push وpull request. وسوم الإصدار تُرفض ما لم تكن على main. مثبّتات Windows تُفحص بمجموع تحقق وتُوقَّع بمفتاح Ed25519 في CI، والمثبّت غير الموقّع غير قابل للنشر. قنوات الإصدار تُضبط لكل رخصة لا لكل بناء، فنعرف دائمًا أي بناء كان يشغّله العميل حين انكسر شيء.
لتطبيقات المتاجر، الأجزاء المملة مؤتمتة: تصريح أمان بيانات Play يولده سكربت، وخطوات الكونسول التي لا واجهة برمجية لها موثّقة خطوة بخطوة، وملاحظات الإصدار تُكتب بثلاث لغات في سجل التغييرات نفسه.
نظام التصميم يسكن داخل التطبيق
كل منتج يحمل رموز تصميمه الخاصة ومعرض مكوّنات داخل التطبيق، يُفتح على /gallery في بناءات التصحيح. أحدها يسرد نحو خمسة وأربعين ودجة. العلامات تُرسم هندسةً متجهية في الكود، وكل أيقونة تشغيل وشاشة بدء ورسم متجر يولد من ذلك المصدر الواحد، فلا تستطيع أيقونة المتجر والعلامة داخل التطبيق أن تنحرفا عن بعضهما أبدًا.
قول «غير جاهز» بصوت عالٍ
ندقّق عملنا ونكتب النتيجة حتى حين لا تكون مُرضية. منصة الأزياء مرّت ببرنامج تحصين من 92 مرحلة مع اثني عشر تدقيقًا حسب الدور قبل التسليم. منصة التبريد تملك AUDIT.md بـ 65 ملاحظة، ثلاث منها حرجة، وحكم «غير جاهز للإنتاج» في الفقرة الأولى. خرائط الطريق تستعمل جداول حالة بثلاثة ألوان، و«غير مبني» لون.
لا شيء هنا ذكي. هي المجموعة الصغيرة نفسها من العادات مطبّقة على كل منتج، حتى يكون كلٌّ أسهل من سابقه.