محادثات كاسبرسكي موبايل # 1. متعددة وحدة

في أواخر فبراير ، أطلقنا تنسيقًا جديدًا لاجتماعات مطوري Android في محادثات Kaspersky Mobile . يتمثل الاختلاف الرئيسي عن التجمعات العادية في أنه بدلاً من مئات المستمعين والعروض التقديمية الجميلة ، اجتمع المطورين "المتمرسين" حول عدة مواضيع مختلفة لمناقشة موضوع واحد فقط: كيف يقومون بتطبيق متعدد الوحدات في طلباتهم ، وما هي المشاكل التي يواجهونها ، وكيفية حلها.



محتوى


  1. قبل التاريخ
  2. الوسطاء في HeadHunter. الكسندر بلينوف
  3. وحدات المجال تينكوف فلاديمير كوخانوف ، الكسندر جوكوف
  4. تحليل التأثير في Avito. يفغيني كريفوبوكوف ، ميخائيل يودين
  5. كما في Tinkoff قاموا بتقليل وقت التجميع للعلاقات العامة من أربعين دقيقة إلى أربع. فلاديمير كوخانوف
  6. روابط مفيدة


قبل الانتقال إلى المحتوى المباشر للاجتماع في مكتب Kaspersky Lab ، دعنا نتذكر من أين جاء التعديل لتقسيم التطبيق إلى وحدات (من الآن فصاعداً ، يُفهم أن الوحدة النمطية هي وحدة Gradle وليست خنجر ، ما لم يُنص على خلاف ذلك).


كان موضوع التعددية في أذهان مجتمع Android لسنوات. يمكن اعتبار أحد العناصر الأساسية تقريرًا صادرًا عن دينيس نيكليودوف في سان بطرسبرج في العام الماضي "Mobius". اقترح تقسيم التطبيق المتجانس ، الذي توقف منذ فترة طويلة ليكون عميلاً رقيقاً ، إلى وحدات لزيادة سرعة البناء.
رابط إلى التقرير: عرض تقديمي ، فيديو


ثم كان هناك تقرير لفلاديمير تاجاكوف من Yandex.Maps حول ربط الوحدات النمطية باستخدام خنجر. وبالتالي ، فإنها تحل مشكلة تخصيص مكون واحد من البطاقات لإعادة استخدامها في العديد من تطبيقات Yandex الأخرى.
رابط إلى التقرير: عرض تقديمي ، فيديو


كما لم تخل Kaspersky Lab عن الاتجاه: في سبتمبر ، كتب Evgeni Matsyuk مقالة حول كيفية توصيل الوحدات النمطية باستخدام Dagger وفي نفس الوقت بناء بنية متعددة الوحدات أفقياً ، دون نسيان اتباع مبادئ Clean Architecture عموديًا.
رابط لهذه المادة


وفي فصل الشتاء موبيوس كان هناك تقريران في وقت واحد. أولاً ، تحدث ألكساندر بلينوف عن التعدد في تطبيق HeadHunter باستخدام Toothpick كـ DI ، وبعده تحدث Artem Zinnatulin عن الألم من 800 وحدة في Lyft. بدأت ساشا في الحديث عن تعدد الوحدات كوسيلة لتحسين بنية التطبيق ، وليس فقط تسريع التجميع.
تقرير بلينوف: عرض تقديمي ، فيديو
تقرير زيناتولين: فيديو


لماذا بدأت المقال بأثر رجعي؟ أولاً ، سيساعدك ذلك على دراسة الموضوع بشكل أفضل إذا كنت تقرأ عن تعدد الوحدات لأول مرة. وثانياً ، بدأ أول خطاب في اجتماعنا بعرض صغير قدمه أليكسي كاليدا من شركة ستريم ، والذي أظهر كيف قسموا تطبيقهم إلى وحدات بناءً على مقال Zhenya (ويبدو أن بعض النقاط تشبهني في مقاربة فلاديمير).


كانت السمة الرئيسية لهذا النهج هي الارتباط بواجهة المستخدم: يتم توصيل كل وحدة كشاشة منفصلة - جزء يتم نقل التبعيات إليه من وحدة التطبيق الرئيسية ، بما في ذلك FragmentManager. أولاً ، حاول الزملاء تنفيذ تعدد الوحدات من خلال الحقن بالوكالة ، وهو ما اقترحته Zhenya في المقال. ولكن يبدو أن هذا النهج غالبًا: كانت هناك مشاكل عندما تعتمد إحدى الميزات على ميزة أخرى ، والتي بدورها تعتمد على الثالثة - كان علينا أن نكتب حاقن وكيل لكل وحدة ميزة. يسمح لك النهج القائم على مكونات واجهة المستخدم بعدم كتابة أي عن طريق الحقن ، مما يسمح بالتبعيات على مستوى تبعية الأجزاء المستهدفة.


