الوحدات هي مبدأ يتم بموجبه تجميع التطبيق من وحدات مستقلة، كل منها مسؤول عن وظيفة واحدة. وفقًا لـ Android Developers، فإن التقسيم إلى وحدات يسرع البناء من خلال التجميع المتوازي ويسمح للفرق بالعمل على أجزاء مختلفة من التطبيق بشكل مستقل. أصبحت الهندسة المعيارية المعيار للمشاريع المحمولة الكبيرة التي تضم عشرات المطورين.
الخلاصة
الوحدات هي طريقة لتنظيم الكود حيث يتكون التطبيق من وحدات مترابطة بشكل فضفاض، توفر كل منها وظيفة محددة بدقة من خلال واجهة عامة. على عكس الهندسة المتجانسة حيث توجد جميع الفئات في مشروع واحد، يقسم النهج المعياري الكود إلى وحدات بناء مستقلة فعليًا.
الهدف الرئيسي من الوحدات هو إدارة التعقيد. يمكن للمطور التركيز على وحدة واحدة دون الحفاظ على قاعدة الكود بأكملها في ذهنه. لكل وحدة منطقة مسؤوليتها الخاصة ويمكن تطويرها واختبارها ونشرها بشكل مستقل عن الآخرين. هذا ذو قيمة خاصة في المشاريع التي تضم 10+ مطورين، حيث يؤدي العمل المتوازي على تطبيق متجانس إلى صراعات دمج متكررة.
من المهم التمييز بين الوحدات والهندسة الطبقية. الطبقات (Presentation, Domain, Data) تقسم الكود حسب المعايير الفنية، بينما تقسمه الوحدات حسب المعايير الوظيفية. يمكن لوحدة “ملف المستخدم” أن تحتوي على طبقاتها الخاصة داخلها. في الممارسة العملية، يتم الجمع بين النهج المعياري والهندسة الطبقية: لكل وحدة هيكلها ثلاثي الطبقات الخاص.
وحدات الميزات هي النوع الأكثر شيوعًا من الوحدات. يتم فصل كل شاشة أو مجموعة من الشاشات ذات الصلة في وحدة خاصة بها: Onboarding, Profile, Settings, Feed. تحتوي وحدة الميزة على كل ما هو مطلوب لتعمل الميزة: واجهة المستخدم ومنطق الأعمال وطبقة البيانات. حدود الوحدة محمية — لا يمكن للميزات الأخرى الوصول إلى فئاتها الداخلية.
الوحدات الأساسية تحتوي على البنية التحتية المشتركة: الشبكات وقواعد البيانات والتحليلات ونظام التصميم. لا تعتمد على وحدات الميزات، لكن وحدات الميزات تعتمد عليها. يضمن هذا الفصل أن تغيير SDK التحليلات لن يؤثر على طبقة الشبكة، والعكس صحيح. يتم إعادة استخدام الوحدات الأساسية عبر الميزات دون تكرار الكود.
الوحدات المشتركة تحتوي على كود تستخدمه ميزات متعددة: نماذج البيانات والأدوات والثوابت والعروض المخصصة. المشكلة الرئيسية للوحدات المشتركة هي خطر التحول إلى مزبلة (“وحدة متنوعة”) حيث يتراكم كود غير متجانس بمرور الوقت. القاعدة: يجب أن يكون للوحدة المشتركة موضوع واضح، على سبيل المثال “shared-ui” أو “shared-models”.
في Android، غالبًا ما يتم فصل الوحدات المشتركة في مكتبات ذات البادئة lib: lib-network, lib-database, lib-ui-components. في iOS، تؤدي Swift Packages الداخلية داخل Workspace نفس الوظيفة. في الممارسة العملية، تحد الفرق عدد الوحدات المشتركة إلى 3-5 لتجنب إنشاء شبكة تبعيات مفرطة تعقد البناء.
وحدات الاختبار المنفصلة تسمح بتشغيل الاختبارات فقط للوحدة المعدلة دون تشغيل مجموعة الاختبارات بأكملها. هذا يقلل وقت خط أنابيب CI/CD من ساعات إلى دقائق. يضمن العزل على مستوى الوحدة SoC على مستوى البناء: لا يمكن لوحدة طبقة الشبكة استيراد مكتبات واجهة المستخدم عن طريق الخطأ في اختباراتها.
يجب أن يكون لكل وحدة API عامة محددة بوضوح. في Android، يتم تحقيق ذلك من خلال معدّلات الوصول و api مقابل implementation في Gradle. في iOS، من خلال معدّلات الوصول public/internal والتبعيات المُدارة عبر Package.swift. تقليل الرؤية إلى الحد الأدنى الضروري هو ممارسة رئيسية للتصميم المعياري.
Gradle يدعم الهندسة المعيارية بشكل أصلي: كل وحدة هي وحدة بناء منفصلة مع ملف build.gradle الخاص بها. تستخدم مشاريع Android مزيجًا من وحدة التطبيق (app) والعديد من وحدات المكتبات. لا يمكن تشغيل وحدات المكتبات كتطبيق ولكن يمكن نشرها كـ AAR في مستودع.
ميزة رئيسية لـ Gradle هي البناء المتوازي للوحدات المستقلة. إذا كانت الوحدات A و B و C لا تعتمد على بعضها البعض، يقوم Gradle بتجميعها في وقت واحد باستخدام جميع نوى المعالج. في المشاريع التي تحتوي على 20+ وحدة، يقلل هذا البناء الكامل من 15 إلى 3-5 دقائق. تستغرق البناءات المتزايدة للوحدة المعدلة ثوانٍ.
يوفر Gradle نوعين من التبعيات بين الوحدات: api (متعدية) و implementation (غير متعدية). الفرق مهم بشكل حاسم للوحدات: implementation يخفي التبعيات المتعدية من مستهلكي الوحدة. إذا كانت وحدة :profile تستخدم :networking من خلال implementation، فإن مستهلكي :profile لا يعرفون عن :networking ولا يمكنهم الوصول إليه.
// settings.gradle — إعلان الوحدات
include ':app'
include ':feature:profile'
include ':feature:settings'
include ':core:network'
include ':core:database'
// build.gradle feature/profile — تبعيات الوحدة
dependencies {
implementation project(':core:network')
implementation project(':core:database')
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
}
يظهر الكود هيكل مشروع Android معياري. Settings.gradle يسرد جميع الوحدات، ويحدد build.gradle لكل وحدة ميزة فقط الوحدات الأساسية التي تحتاجها. يحل نظام البناء تلقائيًا التبعيات المتعدية ويبني الوحدات بالترتيب الصحيح.
Swift Package Manager (SPM) هو أداة الوحدات القياسية في iOS منذ 2019. يسمح SPM بتقسيم التطبيق إلى Swift Packages، يمكن أن يكون كل منها مكتبة أو ملفًا تنفيذيًا. يحدد Package الوحدات (targets) وتبعياتها من خلال Package.swift. SPM مدمج في Xcode ولا يتطلب أدوات إضافية.
CocoaPods يظل مدير التبعية الرئيسي للمكتبات الخارجية. يحدد Podfile و Podspec الهيكل المعياري، ويولد CocoaPods مساحة عمل مع مشاريع pod منفصلة. من أجل الوحدات في مشروعهم الخاص، تختار الفرق بشكل متزايد SPM لأنه مدمج في Xcode ولا يتطلب تثبيتًا.
في الوحدات في iOS، يلعب التحكم في الوصول دورًا مهمًا: public, package, internal, fileprivate و private. تنشر الوحدة فقط تلك الأنواع التي يجب أن تكون متاحة للوحدات الأخرى. يتم إخفاء تفاصيل التنفيذ الداخلية خلف معدّلات internal و private. هذا يمنع التبعيات المخفية بين الوحدات.
// Package.swift — الهيكل المعياري لمشروع iOS
let package = Package(
name: "MyApp",
platforms: [.iOS(SupportedPlatform.iOSVersion.v17)],
products: [
.library(name: "ProfileFeature", targets: ["ProfileFeature"]),
.library(name: "NetworkCore", targets: ["NetworkCore"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.0")
],
targets: [
.target(name: "ProfileFeature", dependencies: ["NetworkCore"]),
.target(name: "NetworkCore", dependencies: ["Alamofire"]),
]
)
Package.swift يعلن عن منتجين مكتبيين: ProfileFeature و NetworkCore. يعتمد ProfileFeature على NetworkCore لكنه لا يعرف بوجود Alamofire — فهو مخفي داخل NetworkCore. هذا العزل هو تطبيق مباشر لـ SoC على مستوى الوحدة: التغييرات في عميل HTTP لا تتطلب إعادة ترجمة ProfileFeature.
الميزة الرئيسية لـ الوحدات هي سرعة التطوير. تعمل الفرق بالتوازي على وحدات مختلفة دون تعارضات في الكود. يقوم خط أنابيب CI/CD ببناء الوحدات المعدلة فقط وتشغيل اختباراتها فقط. يقل وقت التغذية الراجعة ويزداد تواتر الإصدارات. نشرت Spotify و Uber و Airbnb دراسات حالة عن الانتقال إلى الهندسة المعيارية مع تحسينات في المقاييس بمقدار 2-3 مرات.
الميزة الثانية هي عزل الأخطاء. خطأ في وحدة Profile لا يؤثر على وحدة Payments إذا لم تكن هناك تبعيات مباشرة بينهما. هذا مهم بشكل خاص في التطبيقات ذات الوظائف عالية المخاطر (المدفوعات والبيانات الطبية)، حيث لا يجب أن يمنع خطأ في شاشة غير مرتبطة إصدار الوظائف الحيوية.
التحدي الرئيسي هو إدارة التبعيات. مع التصميم السيئ، يظهر رسم بياني للوحدات حيث يؤدي تغيير وحدة واحدة إلى إعادة بناء عشرات الوحدات الأخرى بشكل متسلسل. الحل هو اتباع قاعدة اللادورية: يجب أن يكون رسم بياني تبعيات الوحدات رسمًا بيانيًا موجهًا غير دوري (DAG). أدوات مثل Gradle Module Graph Assert تساعد في اكتشاف الدورات في وقت البناء.
التحدي الثاني هو زيادة وقت الإعداد الأولي. يتطلب إنشاء هندسة معيارية وقتًا أطول في مرحلة تهيئة المشروع. قد لا تستفيد المشاريع الصغيرة التي تضم 1-3 مطورين من الوحدات، مما يقضي وقتًا في الحفاظ على حدود الوحدات دون حاجة حقيقية للتوازي. الحل هو البدء بتطبيق متجانس واستخراج الوحدات مع نمو الفريق.
نهج feature-first يجمع الوحدات حسب الوظيفة: كل شاشة أو مجموعة شاشات تصبح وحدة منفصلة. نهج layer-first يقسم الكود حسب المعايير الفنية: وحدات منفصلة لواجهة المستخدم ومنطق الأعمال والبيانات. في الممارسة العملية، تختار معظم الفرق feature-first مع الوحدات الأساسية — وهذا يوفر عزلًا أفضل وتنقلًا واضحًا في المشروع.
الاختيار بين النهجين يعتمد على حجم الفريق وقابلية توقع الوظائف. إذا كنت تعرف بالضبط الشاشات التي ستحتويها، فإن feature-first يسمح لكل مطور بأن يكون مسؤولاً عن وحدته الخاصة. إذا تغيرت الوظائف بشكل متكرر وتداخلت بين الشاشات، فإن layer-first يوفر مرونة أكبر في إعادة استخدام الكود عبر ميزات مختلفة.
الأسئلة الشائعة
العدد الأمثل يعتمد على حجم المشروع والفريق. لفريق من 5 أشخاص، 6-10 وحدات كافية. لـ 20+ مطورًا، 20-40 وحدة. القاعدة: يجب أن تكون الوحدة صغيرة بما يكفي لفهم مطور واحد لها بالكامل، وكبيرة بما يكفي لعدم إنشاء شبكة تبعيات مفرطة.
الوحدات الصحيحة تسرع البناء من خلال التجميع المتوازي والتخزين المؤقت. لكن العدد المفرط من الوحدات ذات التبعيات الضيقة يبطئ البناء — يقضي Gradle و Xcode وقتًا في حل الرسم البياني. مفتاح البناء السريع هو تقليل التبعيات المتعدية والحفاظ على اللادورية.
نعم، ولكن بشكل تدريجي. ابدأ باستخراج الوحدات الأساسية (الشبكة، قاعدة البيانات)، ثم استخرج الميزات واحدة تلو الأخرى. استخدم feature flags لتمكين الكود المعياري الجديد جنبًا إلى جنب مع الكود المتجانس القديم. يستغرق الترحيل الكامل لتطبيق كبير من 3 إلى 12 شهرًا.
الوحدات هي وحدات تجميع داخل تطبيق واحد. الخدمات المصغرة هي عمليات منفصلة تعمل على خوادم مختلفة. الوحدات تقسم الكود، الخدمات المصغرة تقسم وقت التشغيل. في تطوير التطبيقات المحمولة، غالبًا ما يستخدم مصطلح “microapps” كخليط: وحدات ميزات يمكن تشغيلها كتطبيقات مستقلة.
لكل وحدة اختبارات وحدة خاصة بها تعمل بشكل مستقل. تتحقق اختبارات التكامل من التفاعل بين الوحدات. تغطي اختبارات واجهة المستخدم وحدات الميزات ببيانات وهمية. الهندسة المعيارية تبسط الاختبار: محاكاة تبعية وحدة أخرى أسهل من محاكاة جزء من تطبيق متجانس.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا