<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های Mahdi Sadeghi</title><link>https://virgool.ir/feed/@mahdisadeghiaghdam</link><description>Mobile Team Lead @ Mobillet</description><language>fa</language><pubDate>2026-11-11 10:11:15</pubDate><image><url>https://files.virgool.ir/upload/users/62340/avatar/IelR4w.jpg</url><title>Mahdi Sadeghi</title><link>https://virgool.ir/@mahdisadeghiaghdam</link></image><item><title>اتوماسیون قوانین معماری در Kotlin Multiplatform با Konsist</title><link>https://virgool.ir/@mahdisadeghiaghdam/اتوماسیون-قوانین-معماری-در-kotlin-multiplatform-با-konsist-pwhznf5hqpgh</link><description>چند ماه پیش، در حین بررسی یک Merge Request در پروژه موبایلت (اپلیکیشن موبایل بانک سامان)، متوجه شدم که برای چندمین بار در همان هفته دارم کامنتی مشابه می‌گذارم: «این فایل نباید مستقیماً از لایه Data ایمپورت کند.» در همان لحظه این سوال برایم پیش آمد که چرا باید وقت تیم صرف تکرار یک بررسی مکانیکی شود که می‌توان آن را خودکار کرد؟چالش پروژه‌های Multiplatform با معماری چندلایهپروژه موبایلت، یک اپلیکیشن Kotlin Multiplatform است که از معماری چندلایه استفاده می‌کند. این پروژه شامل چندین ماژول و تیمی متشکل از چند توسعه‌دهنده است. یکی از ویژگی‌های این پروژه این است که هر فیچر به‌صورت کامل ماژولار نشده و بخش‌های مختلف یک فیچر (presentation، domain، data) درون ماژول‌های مشترک قرار دارند.این ساختار مزایایی دارد—از جمله کاهش پیچیدگی مدیریت ماژول‌ها و سادگی تغییرات—اما چالشی نیز به همراه می‌آورد: نبود جداسازی فیزیکی لایه‌ها در سطح ماژول، باعث می‌شود که رعایت قوانین معماری به Discipline تیم وابسته باشد.در عمل، مسائلی که به‌طور مکرر در فرآیند Code Review مشاهده می‌شد عبارت بودند از:نقض Separation of Concerns: ایمپورت مستقیم از پکیج data به presentation، به دلیل اینکه هر دو در یک ماژول قرار دارندعدم رعایت مسئولیت لایه‌ها: استفاده از اصطلاحات رابط کاربری در ViewModel (مانند onLoginButtonClicked)ناهماهنگی در پیاده‌سازی Mapperها: روش‌های مختلف برای تبدیل Model به Entity یا DTOاین موارد به‌تنهایی بحران نبودند و معمولاً با یک کامنت در MR قابل‌رفع بودند، اما حجم زیاد این نوع نقض‌ها انرژی بررسی‌ها را از ارزیابی منطق کسب‌وکار و طراحی به موارد مکانیکی منحرف می‌کرد.راه‌حل: از قوانین ضمنی به تست‌های عینیاصل ساده‌ای وجود دارد: هر قانونی که یک انسان می‌تواند به‌صورت دستی بررسی کند، می‌تواند خودکار شود.در جست‌وجوی راهی برای خودکارسازی این بررسی‌ها، به Konsist رسیدم—یک کتابخانه Static Analysis برای Kotlin که امکان نوشتن تست برای قوانین معماری را فراهم می‌کند.Konsist چیست و چگونه کار می‌کند؟Konsist کد Kotlin را به‌عنوان داده می‌خواند و اجازه می‌دهد Assertionهای ساختاری روی آن اعمال کنید. به‌عبارت دیگر، شما می‌توانید بنویسید: «هیچ کلاسی در پکیج X نباید از پکیج Y چیزی ایمپورت کند» و Konsist این قانون را در هر بیلد بررسی می‌کند.معماری Konsist بر چهار مفهوم استوار است:Scope (محدوده): تعیین بخشی از کد که می‌خواهید بررسی کنید—می‌تواند کل پروژه، یک ماژول، یک پکیج یا حتی یک فایل باشد.Declarations (اعلان‌ها): انتخاب عناصری که می‌خواهید روی آن‌ها کار کنید—کلاس‌ها، توابع، پراپرتی‌ها، ایمپورت‌ها، و غیره.Filtering (فیلترینگ): محدود کردن موارد به آنچه که واقعاً مرتبط است.Assertion (اعمال قانون): تعریف شرطی که باید برقرار باشد.این چهار مرحله به‌صورت Fluent API در کنار هم قرار می‌گیرند و تست قابل‌خواندنی تولید می‌کنند.پیاده‌سازی‌های واقعی در موبایلتدر ادامه، سه مورد از قوانین معماری که در پروژه موبایلت پیاده‌سازی شده‌اند را به‌همراه توضیحات فنی بررسی می‌کنیم.۱. جلوگیری از نشت لایه Data به Presentationدر پروژه موبایلت، لایه‌ها در سطح پکیج جدا شده‌اند، نه ماژول. این یعنی اگر یک توسعه‌دهنده نخواهد، می‌تواند مستقیماً از data.repository یا data.datasource داخل یک presentation.viewmodel ایمپورت کند.این قانون تضمین می‌کند که چنین نقضی رخ نمی‌دهد:ArchitectureTest {&#xA;    @Test&#xA;    fun `presentation layer should not depend on data layer`() {&#xA;        Konsist&#xA;            .scopeFromProject()&#xA;            .files&#xA;            .filter { it.packagee?.fullyQualifiedName?.contains(&#34;.presentation&#34;) == true }&#xA;            .assertFalse { file -&gt;&#xA;                file.imports.any { import -&gt;&#xA;                    import.name.contains(&#34;.data.&#34;)&#xA;                }&#xA;            }&#xA;    }&#xA;}&#xA;توضیح فنی:scopeFromProject(): کل کدبیس را اسکن می‌کند.files: همه فایل‌های پروژه را برمی‌گرداند.filter { ... }: فقط فایل‌هایی که در پکیج presentation هستند را نگه می‌دارد.assertFalse { ... }: اطمینان می‌دهد که هیچ ایمپورتی شامل .data. نیست.این تست در هر بیلد اجرا می‌شود و اگر کسی سعی کند مستقیماً از لایه Data در Presentation استفاده کند، بیلد شکست می‌خورد.چرا این مهم است؟چون بخش‌های مختلف فیچرها ماژولار نشده‌اند، این تست به‌عنوان یک دیوار محافظ عمل می‌کند که از Coupling غیرضروری بین لایه‌ها جلوگیری می‌کند. بدون این تست، نقض این قانون تنها در مرحله Code Review قابل‌تشخیص است—که هم انسانی است و هم دیر.۲. محدودسازی اصطلاحات UI در ViewModelیکی از اصول مهم در معماری MVI این است که ViewModel نباید دانش مستقیمی از رابط کاربری داشته باشد. در عوض، باید با مفاهیم Intent (نیت کاربر) و State (وضعیت برنامه) کار کند.این قانون اطمینان می‌دهد که متدهای ViewModel از اصطلاحاتی مانند Clicked، Pressed یا On استفاده نمی‌کنند:@Test&#xA;fun `viewModel functions should not use UI terminology`() {&#xA;    val forbiddenTerms = listOf(&#34;Clicked&#34;, &#34;Pressed&#34;, &#34;Tapped&#34;, &#34;On&#34;)&#xA;&#xA;    Konsist&#xA;        .scopeFromProject()&#xA;        .classes()&#xA;        .filter { it.name.endsWith(&#34;ViewModel&#34;) }&#xA;        .functions()&#xA;        .assertFalse { function -&gt;&#xA;            forbiddenTerms.any { term -&gt; &#xA;                function.name.contains(term, ignoreCase = false) &#xA;            }&#xA;        }&#xA;}&#xA;توضیح فنی:classes(): همه کلاس‌های پروژه را برمی‌گرداند.filter { it.name.endsWith(&#34;ViewModel&#34;) }: فقط ViewModelها را نگه می‌دارد.functions(): همه متدهای public آن ViewModelها را برمی‌گرداند.assertFalse { ... }: اطمینان می‌دهد هیچ متدی شامل کلمات ممنوعه نیست.پیش و بعد:// ❌ قبل (نقض)&#xA;class LoginViewModel {&#xA;    fun onLoginButtonClicked() { ... }&#xA;}&#xA;&#xA;// ✅ بعد (صحیح)&#xA;class LoginViewModel {&#xA;    fun submitLogin() { ... }&#xA;}&#xA;این قانون باعث شد بحث‌های سلیقه‌ای («بهتر است این نام را تغییر دهیم») به یک معیار عینی تبدیل شود. دیگر نیازی نیست در هر PR این موضوع را توضیح دهیم—کد خودش قانون را تحمیل می‌کند.۳. استانداردسازی Mapperهادر پروژه موبایلت، Mapperها (تبدیل‌کننده‌های Model به Entity یا DTO) به‌صورت Extension Function پیاده‌سازی می‌شوند. اما بدون استاندارد، دو مشکل پیش می‌آمد:بعضی Mapperها پارامتر ورودی می‌گرفتند، بعضی نه.بعضی Mapperها داخل لایه domain تعریف می‌شدند که منطقاً نادرست است.این قانون هر دو موضوع را حل می‌کند:@Test&#xA;fun `mapper extensions should take no arguments and live outside domain`() {&#xA;    Konsist&#xA;        .scopeFromProject()&#xA;        .functions()&#xA;        .filter { &#xA;            it.name.startsWith(&#34;to&#34;) &amp;&amp; &#xA;            it.hasReceiverType() &#xA;        }&#xA;        .assertTrue { function -&gt;&#xA;            val hasNoParameters = function.parameters.isEmpty()&#xA;            val isOutsideDomain = function.packagee&#xA;                ?.fullyQualifiedName&#xA;                ?.contains(&#34;.domain&#34;) == false&#xA;&#xA;            hasNoParameters &amp;&amp; isOutsideDomain&#xA;        }&#xA;}&#xA;توضیح فنی:functions(): همه توابع پروژه.filter { it.name.startsWith(&#34;to&#34;) &amp;&amp; it.hasReceiverType() }: فقط Extension Functionهایی که با to شروع می‌شوند (مانند toEntity() یا toDto()).assertTrue { ... }: دو شرط را چک می‌کند:parameters.isEmpty(): نباید پارامتر ورودی داشته باشد.!contains(&#34;.domain&#34;): نباید در پکیج domain باشد.چرا این مهم است؟Consistency: همه Mapperها از یک الگو پیروی می‌کنند.Readability: خواندن کد ساده‌تر می‌شود، چون همیشه می‌دانید چگونه یک Model تبدیل می‌شود.Separation of Concerns: لایه domain نباید به جزئیات تبدیل داده وابسته باشد.یکپارچگی با CI/CDیکی از نکات کلیدی این است که این تست‌ها نباید فقط Local اجرا شوند. آن‌ها باید بخشی از Pipeline باشند تا هیچ MR بدون رعایت قوانین معماری Merge نشود.ما این تست‌ها را به‌عنوان یک Step مستقل در Pipeline موبایلت قرار دادیم:KtLint → Konsist (Architecture Tests) → Unit Tests → Buildمنطق این ترتیب:KtLint: ابتدا فرمت و Style کد بررسی می‌شود.Konsist: سپس ساختار و معماری کد چک می‌شود.Unit Tests: بعد رفتار کد آزمایش می‌شود.Build: در نهایت، فرآیند Build اجرا می‌شود.اگر معماری پروژه نقض شده باشد، اجرای مراحل بعدی منطقی نیست. این رویکرد Fail-Fast باعث می‌شود مشکلات زودتر شناسایی شوند.نکته عملی: زمان اجرای تست‌های Konsist بسته به اندازه پروژه متفاوت است. در پروژه موبایلت، اجرای کامل تست‌ها حدود ۱۵ ثانیه طول می‌کشد—که در مقایسه با ارزش آن، قابل‌قبول است.چالش‌ها و نکات عملیاستفاده از Konsist در موبایلت بدون چالش نبود. دو موضوعی که با آن مواجه شدیم:۱. مشکل Performance در پروژه‌های بزرگscopeFromProject() کل پروژه را اسکن می‌کند. در پروژه‌های بزرگ، این کار می‌تواند کند باشد. راه‌حل ما:// ❌ کند&#xA;Konsist.scopeFromProject()&#xA;&#xA;// ✅ سریع‌تر&#xA;Konsist.scopeFromModule(&#34;app&#34;)&#xA;Konsist.scopeFromPackage(&#34;com.example.feature&#34;)&#xA;با محدود کردن Scope به ماژول یا پکیج خاص، سرعت اجرای تست‌ها به‌طور قابل‌توجهی بهبود می‌یابد.۲. False Positives و نیاز به تنظیم دقیققوانین خیلی سخت‌گیرانه می‌توانند False Positive تولید کنند. مثلاً، قانون ممنوعیت On در نام متدهای ViewModel، موارد مشروعی مانند onCleared() (متد Lifecycle) را هم می‌گیرد.راه‌حل:kotlin.filter { !it.name.equals(&#34;onCleared&#34;) }یا استفاده از Regex دقیق‌تر:kotlin.filter { it.name.matches(Regex(&#34;on[A-Z].*Clicked&#34;)) }این نیازمند تنظیم تدریجی است. بهتر است با قوانین کلی شروع کنید و به‌مرور استثناها را اضافه کنید.تأثیر روی فرآیند توسعهپس از چند ماه استفاده از Konsist در پروژه موبایلت، تغییرات قابل‌توجهی در فرآیند توسعه مشاهده شد:۱. کاهش حجم کامنت‌های Code Reviewپیش از Konsist، بخش قابل‌توجهی از کامنت‌های PR مربوط به نقض قوانین معماری بود. حالا این موارد قبل از Push شناسایی می‌شوند.۲. تمرکز بیشتر روی منطق کسب‌وکاروقتی بخش مکانیکی بررسی‌ها خودکار شد، Code Review می‌تواند روی طراحی، الگوریتم‌ها، و کیفیت منطق تمرکز کند.۳. کاهش Onboarding Timeوقتی یک توسعه‌دهنده جدید به تیم موبایلت اضافه می‌شود، دیگر لازم نیست همه قوانین معماری را به‌صورت شفاهی توضیح دهیم. کد خودش قوانین را تحمیل می‌کند و Feedback فوری می‌دهد.۴. تبدیل قوانین ضمنی به مستندات زندهتست‌های Konsist به‌عنوان مستندات اجرایی عمل می‌کنند. هر کسی که بخواهد بفهمد چه قوانینی در پروژه رعایت می‌شوند، کافی است فایل تست را باز کند.امیدواریم این مقاله برای شما برنامه‌نویسان اندروید هم مفید بوده باشه. ما توی تیم موبایلت، همیشه مشتاق یادگیری و به اشتراک گذاشتن تجربه‌هامون هستیم. اگه سوالی دارید یا دوست دارید بیشتر در مورد این موضوع صحبت کنیم، حتماً توی بخش نظرات با ما در ارتباط باشید.در مقالات بعدی، به سراغ موضوعات جذاب دیگه‌ای از دنیای توسعه اندروید خواهیم رفت!</description><category>Mahdi Sadeghi</category><author>Mahdi Sadeghi</author><pubDate>Sat, 20 Jun 2026 15:40:37 +0330</pubDate></item><item><title>مدیریت وابستگی ها در موبایلت بانک سامان</title><link>https://virgool.ir/Rahkar/مدریریت-وابستگی-ها-در-موبایلت-بانک-سامان-l0lt4wy80yz6</link><description>سلام به همه برنامه‌نویسان اندروید عزیز! 👋این اولین نوشته از مجموعه مقالاتیه که ما در تیم شرکت راهکار، تصمیم گرفتیم تجربه‌ها و دستاوردهای فنی‌مون رو با شما به اشتراک بذاریم. ما توی راهکار، همیشه به دنبال راه‌هایی برای بهبود فرآیند توسعه و ارتقاء کیفیت پروژه‌هامون هستیم. هدفمون از این مقالات، ایجاد یک فضای دوستانه برای یادگیری و تبادل نظر بین برنامه‌نویسان اندروید ایرانه.لازم به ذکر هست که این مقاله، حاصل تلاش‌های ارزشمند تیم موبایل راهکار هست، و ما صمیمانه از نقش کلیدی همکار عزیزمون، آقای امیرحسین اسدپور، در توسعه و پیاده‌سازی این راهکار قدردانی می‌کنیم. این پلاگین‌ها توسط امیرحسین اسدپور توسعه داده شده است.توی این مقاله، می‌خوایم در مورد یکی از چالش‌های اساسی که توی پروژه‌های بزرگ و ماژولار اندروید باهاش دست و پنجه نرم کردیم صحبت کنیم: مدیریت وابستگی‌ها (Dependency Management). اگه شما هم تجربه کار با پروژه‌های چند ماژولی رو داشته باشید، حتماً می‌دونید که تکرار وابستگی‌ها توی ماژول‌های مختلف و انجام تنظیمات مشابه برای هر ماژول، چقدر می‌تونه دردسرساز باشه.تیم اندروید موبایلت برای حل این مشکل و داشتن یک ساختار منظم‌تر و کارآمدتر، یک راه حل خلاقانه رو پیاده‌سازی کرد: ساخت و استفاده از کانونشن پلاگین‌های Gradle اختصاصی. توی این مقاله، بهتون می‌گیم که چرا این تصمیم رو گرفتیم، چه مزایایی داشت، و چطور چند تا از پلاگین‌های کلیدی‌مون رو ساختیم.چرا مدیریت وابستگی‌ها توی پروژه‌های ماژولار مهمه؟قبل از اینکه بریم سراغ اصل مطلب و پلاگین‌هامون رو معرفی کنیم، بذارید یکم در مورد اهمیت مدیریت درست وابستگی‌ها توی پروژه‌های ماژولار صحبت کنیم. وقتی پروژه اندرویدتون از چند تا ماژول تشکیل شده (چه ماژول‌های Feature، چه Library و چه Dynamic Feature)، مدیریت وابستگی‌ها کم کم تبدیل میشه به یه چالش بزرگ.تصور کنید که یه سری کتابخونه پرکاربرد مثل androidx.core-ktx, appcompat, material یا کتابخونه‌های تست رو توی اکثر ماژول‌هاتون استفاده می‌کنید. اگه یه سیستم مدیریت متمرکز نداشته باشید، مجبورید این وابستگی‌ها رو به صورت جداگانه توی فایل build.gradle هر ماژول تعریف کنید. این کار کلی مشکل به وجود میاره:تکرار کد: هی یه وابستگی رو توی چند تا فایل build.gradle تعریف می‌کنید و این باعث میشه کدتون زیاد بشه و احتمال خطا هم بالا بره.ناسازگاری نسخه‌ها: ممکنه یهو یادتون بره یه کتابخونه رو توی یه ماژول آپدیت کنید و این باعث میشه نسخه‌های کتابخونه‌ها توی ماژول‌های مختلف با هم فرق داشته باشن و کلی مشکل عجیب و غریب موقع اجرا به وجود بیاد.شلوغ شدن فایل‌های build.gradle: فایل‌های build.gradleتون خیلی شلوغ میشن و دیگه خوندنشون سخت میشه.سختی به‌روزرسانی‌ها: اگه بخواید یه کتابخونه رو توی کل پروژه آپدیت کنید، باید کلی فایل build.gradle رو تغییر بدید که هم زمان‌بره و هم ممکنه یه جا یادتون بره و خرابکاری بشه.عدم انسجام توی تنظیمات: علاوه بر وابستگی‌ها، یه سری تنظیمات دیگه هم مثل compileSdk, minSdk, تنظیمات کامپایلر Kotlin و فعال کردن ViewBinding و Compose هستن که ممکنه توی ماژول‌های مختلف تکرار بشن.تاریخچه مدیریت وابستگی‌ها در Gradle قبل از Convention Pluginsقبل از ظهور Convention Plugins و Version Catalogs، توسعه‌دهندگان اندروید و Gradle از روش‌های مختلفی برای مدیریت وابستگی‌ها و تنظیمات استفاده می‌کردند که هر کدام چالش‌های خاص خود را داشتند.روش‌های پیشین:۱. تعریف مستقیم در هر build.gradle:ابتدایی‌ترین روش، تعریف مستقیم هر وابستگی در فایل build.gradle مربوط به هر ماژول بود. این روش برای پروژه‌های کوچک با چند ماژول قابل مدیریت بود، اما همانطور که در بالا اشاره شد، با بزرگ شدن پروژه و افزایش ماژول‌ها، به سرعت به کابوسی از کد تکراری، ناسازگاری نسخه‌ها و مشکلات به‌روزرسانی تبدیل می‌شد.مثال کد (قبل):// app/build.gradle.kts&#xA;plugins {&#xA;    id(&#34;com.android.application&#34;)&#xA;    kotlin(&#34;android&#34;)&#xA;    kotlin(&#34;kapt&#34;)&#xA;}&#xA;&#xA;android {&#xA;    compileSdk = 34&#xA;    defaultConfig {&#xA;        minSdk = 21&#xA;        targetSdk = 34&#xA;        // ...&#xA;    }&#xA;    buildFeatures {&#xA;        viewBinding = true&#xA;    }&#xA;    // ...&#xA;}&#xA;&#xA;dependencies {&#xA;    implementation(&#34;androidx.core:core-ktx:1.10.0&#34;)&#xA;    implementation(&#34;androidx.appcompat:appcompat:1.6.1&#34;)&#xA;    implementation(&#34;com.google.android.material:material:1.9.0&#34;)&#xA;    implementation(&#34;com.google.dagger:hilt-android:2.44&#34;)&#xA;    kapt(&#34;com.google.dagger:hilt-compiler:2.44&#34;)&#xA;    // ...&#xA;}&#xA;&#xA;// library_feature/build.gradle.kts&#xA;plugins {&#xA;    id(&#34;com.android.library&#34;)&#xA;    kotlin(&#34;android&#34;)&#xA;    kotlin(&#34;kapt&#34;)&#xA;}&#xA;&#xA;android {&#xA;    compileSdk = 34&#xA;    defaultConfig {&#xA;        minSdk = 21&#xA;        // ...&#xA;    }&#xA;    buildFeatures {&#xA;        viewBinding = true&#xA;    }&#xA;    // ...&#xA;}&#xA;&#xA;dependencies {&#xA;    implementation(&#34;androidx.core:core-ktx:1.10.0&#34;)&#xA;    implementation(&#34;com.google.dagger:hilt-android:2.44&#34;)&#xA;    kapt(&#34;com.google.dagger:hilt-compiler:2.44&#34;)&#xA;    // ...&#xA;}۲. استفاده از بلاکext در build.gradle روت:یکی از اولین تلاش‌ها برای متمرکز کردن نسخه‌ها، استفاده از بلاک ext در فایل build.gradle روت پروژه بود. در این روش، نسخه‌ها به عنوان متغیر تعریف می‌شدند و سپس در ماژول‌های مختلف به آن‌ها ارجاع داده می‌شد. این کار تا حدی مشکل ناسازگاری نسخه‌ها را حل می‌کرد، اما هنوز هم نیاز به تعریف وابستگی‌های کامل در هر ماژول وجود داشت.// build.gradle (Project level)&#xA;buildscript {&#xA;    ext {&#xA;        kotlin_version = &#34;1.9.0&#34;&#xA;        hilt_version = &#34;2.44&#34;&#xA;        // ...&#xA;    }&#xA;    // ...&#xA;}&#xA;&#xA;// app/build.gradle&#xA;dependencies {&#xA;    implementation(&#34;com.google.dagger:hilt-android:$hilt_version&#34;)&#xA;    kapt(&#34;com.google.dagger:hilt-compiler:$hilt_version&#34;)&#xA;}۳. استفاده از برای تعریف Constantها:با پیشرفت Gradle و نیاز به منطق پیچیده‌تر، بسیاری از پروژه‌ها شروع به استفاده از buildSrc کردند. این پوشه یک پروژه Gradle مستقل است که قبل از بقیه پروژه کامپایل می‌شود و به شما اجازه می‌دهد تا کد Kotlin یا Groovy بنویسید و Constantها و توابع کمکی را تعریف کنید. این روش به شما اجازه می‌داد تا لیست کامل وابستگی‌ها را به عنوان object یا class تعریف کنید و آن‌ها را در build.gradle ماژول‌ها استفاده کنید. این یک گام بزرگ رو به جلو بود، اما هنوز هم برای تنظیمات پیچیده یا اعمال پلاگین‌های متعدد نیاز به کپی‌پیست داشت.معرفی Convention Plugins و Version Catalogsاین Convention Plugins به عنوان راه حلی قدرتمند برای این مشکلات معرفی شدند. به طور دقیق Gradle در نسخه 6.0 (در سال 2019) مفهوم precompiled script plugins را معرفی کرد که اساس Convention Plugins امروزی است. هدف اصلی این قابلیت، حذف تکرار در فایل‌های build.gradle و متمرکز کردن منطق پیکربندی بود.با معرفی precompiled script plugins (که بعداً به Convention Plugins شهرت یافتند)، توسعه‌دهندگان می‌توانستند بخش‌های تکراری build.gradle را به پلاگین‌های مجزا تبدیل کنند. این پلاگین‌ها می‌توانستند هم وابستگی‌ها و هم تنظیمات Gradle را به صورت یکپارچه اعمال کنند.همچنین، با معرفی Version Catalogs در Gradle 7.0 (در سال 2021)، مدیریت نسخه‌های وابستگی‌ها حتی ساده‌تر و متمرکزتر شد. فایل libs.versions.toml که پیش‌تر به آن اشاره شد، بخشی از این سیستم است که به شما امکان می‌دهد تمام وابستگی‌ها، پلاگین‌ها و نسخه‌ها را در یک مکان واحد تعریف کنید.هدف اصلی Convention Plugins تنها مدیریت وابستگی‌ها نیست، بلکه متمرکز کردن هرگونه پیکربندی و منطق Gradle است که در چندین ماژول تکرار می‌شود. این شامل تنظیمات SDK، گزینه‌های کامپایلر، فعال‌سازی فیچرها (مانند ViewBinding یا Compose)، و حتی اعمال پلاگین‌های دیگر (مثل Hilt یا Lint) می‌شود. این ویژگی آن‌ها را به ابزاری قدرتمند برای ایجاد یکپارچگی و کاهش boilerplate code در پروژه‌های بزرگ و ماژولار تبدیل می‌کند.راهکار تیم موبایلت: پلاگین‌های Gradle اختصاصیبرای اینکه از شر این مشکلات خلاص بشیم، تیم اندروید موبایلت تصمیم گرفت یه راه حل اساسی برای خودمون پیدا کنیم. ما از قدرت Gradle استفاده کردیم و پلاگین‌های اختصاصی خودمون رو ساختیم. لازم به ذکره که ایده و الهام اصلی این پیاده‌سازی، از پروژه متن‌باز Now in Android گوگل گرفته شده . این پروژه، یک نمونه‌ عالی از پیاده‌سازی Convention Plugins رو داره. و ما با بررسی آن، این راهکار را برای نیازهای پروژه‌های خودمان پیاده سازی کردیمبا این کار تونستیم:وابستگی‌های مشترک رو به صورت متمرکز مدیریت کنیم: همه کتابخونه‌هایی که توی بیشتر ماژول‌هامون استفاده میشن رو یه جا تعریف کردیم و به راحتی توی ماژول‌های مختلف ازشون استفاده کردیم.یه سری تنظیمات پیش‌فرض و مشترک رو اعمال کنیم: تنظیماتی مثل نسخه‌های compileSdk و minSdk, تنظیمات کامپایلر Kotlin، و فعال کردن ViewBinding و Compose رو به صورت خودکار روی ماژول‌هامون اعمال کردیم.حجم فایل‌های build.gradle رو کم کنیم و خوانایی‌شون رو بالا ببریم: با استفاده از پلاگین‌ها، فایل‌های build.gradle ماژول‌هامون خیلی خلوت‌تر و تمیزتر شدن.به‌روزرسانی‌ها رو راحت‌تر کنیم: برای به‌روزرسانی یه کتابخونه یا تغییر یه تنظیم، فقط کافیه پلاگین‌ها رو تغییر بدیم و این تغییرات به صورت خودکار روی همه ماژول‌های مربوطه اعمال میشه.سرعت توسعه رو افزایش بدیم و خطاها رو کم کنیم: با حذف کارهای تکراری و داشتن تنظیمات یکسان، سرعت توسعه‌مون خیلی بیشتر شده و احتمال خطاها هم خیلی کمتر شده.نگاهی به پلاگین‌های ما:حالا بیاید چند تا از پلاگین‌های Gradle اختصاصی که توی تیم موبایلت ساختیم رو با هم ببینیم و نحوه کارکرد و مزایاشون رو بررسی کنیم. کدهایی که توی توضیحات قبلی به اشتراک گذاشتید، نمونه‌هایی از این پلاگین‌های قدرتمند هستن.1. AndroidApplicationConventionPlugin:این پلاگین برای پیکربندی ماژول‌های Application (همون ماژول اصلی اپلیکیشن) استفاده میشه. همونطور که توی کد می‌بینید:class AndroidApplicationConventionPlugin : Plugin&lt;Project&gt; {&#xA;    override fun apply(target: Project) {&#xA;        with(target) {&#xA;            with(pluginManager) {&#xA;                apply(libs.findPlugin(&#34;android-application&#34;).get().get().pluginId)&#xA;                apply(&#34;kotlin-android&#34;)&#xA;                apply(&#34;kotlin-kapt&#34;)&#xA;            }&#xA;            extensions.configure&lt;ApplicationExtension&gt; {&#xA;                configureKotlinAndroid(this)&#xA;                defaultConfig.targetSdk = 34&#xA;            }&#xA;        }&#xA;    }&#xA;}این پلاگین اول پلاگین‌های com.android.application، kotlin-android و kotlin-kapt رو اعمال می‌کنه. این کار باعث میشه که قابلیت‌های اصلی توسعه اپلیکیشن اندروید و پشتیبانی از کاتلین و KAPT به ماژول اضافه بشه. بعدش از طریق extensions.configure&lt;ApplicationExtension&gt;، تنظیمات مربوط به ApplicationExtension رو پیکربندی می‌کنه. اینجا، تابع configureKotlinAndroid (که توی فایل ir.mobillet.app تعریف شده) برای اعمال تنظیمات مشترک کاتلین و تنظیم targetSdk به 34 فراخوانی میشه.2. AndroidLibraryConventionPlugin:این پلاگین برای پیکربندی ماژول‌های Library (کتابخانه‌های اندروید) استفاده میشه:class AndroidLibraryConventionPlugin : Plugin&lt;Project&gt; {&#xA;    override fun apply(target: Project) {&#xA;        with(target) {&#xA;            with(pluginManager) {&#xA;                listOf(&#xA;                    &#34;androidLibrary&#34;,&#xA;                    &#34;kotlin-android-gradle&#34;&#xA;                )&#xA;                    .map { libs.findPlugin(it).get().get().pluginId }&#xA;                    .forEach { apply(it) }&#xA;                apply(&#34;mobillet.android.flavors&#34;)&#xA;                apply(&#34;kotlin-android&#34;)&#xA;                apply(&#34;kotlin-kapt&#34;)&#xA;                apply(&#34;kotlin-parcelize&#34;)&#xA;                apply(&#34;mobillet.android.lint&#34;)&#xA;                apply(&#34;mobillet.android.hilt&#34;)&#xA;            }&#xA;            extensions.configure&lt;LibraryExtension&gt; {&#xA;                configureKotlinAndroid(this)&#xA;                defaultConfig {&#xA;                    vectorDrawables.useSupportLibrary = true&#xA;                }&#xA;                @Suppress(&#34;UnstableApiUsage&#34;)&#xA;                testOptions {&#xA;                    unitTests.isReturnDefaultValues = true&#xA;                }&#xA;                buildFeatures {&#xA;                    viewBinding = true&#xA;                }&#xA;                buildTypes {&#xA;                    release {&#xA;                        isMinifyEnabled = true&#xA;                        proguardFiles(&#xA;                            getDefaultProguardFile(&#34;proguard-android-optimize.txt&#34;),&#xA;                            &#34;proguard-rules.pro&#34;&#xA;                        )&#xA;                    }&#xA;                }&#xA;            }&#xA;        }&#xA;    }&#xA;}این پلاگین یه عالمه پلاگین مورد نیاز برای یه ماژول Library رو اعمال می‌کنه، از جمله com.android.library، kotlin-android-gradle، پلاگین‌های مربوط به Flavorها (mobillet.android.flavors که اینجا جزییاتش نیست)، kotlin-android، kotlin-kapt، kotlin-parcelize، Lint (mobillet.android.lint) و Hilt (mobillet.android.hilt). بعدش تنظیمات مربوط به LibraryExtension رو پیکربندی می‌کنه:فراخوانی configureKotlinAndroid برای تنظیمات مشترک کاتلین.فعال کردن vectorDrawables.useSupportLibrary.تنظیم unitTests.isReturnDefaultValues برای تست‌های واحد.فعال کردن viewBinding.پیکربندی تنظیمات مربوط به buildTypes به خصوص برای Build Type release (فعال کردن Minify و تنظیم فایل‌های Proguard).3. AndroidHiltConventionPlugin:این پلاگین برای مدیریت وابستگی‌های مربوط به Hilt (Dependency Injection) توی ماژول‌ها استفاده میشه:class AndroidHiltConventionPlugin : Plugin&lt;Project&gt; {&#xA;    override fun apply(target: Project) {&#xA;        with(target) {&#xA;            with(pluginManager) {&#xA;                apply(libs.findPlugin(&#34;hilt-android&#34;).get().get().pluginId)&#xA;            }&#xA;            dependencies {&#xA;                &#34;implementation&#34;(libs.findLibrary(&#34;hilt-androidPart&#34;).get())&#xA;                &#34;kapt&#34;(libs.findLibrary(&#34;hilt-compiler&#34;).get())&#xA;            }&#xA;        }&#xA;    }&#xA;}این پلاگین به راحتی پلاگین dagger.hilt.android.plugin رو اعمال می‌کنه و بعدش وابستگی‌های مورد نیاز برای Hilt (hilt-android و hilt-compiler) رو با استفاده از libs.findLibrary() (که به libs.versions.toml اشاره داره) به ماژول اضافه می‌کنه. توجه کنید که hilt-compiler توی scope kapt اضافه شده.4. AndroidApplicationComposeConventionPlugin:این پلاگین برای پیکربندی ماژول‌هایی که از Jetpack Compose استفاده می‌کنند طراحی شده:class AndroidApplicationComposeConventionPlugin : Plugin&lt;Project&gt; {&#xA;    override fun apply(target: Project) {&#xA;        with(target) {&#xA;            try {&#xA;                extensions.configure&lt;LibraryExtension&gt; {&#xA;                    configureAndroidCompose(this)&#xA;                }&#xA;            } catch (e: UnknownDomainObjectException) {&#xA;                extensions.configure&lt;ApplicationExtension&gt; {&#xA;                    configureAndroidCompose(this)&#xA;                }&#xA;            }&#xA;        }&#xA;    }&#xA;}این پلاگین تلاش می‌کنه تا configureAndroidCompose رو روی LibraryExtension اعمال کنه و اگه اون وجود نداشت (که نشون میده ماژول Application هست)، اون رو روی ApplicationExtension اعمال می‌کنه. این نشون میده که تنظیمات Compose هم توی ماژول‌های Feature (که معمولاً Library هستن) و هم توی ماژول اصلی Application قابل استفاده هستن. تابع configureAndroidCompose (که توی فایل ir.mobillet.app تعریف شده) تنظیمات مربوط به Compose رو انجام میده.البته توی این مورد میتونیم به جای استفاده از try-catch، توی پلاگین جدا، یکی برای Application و دیگری برای Library بسازیم و هر کدوم رو در جای مورد نیازش استفاده کنیم.5. AndroidLintConventionPlugin:این پلاگین برای اعمال تنظیمات مربوط به Lint (ابزار آنالیز کد استاتیک) استفاده میشه:class AndroidLintConventionPlugin : Plugin&lt;Project&gt; {&#xA;    override fun apply(target: Project) {&#xA;        with(target) {&#xA;            with(pluginManager) {&#xA;                apply(libs.findPlugin(&#34;ktlint&#34;).get().get().pluginId)&#xA;            }&#xA;        }&#xA;    }&#xA;}این پلاگین به سادگی پلاگین ktlint رو اعمال می‌کنه، که به تیم اجازه میده استایل کد کاتلین رو توی کل پروژه به صورت یکنواخت حفظ کنه.مدیریت نسخه‌ها با libs.versions.toml:همونطور که توی کد پلاگین AndroidHiltConventionPlugin دیدید، ما از libs.findLibrary() برای دسترسی به وابستگی‌ها استفاده می‌کنیم. این متد از یه فایل به اسم libs.versions.toml میاد که توی ساختار build-logic پروژه تعریف شده. این فایل یه روش متمرکز و سازمان‌یافته برای مدیریت نسخه‌های کتابخونه‌ها و پلاگین‌ها توی کل پروژه فراهم می‌کنه.فایل dependencyResolutionManagement توی فایل settings.gradle.kts پروژه build-logic نحوه تعریف این کاتالوگ نسخه رو نشون میده:dependencyResolutionManagement {&#xA;    repositories {&#xA;        google()&#xA;        mavenCentral()&#xA;    }&#xA;    versionCatalogs {&#xA;        create(&#34;libs&#34;) {&#xA;            from(files(&#34;../gradle/libs.versions.toml&#34;))&#xA;        }&#xA;    }&#xA;}و فایل plugins توی build.gradle.kts پروژه build-logic نحوه تعریف Alias برای پلاگین‌ها رو نشون میده:plugins {&#xA;    alias(libs.plugins.android.application) apply false&#xA;// ... سایر پلاگین ها&#xA;}با استفاده از این سیستم، به راحتی میشه نسخه‌های کتابخونه‌ها و پلاگین‌ها رو یه جا به‌روزرسانی کرد و از ناسازگاری نسخه‌ها توی ماژول‌های مختلف جلوگیری کرد.چطوری از پلاگین‌ها توی ماژول‌هامون استفاده می‌کنیم؟برای استفاده از این پلاگین‌ها توی ماژول‌های مختلف پروژه اندروید، کافیه اون‌ها رو توی بخش plugins فایل build.gradle ماژول مورد نظر اعمال کنیم.مثال کد (بعد از استفاده از Convention Plugins):// app/build.gradle.kts&#xA;plugins {&#xA;    id(&#34;mobillet.android.application&#34;)&#xA;    id(&#34;mobillet.android.hilt&#34;)&#xA;    id(&#34;mobillet.android.compose&#34;) &#xA;    id(&#34;mobillet.android.lint&#34;) &#xA;}&#xA;&#xA;android {&#xA;    // دیگه نیازی به تنظیمات تکراری compileSdk, minSdk, targetSdk, viewBinding, etc. نیست!&#xA;    // همه این‌ها در Convention Plugin مدیریت شده‌اند.&#xA;}&#xA;&#xA;dependencies {&#xA;    // فقط وابستگی‌های خاص این ماژول که عمومی نیستند&#xA;    implementation(project(&#34;:feature:some_feature&#34;)) // وابستگی به ماژول فیچر&#xA;}&#xA;&#xA;// library_feature/build.gradle.kts&#xA;plugins {&#xA;    id(&#34;mobillet.android.library&#34;) &#xA;    id(&#34;mobillet.android.hilt&#34;) &#xA;    id(&#34;mobillet.android.compose&#34;) &#xA;    id(&#34;mobillet.android.lint&#34;)  // برای استفاده از Lint&#xA;}&#xA;&#xA;android {&#xA;    // دیگه نیازی به تنظیمات تکراری نیست!&#xA;}&#xA;&#xA;dependencies {&#xA;    // فقط وابستگی‌های خاص این ماژول&#xA;    implementation(libs.androidx.datastore.preferences) // مثال: یک وابستگی خاص این ماژول&#xA;}همونطور که می‌بینید، فایل build.gradle ماژول‌ها خیلی تمیزتر و متمرکز بر وابستگی‌ها و تنظیمات خاص خود ماژول شده.مزایای کلی استفاده از پلاگین‌های Gradle اختصاصی:افزایش سرعت بیلد: با کم شدن تنظیمات تکراری، Gradle می‌تونه سریع‌تر بیلد کنه.بهبود سازماندهی کد: فایل‌های build.gradle تمیزتر و خواناتر میشن و تمرکز روی منطق ماژول بیشتر میشه.افزایش قابلیت استفاده مجدد از کد: تنظیمات و وابستگی‌های مشترک به صورت یکپارچه مدیریت میشن.کاهش خطاهای انسانی: با حذف کارهای تکراری، احتمال خطا توی تنظیمات کم میشه.بهبود تجربه توسعه‌دهنده: برنامه‌نویس‌ها می‌تونن بیشتر روی کد تمرکز کنن و کمتر درگیر تنظیمات Gradle بشن.انسجام توی کل پروژه: خیالمون راحته که همه ماژول‌ها از نسخه‌های یکسان کتابخونه‌ها و تنظیمات مشابه استفاده می‌کنن.نتیجه‌گیری: سرمایه‌گذاری برای آینده‌ای بهترتصمیم تیم موبایلت برای ساخت پلاگین‌های Gradle اختصاصی، یه سرمایه‌گذاری ارزشمند برای بهبود کیفیت و کارایی فرآیند توسعه پروژه‌های اندرویدمون بوده. این رویکرد نه تنها مشکلات مدیریت وابستگی‌ها و پیکربندی‌های تکراری رو حل کرده، بلکه باعث افزایش سرعت توسعه، بهبود سازماندهی کد و کاهش خطاهای انسانی شده.امیدواریم این مقاله برای شما برنامه‌نویسان اندروید هم مفید بوده باشه و بهتون نشون بده که چطور میشه با استفاده از Gradle، مشکلات پیچیده رو به یه راه حل ساده و کارآمد تبدیل کرد. ما توی تیم موبایلت، همیشه مشتاق یادگیری و به اشتراک گذاشتن تجربه‌هامون هستیم. اگه سوالی دارید یا دوست دارید بیشتر در مورد این موضوع صحبت کنیم، حتماً توی بخش نظرات با ما در ارتباط باشید.در مقالات بعدی، به سراغ موضوعات جذاب دیگه‌ای از دنیای توسعه اندروید خواهیم رفت!</description><category>Mahdi Sadeghi</category><author>Mahdi Sadeghi</author><pubDate>Mon, 09 Jun 2025 14:54:02 +0330</pubDate></item><item><title>نکاتِ خیلی مهمِ کدنویسیِ تمیز در اندروید</title><link>https://virgool.ir/coderlife/نکاتِ-مهمِ-کدنویسیِ-تمیز-در-اندروید-alqonj0iis87</link><description>در برنامه نویسی اندروید یکی از نکات مهمی که نمیتونیم ازش غافل بشیم کدنویسی تمیزه. و بدون کد تمیز امکان نگهداری و توسعه اپلیکیشن سخت و حتی میتونه غیر ممکن باشه! برای همین یک سری نکات برای رعایت این اصول وجود داره که توی این مقاله بهش اشاره میکنیم (اینجا نمیخوام درباره کلین کد و اینا حرف بزنم. قضیه ساده تر و مهم تر از این حرفاست که اغلب رعایت نمیشه):1.Project guidelines1.1 Project structure پروژه ها باید از ساختار دستورالعمل های پروژه پیروی کنند که در این لینک (Android Gradle plugin user guide) تعریف شده. پروژه ribot Boilerplate یه منبع خوب برای یادگیری این مورده.1.2 Project structure 1.2.1 Class filesکلاس ها باید با قوانین UpperCamelCase نام گذاری بشوند.کلاس هایی که از کلاس کامپوننت اندروید ارث بری شدن باید به اسم همون کامپوننت ختم بشن. به عنوان مثال:SignInActivity, SignInFragment,ImageUploaderService,ChangePasswordDialog.1.2.2 Resources filesریسورس ها باید از قوانین lowercase_underscore پیروی کنند.1.2.2.1 Drawable filesقرارداد اسم گذاری برای برای drawable ها:قرارداد اسم گذاری برای برای icon ها(Android iconography guidelines):قرارداد اسم گذاری برای برای selector state ها:1.2.2.2 Layout filesلایوت ها باید با کامپوننت هاشون مچ بشن. مانند تصویر زیر:1.2.2.3 Menu filesمشابه بالا نام منو ها باید با نام کامپوننتش  مطابقت داشته باشه. به عنوان مثال اگه ما یک menu تعریف کنیم که توی UserActivity استفاده میشه پس اسمش باید activity_user.xml باشه.اینکه کلمه menu بخشی از اسم منو باشه روش درستی نیست چون که خودش داخل menu directory قرار داره پس مشخص شده که این فایلِ xml یک منو هست.1.2.2.4 Values filesریسورس فایل هایی که باید توی پوشه values قرار داشته باشنم. مانند:strings.xml, styles.xml, colors.xml, dimens.xml, attrs.xml2.Code guidelines2.1 Java language rules2.1.1 Don&#39;t ignore exceptionsهرگز استثناها رو به حال خودشون رها نکنید:void setServerPort(String value) {&#xA;    try {&#xA;        serverPort = Integer.parseInt(value);&#xA;    } catch (NumberFormatException e) { }&#xA;}همونطور که می بینید کد بالا به دلیل این که پیش بینی میکنیم هیچوقت وارد قسمت catch نمیشه اون رو خالی گذاشتیم! بهتره هیچوقت اینکار رو نکینم! راه درست و اصولی اینه که همه exception ها رو هندل کنیم و اونا رو به حال خودشون رها نکنیم.2.1.2 Don&#39;t catch generic exceptionکاری که تو کد پایین انجام شده اشتباهه:try {&#xA;    someComplicatedIOFunction();        // may throw IOException&#xA;    someComplicatedParsingFunction();   // may throw ParsingException&#xA;    someComplicatedSecurityFunction();  // may throw SecurityException&#xA;    // phew, made it all the way&#xA;} catch (Exception e) {                 // I&#39;ll just catch all exceptions&#xA;    handleError();                      // with one generic handler!&#xA;}این کار اصولی نیست که استثناها رو به شکل عمومی هندل کنیم. چون اینکار باعث میشه که exception هایی که هرگز انتظارش رو نداریم(شامل استثناهای زمان اجرا مثل: ClassCastException) در سطح اپلیکیشن اتفاق بیوفته. این به این معنیه که اگه کسی استثنایی اضافه کنه چون ما بهش گفتیم که در صورت بروز هر نوع خطایی به exception عمومی بره پس کامپایلر اون استثنارو هندل نمیکنه!برای فهم بهتره اینکه چرا نباید اینکار رو بکنیم میتونید به این لینک برید.2.1.3 Don&#39;t use finalizersدر اینجا finalizer هنگام اجرای زباله روبی (یا همون garbage collected خودمون) اجرای میشه در حالی که اینکار میتونه برای پاک کردن منابع اللخصوص منابع خارجی مفید باشه اما مشخص نیست اون چه زمانی صدا زده میشه(یا حتی اصلا صدا زده میشه یا نه؟!)اندرویید از finalizer ها استفاده نمیکنه. پس بجاش میتونیم با هندل کردن درست exception ها اینکار رو انجام بدیم. اما اگه واقعا نیاز به استفاده ازش داشتیم میتونیم یک تابع مانند ()close بنویسیم و با نوشتن یک دستورشرطی هر موقع که نیاز بود ازش استفاده کنیم.2.1.4 Fully qualify importsزمانی که میخوایم از کلاسی مثل Bar در پکیج foo استفاده کنیم دو راه داریم.1- راه بد: import foo.*;2-راه خوب:import foo.Bar;اگه به همه کلاس های foo نیاز نداریم پس بهتره فقط اونی که نیاز داریم رو import کنیم.2.2 Java style rules2.2.1 Fields definition and namingفیلد ها باید بالای فایل تعریف بشن و از قوانین اسم گذاری زیر پیروی کنند:اسم فیلد های پرایویت و غیر استاتیک باید با حرف m شروع بشن.اسم فیلد های پرایویت و استاتیک باید با حرف s شروع بشن.اسم بقیه فیلدها باید با حرف کوچیک شروع بشن.همه حروف فیلد های فاینال و استاتیک باید با حروف بزرگ نوشته بشه و با آندرلاین جدا بشه.مثال:public class MyClass {&#xA;    public static final int SOME_CONSTANT = 42;&#xA;    public int publicField;&#xA;    private static MyClass sSingleton;&#xA;    int mPackagePrivate;&#xA;    private int mPrivate;&#xA;    protected int mProtected;&#xA;}2.2.3 Treat acronyms as wordsبا کلمات اختصاری مثل حروف رفتار کنید. مثلا آیدی رو باید به شکل Id بنویسیم نه ID. مانند عکس زیر:2.2.4 Use spaces for indentationاز فضاها برای تو رفتگی ها استفاده کنید.4 فاصله برای بلاک هاif (x == 1) {&#xA;    x++;&#xA;}8 فاصله برای line wrap ها(فارسیش نمیدونم چی میشه:( )Instrument i =&#xA;        someLongthat, wouldNotFit, on, one, line);2.2.5 Use standard brace styleبراکت ها رو درست توی همون خط بزارید نه یک بعدش.class MyClass {&#xA;    int func() {&#xA;        if (something) {&#xA;            // ...&#xA;        } else if (somethingElse) {&#xA;            // ...&#xA;        } else {&#xA;            // ...&#xA;        }&#xA;    }&#xA;}استفاده از براکت ها ضروریه درصورتی که کد ما بیشتر از یک خط باشه.اگه کد از ماکسیمم طول خط کمتره استفاده از براکت ها ضروری نیست.کد خوب:if (condition) body();کد بد:if (condition)&#xA;    body();  // bad!2.2.6 Annotations2.2.6.1 Annotations practicesبرخی از annotation ها با توجه به استاندارد اندروید عبارت اند از:- هر زمان که میخوایم متدی در کلاس والد رو override کنیم باید از Override@ در بالای متدمون استفاده کنیم.- ممکنه گاهی متدما یک warning داشته باشه اما ما اطمینان داریم که هشدار برطرفه میشه در این مواقع میتونیم از SuppressWarnings@ استفاده میکنیم. این کار باعث میشه کامپایلر هشدار مربوط به اون متد رو نادیده بگیره.برای مطالعه بیشتر میتونید به این لینک مراجعه کنید.2.2.6.2 Annotations styleکلاس ها، متدها، سازنده ها:زمانی که annotation ها به یک کلاس، متد یا سازنده اضافه میشوند باید هر کدام در یک خط جداگانه نوشته بشوند./* This is the documentation block about the class */&#xA;@AnnotationA&#xA;@AnnotationB&#xA;public class MyAnnotatedClass { }فیلدها:زمانی که annotation ها به یک فیلد اضافه میشن باید در یک خط نوشته بشن مگر اینکه طول اونها از ماکسیمم طول خط بیشتر باشه.@Nullable @Mock DataManager mDataManager;2.2.7 Limit variable scopeدامنه متغیرهای محلی باید حداقل باشه. با انجام اینکار ما خوانایی و قابلیت نگهداری کدمون رو افزایش و احتمال بروز خطا رو کاهش میدیم. هر متغیر باید در محدوده بلاکی تعریف بشه که اون متغیر فقط در اون محدوده تاثیر گذاره.متغیر های محلی باید زمانی تعریف بشن که برای اولین بار استفاده میشن. همه متغیر های محلی زمانی که تعریف میشن باید دارای مقدار اولیه باشن. اگه اطلاع کافی برای مقداردهی اولیه یک متغیر محلی نداریم باید تا زمان تعریف کردن اون دست نگهداریم.2.2.8 Order import statementsترتیب import ها: ترتیب import ها رو خود اندروید استدیو رعایت میکنه پس لازم نیست نگران این مورد باشید اما دونستنش خالی از لطف نیست.ترتیب به این صورته:1 - import  های اندروید2 - import های third parties(com, junit, net, org)3 - import های java و javax4 - import های همون پروژه2.2.9 Logging guidelinesاستفاده از متد های لاگ برای شناسایی مشکلات در اختیار برنامه نویس ها قرار میگیره.Log.v(String tag, String msg) (verbose)Log.d(String tag, String msg) (debug)Log.i(String tag, String msg) (information)Log.w(String tag, String msg) (warning)Log.e(String tag, String msg) (error)یک قانون کلی وجود داره که ما از اسم کلاس ها به عنوان tag استفاده میکنیم و اون رو بالای یک کلاس به شکل static final تعریف میکنیم.public class MyClass {&#xA;    private static final String TAG = MyClass.class.getSimpleName();&#xA;&#xA;    public myMethod() {&#xA;        Log.e(TAG, &#34;My error message&#34;);&#xA;    }&#xA;}لاگ های VERBOSE و DEBUG باید هنگام ساخت نسخه نهایی غیر فعال بشن. حتی خوبه که بقیه لاگ ها هم غیرفعال بشن اما ممکنه که تشخیص بدید که استفاده از لاگ ها ممکنه برای کشف مشکلات مفید باشه که در اون صورت باید مطمئن بیشید که اطلاعاتی مثل آدرس ایمیل، آیدی و... نشت نکنه!برای اینکه لاگ هارو در نسخه دیباگ نشون بدیم:if (BuildConfig.DEBUG) Log.d(TAG, &#34;The value of x is &#34; + x);2.2.10 Class member orderingهیچ راه حل درستی برای ترتیب اعضای کلاس وجود نداره اما میتونیم از یک روش منطقی و ثابت برای خوانایی کدمون استفاده کنیم.ترتیبی که توصیه میشه به شکل زیره:ConstantsFieldsConstructorsOverride methods and callbacks (public or private)Public methodsPrivate methodsInner classes or interfacesبه عنوان مثال:public class MainActivity extends Activity {&#xA;    private static final String TAG = MainActivity.class.getSimpleName();&#xA;&#xA;    private String mTitle;&#xA;    private TextView mTextViewTitle;&#xA;&#xA;    @Override&#xA;    public void onCreate() {&#xA;        ...&#xA;    }&#xA;&#xA;    public void setTitle(String title) {&#xA;    &#x9;mTitle = title;&#xA;    }&#xA;&#xA;    private void setUpView() {&#xA;        ...&#xA;    }&#xA;&#xA;    static class AnInnerClass {&#xA;&#xA;    }&#xA;&#xA;}اگه کلاسمون از کامپوننت های اندروید مثل اکتیویتی و فرگمنت ارث بری میکنه و متد های لایف سایکل رو اورراید میکنیم ترتیب درست به شکل زیره:public class MainActivity extends Activity {&#xA;&#xA;&#x9;//Order matches Activity lifecycle&#xA;    @Override&#xA;    public void onCreate() {}&#xA;&#xA;    @Override&#xA;    public void () {}&#xA;&#xA;    @Override&#xA;    public void () {}&#xA;&#xA;    @Override&#xA;    public void onDestroy() {}&#xA;&#xA;}2.2.11 Parameter ordering in methodsتوی اندروید ممکنه ما متدی بنویسیم که از Context استفاده میکنه. در این صورت Context باید اولین پارامتر ورودی متدمون باشه.اگه متد ما نیاز به یک اینترفیس callback داره برعکسِ Context اون باید آخرین پارامتر ورودی متدمون باشه.مثال:// Context always goes first&#xA;public User loadUser(Context context, int userId);&#xA;&#xA;// Callbacks always go last&#xA;public void loadUserAsync(Context context, int userId, UserCallback callback);2.2.12 String constants, naming, and valuesالمان های زیادی در اندروید وجود داره که برای استفاده از اونها ممکنه مجبور بشیم که ثابت های رشته ای تعریف بکنیم. حتی در برنامه های کوچک. مانند SharedPreferences، Bundle یا Intent باید اونها رو به شکل final static تعریف کنیم. و نام اونها رو باید به شکل پیشوند بنویسیم. این پیشوند ها باید به شکل زیر باشند.مثال:// Note the value of the field is the same as the name to avoid duplication issues&#xA;static final String PREF_EMAIL = &#34;PREF_EMAIL&#34;&#xA;static final String BUNDLE_AGE = &#34;BUNDLE_AGE&#34;&#xA;static final String ARGUMENT_USER_ID = &#34;ARGUMENT_USER_ID&#34;&#xA;&#xA;// Intent-related items use full package name as value&#xA;static final String EXTRA_SURNAME = &#34;com.myapp.extras.EXTRA_SURNAME&#34;&#xA;static final String ACTION_OPEN_USER = &#34;com.myapp.action.ACTION_OPEN_USER&#34;2.2.13 Arguments in Fragments and Activitiesزمانی که میخوایم دیتایی رو ازطریق intent یا bundle به اکتیویتی یا فرگمنت منتقل کنیم باید کلیدها برای دو مقدار متفاوت از قوانین بالا پیروی کنه.زمانی که یک اکتیویتی یا فرگمنت انتظار یک آرگومان رو داره، در این صورت باید یک متد public static برای تسهیل ایجاد intent یا فرگمنت مناسب بنویسیم.در مورد اکتیویتی ها این متد با اسم ()getStartIntent نوشته میشه. مانند کد زیر:public static Intent getStartIntent(Context context, User user) {&#xA;&#x9;Intent intent = new Intent(context, ThisActivity.class);&#xA;&#x9;intent.putParcelableExtra(EXTRA_USER, user);&#xA;&#x9;return intent;&#xA;}در مورد فرگمنت ها این متد ()newInstance نام داره و مثل کد زیر نوشته میشه:public static UserFragment newInstance(User user) {&#xA;&#x9;UserFragment fragment = new UserFragment();&#xA;&#x9;Bundle args = new Bundle();&#xA;&#x9;args.putParcelable(ARGUMENT_USER, user);&#xA;&#x9;fragment.setArguments(args)&#xA;&#x9;return fragment;&#xA;}نکته اول: این متدها باید بالای کلاس و قبل از متد ()onCreate قرار بگیرند.نکته دوم: اگه از روش های بالا استفاده کردیم کلید ها باید به صورت private تعریف بشند چون دیگه بیرون از کلاس به اونها نیازی نیست.2.2.14 Line length limitطول خطوط کد نباید از 100 کاراکتر بیشتر بشه. اگه کد ما بیشتر از اینه پس باید با دو روش زیر تقسیم بشه.استفاده از یک متغیر محلی یا یک متد.(ترجیحا)تقیسم کردن یک خط به چند خط زیر هم.در این مورد دو تا استثنا وجود داره که ممکنه از 100 کارکتر بیشتر بشه: خطوطی که امکان تقسیم شدن رو ندارند. مانند URL ها طولانی در کامنت هاتعریف ها مربوط به import ها و package ها.2.2.14.1 Line-wrapping strategiesبرای توضیح دادن line_wrap یک فرمول دقیق و مشخصی وجود نداره. اما تعداد کمی قانون متداول وجود داره.Break at operatorsبرای شکستن خطوط طولانی میتونیم اون رو قبل از عملگر ها بشکنیم مثل کد زیر:int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne&#xA;        + theFinalOne;Assignment Operator Exceptionیک قانون دیگه وجود داره که در اون میتونیم این شکستن رو بعد از عملگر مساوی(=) انجام بدیم. مثل کد زیر:int longName =&#xA;        anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + theFinalOne;Method chain caseزمانی که چند متد به شکل زنجیر وار صدا زده میشن(در واقع وقتی داریم از builder ها استفاده میکنیم) میتونیم بعد از صدا زدن متد و قبل از کاراکتر (.) این شکستن رو انجام بدیم. مثل کد زیر:Picasso.with(context).load(&#34;http://ribot.co.uk/images/sexyjoe.jpg&#34;).into(imageView);Picasso.with(context)&#xA;        .load(&#34;http://ribot.co.uk/images/sexyjoe.jpg&#34;)&#xA;        .into(imageView);Long parameters caseوقتی یک متد پارامتر های زیادی داره و باعث طولانی تر شدن خط ما میشه ما باید در هر پارامتر و بعد از کاما(,) این شکستن رو انجام بدیم. مثل کد زیر:loadPicture(context, &#34;http://ribot.co.uk/images/sexyjoe.jpg&#34;, mImageViewProfilePicture);&#xA;loadPicture(context,&#xA;        &#34;http://ribot.co.uk/images/sexyjoe.jpg&#34;,&#xA;        mImageViewProfilePicture,&#xA;        clickListener);2.2.15 RxJava chains stylingدر استفاده از متدهای زنجیره ای rxjava باید قبل از نقطه(.) شکستن اتفاق بیوفته. مثل کد زیر:public Observable&lt;Location&gt; syncLocations() {&#xA;    return mDatabaseHelper.getAllLocations()&#xA;            .concatMap(new Func1&lt;Location, Observable&lt;? extends Location&gt;&gt;() {&#xA;                @Override&#xA;                 public Observable&lt;? extends Location&gt; call(Location location) {&#xA;                     return mRetrofitService.getLocation(location.id);&#xA;                 }&#xA;            })&#xA;            .retry(new Func2&lt;Integer, Throwable, Boolean&gt;() {&#xA;                 @Override&#xA;                 public Boolean call(Integer numRetries, Throwable throwable) {&#xA;                     return throwable instanceof RetrofitError;&#xA;                 }&#xA;            });&#xA;}2.3 XML style rules2.3.1 Use self closing tagsاگه یک المان در XML هیچ محتوایی در داخلش نداره پس باید اون رو در خودش ببندیم مانند کد زیر:کد خوب:&lt;TextView&#xA;&#x9;android:id=&#34;@+id/text_view_profile&#34;&#xA;&#x9;android:layout_width=&#34;wrap_content&#34;&#xA;&#x9;android:layout_height=&#34;wrap_content&#34; /&gt;کد بد:&lt;!-- Don\&#39;t do this! --&gt;&#xA;&lt;TextView&#xA;    android:id=&#34;@+id/text_view_profile&#34;&#xA;    android:layout_width=&#34;wrap_content&#34;&#xA;    android:layout_height=&#34;wrap_content&#34; &gt;&#xA;&lt;/TextView&gt;2.3.2 Resources namingآیدی ریسورس ها باید به شکل lowercase_underscore انجام بشه.2.3.2.1 ID namingآیدی باید به شکل پیشوند و با نام المان شروع بشه:مثال:&lt;ImageView&#xA;    android:id=&#34;@+id/image_profile&#34;&#xA;    android:layout_width=&#34;wrap_content&#34;&#xA;    android:layout_height=&#34;wrap_content&#34; /&gt;&#xA;&lt;menu&gt;&#xA;&#x9;&lt;item&#xA;        android:id=&#34;@+id/menu_done&#34;&#xA;        android:title=&#34;Done&#34; /&gt;&#xA;&lt;/menu&gt;2.3.2.2 Stringsنام رشته باید با یک پیشوند از بخشی که به اون تعلق داره شروع بشه. برای مثال: registration_email_hintیاregistration_name_hintاگه یک رشته به هیچ بخش مشخصی تعلق نداره پس باید از قوانین زیر پیروی کنه:2.3.2.3 Styles and Themesبرخلاف بقیه ریسورس ها استایل ها باید با قانون UpperCamelCase.نام گذاری بشن.2.3.3 Attributes orderingبه عنوان یک قاعده کلی باید سعی کنید attribute های مشابه رو با هم گروه بندی کنیم. یک راه خوب برای مرتب سازی اونها به شکل زیره:View IdStyleLayout width and layout heightOther layout attributes, sorted alphabeticallyRemaining attributes, sorted alphabetically2.4 Tests style rules2.4.1 Unit testsکلاسهای آزمون باید با نام کلاس مورد نظر مطابقت داشته باشند. مثلا اگه ما کلاس تستی داشته باشیم که مربوط به DataBaseHelper پس باید اسم کلاس تستمون DataBaseHelperTest باشه.متد تست با Test@ حاشیه نویسی میشه و باید با نام متدی که تست میشه شروع بشه و بعد پیشبینی و/یا رفتار مورد انتظار نوشته بشه.به مثال زیر نوجه کنید:Template: @Test void methodNamePreconditionExpectedBehaviour()Example: @Test void signInWithEmptyEmailFails()اگه اسم متد تست به اندازه کافی واضحه پس نیازی نیست به اضافه کردن پیشبینی و/یا رفتار مورد انتظار نیست.گاهی ممکنه کلاسی دارای متدهای بزرگ زیادی باشه، و اغلب نیازه که برای هر کدوم چندین تست نوشته بشه. در این صورت خوبه که کلاس های تست رو به چند کلاس مختلف تقسیم کنیم. برای مثال فرض کنید کلاسی به اسم DataManager وجود داره که دارای متدهای بزرگ زیادیه، پس ما میتونیم اونو به کلاس های تست DataManagerSignInTest ،DataManagerLoadUsersTest و ... تقسیم کنیم.2.4.2 Espresso testsمعمولا هر کلاس تست Espresso برای یک اکتیویتی نوشته شده پس نام اون باید با نام اکتیویتی تطبیق داشته باشه مثل: SignInActivityTestباید هنگام استفاده از Espressso هر متد رو در خط جداگانه ای نوشت.onView(withId(R.id.view))&#xA;        .perform(scrollTo())&#xA;        .check(matches(isDisplayed()))خب این مقاله تموم شد.امیدوارم که خوب ترجمه کرده باشم. لینک اصلی مقاله اینجاست و اینجاست.اگه جایی رو بد توضیح دادم یا اشتباه نوشتم ممنون میشم بهم بگین.</description><category>Mahdi Sadeghi</category><author>Mahdi Sadeghi</author><pubDate>Tue, 25 Aug 2020 13:45:35 +0430</pubDate></item></channel></rss>