القيود الرئيسية التي يتضمنها هذا التطبيق: يجب أن تكون الميزة جزءًا (أو عرضًا آخر) ؛ وجود شظايا متداخلة ، مما يؤدي إلى وعاء كبير. إذا كانت إحدى الميزات تنفذ ميزات أخرى ، فيجب إضافتها إلى خريطة التبعية ، والتي يتحقق منها Dagger عند تجميعها. عندما يكون هناك العديد من هذه الميزات ، تنشأ صعوبات في وقت ربط الرسم البياني التبعية.



بعد تقرير أليكسي ، أخذ ألكساندر بلينوف الكلمة. في رأيه ، سيكون التنفيذ المرتبط بـ UI مناسبًا لحاويات DI في Flutter. ثم تحولت المناقشة إلى نقاش متعدد الوحدات في HeadHunter. كان الغرض من تقسيمهم إلى وحدات هو إمكانية العزل المعماري للميزات وزيادة سرعة التجميع.


قبل التقسيم إلى وحدات ، من المهم التحضير. أولاً ، يمكنك إنشاء رسم بياني تبعية - على سبيل المثال ، باستخدام هذه الأداة . هذا سيساعد على عزل المكونات مع الحد الأدنى من التبعيات والتخلص من المكونات غير الضرورية (ختم). بعد ذلك فقط ، يمكن تحديد المكونات الأقل اتصالًا في الوحدات النمطية.


استذكر ألكساندر النقاط الرئيسية التي تحدث عنها بمزيد من التفصيل في موبيوس. تتمثل إحدى المهام المعقدة التي يجب أن تأخذها الهندسة المعمارية في الاعتبار في إعادة استخدام وحدة نمطية واحدة من أماكن مختلفة في التطبيق. في المثال مع تطبيق hh ، هذه هي وحدة استئناف ، والتي يجب أن تكون متاحة لوحدة قائمة الشواغر (VacanciesList) ، عندما يذهب المستخدم إلى السيرة الذاتية التي قدمها لهذا المنصب الشائع ، وإلى وحدة الاستجابة السلبية (التفاوض). من أجل الوضوح ، أعدت رسم الصورة التي صورتها ساشا على لوحة فليب.



تحتوي كل وحدة نمطية على كيانين رئيسيين: التبعيات - التبعيات التي تحتاجها هذه الوحدة ، و API - الطرق التي توفرها الوحدة النمطية للخارج إلى الوحدات النمطية الأخرى. يتم التواصل بين الوحدات من قبل الوسطاء ، وهي بنية مسطحة في وحدة التطبيق الرئيسية. كل ميزة لديها اختيار واحد. يتم تضمين الوسطاء أنفسهم في MediatorManager معينة في وحدة تطبيق المشروع. في الكود ، يبدو كالتالي:


object MediatorManager { val chatMediator: ChatMediator by lazy { ChatMediator() } val someMediator: ... } class TechSupportMediator { fun provideComponent(): SuppportComponent { val deps = object : SuppportComponentDependencies { override fun getInternalChat{ MediatorManager.rootMediator.api.openInternalChat() } } } } class SuppportComponent(val dependencies) { val api: SupportComponentApi = ... init { SupportDI.keeper.installComponent(this) } } interface SuppportComponentDependencies { fun getSmth() fun close() { scopeHolder.destroyCoordinator < -ref count } } 

وعد ألكساندر قريبًا بنشر مكون إضافي لإنشاء الوحدات النمطية في Android Studio ، والذي يُستخدم للتخلص من لصق النسخ في شركتهما ، فضلاً عن مثال لمشروع وحدة متعددة الوحدات.


بعض الحقائق عن النتائج الحالية لفصل وحدة تطبيق hh:


  • ~ 83 وحدات ميزة.
  • لإجراء اختبار A / B ، يمكن استبدال الميزات بالكامل بوحدة الميزات على مستوى الوسيط.
  • يوضح الرسم البياني لـ Gradle Scan أنه بعد تجميع الوحدات بالتوازي ، تتم عملية مطولة للتطبيق (في هذه الحالة ، اثنان: للباحثين عن عمل وأصحاب العمل):


أخذت الكلمة التالية من ألكساندر وفلاديمير من تينكوف:
مخطط بنية الوحدة النمطية الخاصة بهم يشبه هذا:


يتم تقسيم الوحدات النمطية إلى فئتين: الوحدات النمطية للميزات ووحدات المجال.
تحتوي وحدات الميزات على منطق العمل وميزات واجهة المستخدم. تعتمد على وحدات المجال ، لكن لا يمكنها الاعتماد على بعضها البعض.


تحتوي وحدات المجال على رمز للعمل مع مصادر البيانات ، أي بعض الطرز ، DAO (للعمل مع قاعدة البيانات) ، API (للعمل مع الشبكة) ومستودعات التخزين (ادمج عمل API و DAO). الوحدات النمطية للنطاق ، على عكس الوحدات النمطية للميزات ، يمكن أن تعتمد على بعضها البعض.


تتم العلاقة بين المجال ووحدات المعالم بالكامل داخل الوحدات النمطية للميزات (أي ، في مصطلحات hh ، يتم حل تبعيات Dependecies و API لوحدات المجال بالكامل في الوحدات النمطية للميزات التي تستخدمها ، دون استخدام كيانات إضافية مثل الوسطاء).


وأعقب ذلك سلسلة من الأسئلة ، والتي سأضعها دون تغيير تقريبًا هنا في تنسيق "الإجابة عن الأسئلة":


- كيف يتم التفويض؟ كيف يمكنك سحبها إلى وحدات الميزة؟
- لا تعتمد الميزات معنا على التفويض ، نظرًا لأن جميع إجراءات التطبيق تقريبًا تحدث في المنطقة المعتمدة.

- كيفية تتبع وتنظيف المكونات غير المستخدمة؟
- لدينا كيان مثل InjectorRefCount (يتم تنفيذه من خلال WeakHashMap) ، والذي عند حذف آخر نشاط (أو جزء) باستخدام هذا المكون ، يحذفه.

- كيفية قياس المسح الضوئي "النظيف" ووقت البناء؟ في حالة تشغيل ذاكرات التخزين المؤقت ، يتم الحصول على فحص قذر إلى حد ما.
- يمكنك تعطيل Gradle Cache (org.gradle.caching في gradle.properties).

- كيفية تشغيل اختبارات الوحدة من جميع الوحدات في وضع التصحيح؟ إذا قمت بإجراء اختبار المهارات فقط ، يتم سحب الاختبارات من جميع النكهات و buildType.
(أثار هذا السؤال مناقشة العديد من المشاركين في الاجتماع.)
- يمكنك محاولة تشغيل testDebug.
- ثم لن يتم تشديد الوحدات التي لا يوجد بها تكوين تصحيح. يبدأ إما أكثر من اللازم أو قليل جدًا.
- يمكنك كتابة مهمة Gradle ، والتي ستتجاوز testDebug لمثل هذه الوحدات ، أو قم بإجراء تكوين تصحيح مزيف في الوحدة النمطية build.gradle.
- يمكنك تنفيذ هذا النهج مثل هذا:

 withAndroidPlugin(project) { _, applicationExtension -> applicationExtension.testVariants.all { testVariant -> val testVariantSuffix = testVariant.testedVariant.name.capitalize() } } val task = project.tasks.register < SomeTask > ( "doSomeTask", SomeTask::class.java ) { task.dependsOn("${project.path}:taskName$testVariantSuffix") } 



تم تقديم العرض المرتقب التالي من قِبل Evgeny Krivobokov و Mikhail Yudin من Avito.
استخدموا mindmap لتصور قصتهم.


الآن مشروع الشركة يحتوي على أكثر من 300 وحدة ، 97 ٪ من قاعدة الشفرة مكتوبة في Kotlin. كان الغرض الرئيسي من التقسيم إلى وحدات هو تسريع عملية تجميع المشروع. حدث الانقسام إلى الوحدات النمطية تدريجياً ، مع تخصيص الأجزاء الأقل اعتمادًا من الشفرة للوحدات النمطية. للقيام بذلك ، تم تطوير أداة لتمييز تبعيات أكواد المصدر في الرسم البياني لتحليل التأثير ( تقرير حول تحليل التأثير في Avito ).


باستخدام هذه الأداة ، يمكنك وضع علامة على وحدة الميزات باعتبارها نهائية بحيث لا يمكن أن تعتمد عليها الوحدات النمطية الأخرى. سيتم فحص هذه الخاصية أثناء تحليل الأثر وتوفر تحديدًا للتبعيات الصريحة والاتفاقيات مع الفرق المسؤولة عن الوحدة. استنادًا إلى الرسم البياني الذي تم إنشاؤه ، يتم أيضًا فحص توزيع التغييرات لتشغيل اختبارات الوحدة للرمز المتأثر.


تستخدم الشركة مستودعًا أحاديًا ، ولكن لمصادر Android فقط. رمز المنصات الأخرى يعيش بشكل منفصل.


يستخدم Gradle لبناء المشروع (على الرغم من أن الزملاء يفكرون بالفعل في بناء مثل Buck أو Bazel الأكثر ملاءمة للمشاريع متعددة الوحدات). لقد جربوا بالفعل Kotlin DSL ، ثم عادوا إلى Groovy في البرامج النصية Gradle ، لأنه من غير المناسب دعم إصدارات مختلفة من Kotlin في Gradle وفي المشروع - يتم وضع المنطق العام في الإضافات.


يمكن لـ Gradle موازنة المهام وذاكرة التخزين المؤقت وعدم إعادة تجميع التبعيات الثنائية إذا لم يتغير ABI ، مما يضمن التجميع الأسرع لمشروع متعدد الوحدات. للتخزين المؤقت الأكثر كفاءة ، يتم استخدام Mainfraimer والعديد من الحلول المكتوبة ذاتيًا:


  • عند التبديل من فرع إلى فرع ، قد يترك Git مجلدات فارغة تقطع التخزين المؤقت ( قضية Gradle # 2463 ). لذلك ، يتم حذفها يدويًا باستخدام ربط Git.
  • إذا لم تتحكم في البيئة على أجهزة المطورين ، فإن الإصدارات المختلفة من Android SDK والمعلمات الأخرى يمكن أن تؤدي إلى انخفاض التخزين المؤقت. أثناء إنشاء المشروع ، يقارن البرنامج النصي معلمات البيئة مع المعلمات المتوقعة: إذا تم تثبيت الإصدارات أو المعلمات غير الصحيحة ، فسوف تسقط البنية.
  • تحليلات يجري على / قبالة المعلمات والبيئة. هذا لرصد ومساعدة المطورين.
  • كما يتم إرسال أخطاء الإنشاء إلى التحليلات. يتم إدخال المشاكل المعروفة والشعبية على صفحة خاصة مع حل.

كل هذا يساعد على تحقيق 15 ٪ من ذاكرة التخزين المؤقت تفوت على CI و 60-80 ٪ محليا.


يمكن أن تكون نصائح Gradle التالية مفيدة أيضًا في حالة ظهور عدد كبير من الوحدات النمطية في مشروعك:


  • تعطيل الوحدات النمطية عبر علامات IDE غير مريح ؛ يمكن إعادة تعيين هذه العلامات. لذلك ، يتم تعطيل الوحدات النمطية من خلال settings.gradle.
  • في الاستوديو 3.3.1 ، يوجد مربع اختيار "تخطي إنشاء مصدر في مزامنة Gradle إذا كان المشروع يحتوي على أكثر من وحدة واحدة". بشكل افتراضي ، يتم إيقاف تشغيله ، من الأفضل تشغيله.
  • يتم تسجيل التبعيات في buildSrc لإعادة استخدامها في جميع الوحدات. هناك خيار آخر وهو Plugins DSL ، ولكن بعد ذلك لا يمكنك وضع تطبيق المكون الإضافي في ملف منفصل.


انتهى اجتماعنا مع فلاديمير من Tinkoff بعنوان clickbait للتقرير ، "كيفية تقليل التجميع في العلاقات العامة من 40 دقيقة إلى أربعة" . في الواقع ، كنا نتحدث عن توزيع بدايات سدادات الدرجات: تصميمات apk والاختبارات والمحللات الثابتة.


في البداية ، أجرى الرجال في كل طلب سحب تحليل ثابت ، مباشرة التجميع والاختبارات. استغرقت هذه العملية 40 دقيقة ، استغرق منها فقط Lint و SonarQube 25 وسقطت 7٪ فقط من عمليات الإطلاق.


وبالتالي ، تقرر وضع الإطلاق في وظيفة منفصلة ، والتي تعمل وفق جدول زمني كل ساعتين ، وفي حالة وجود خطأ ، ترسل رسالة إلى Slack.


كان الموقف المعاكس يستخدم الكشف. تحطمت بشكل مستمر تقريبًا ، وهذا هو السبب في أنه تم وضعه في فحص ما قبل الدفع الأولي.


لذلك ، بقيت اختبارات التجميع والوحدة apk فقط في التحقق من طلب السحب. تجمع الاختبارات المصادر قبل التشغيل ، لكن لا تجمع الموارد. منذ أن نجح دمج الموارد دائمًا تقريبًا ، تم التخلي عن تجميع apk نفسه أيضًا.


كنتيجة لذلك ، لم يتم إطلاق اختبارات الوحدة إلا بناءً على طلب السحب ، مما سمح لنا بتحقيق الدقائق الأربع المحددة. يتم تنفيذ apk بناء على طلب سحب الاندماج في ديف.



على الرغم من حقيقة أن الاجتماع استغرق ما يقرب من 4 ساعات ، لم نتمكن من مناقشة القضية الملحة المتمثلة في تنظيم الملاحة في مشروع متعدد الوحدات. ربما هذا هو موضوع محادثات Kaspersky Mobile Talks التالية. علاوة على ذلك ، أحب المشاركون التنسيق حقًا. أخبرنا بما تريد التحدث عنه في الاستطلاع أو في التعليقات.


وأخيرًا ، روابط مفيدة من الدردشة نفسها:


Source: https://habr.com/ru/post/ar446626/


All Articles