Code Smell — نشانه سطحی در کد است که درباره مشکل بالقوه در طراحی یا معماری برنامه هشدار میدهد. این اصطلاح را کنت بک معرفی کرد و مارتین فاولر در کتاب «Refactoring: Improving the Design of Existing Code» آن را رایج ساخت. به گفته Martin Fowler، بوی کد لزوماً به معنی باگ نیست، اما تقریباً همیشه به نیاز به بازآرایی برای بهبود قابلیت نگهداری اشاره دارد.
نکات اصلی
Code Smell (بوی کد) — استعارهای برای نشانههایی در کد منبع است که با احتمال زیاد به مشکلات عمیقتر اشاره دارند. خود این اصطلاح تعریف رسمی ندارد — این یک اکتشافی مبتنی بر تجربه برنامهنویسان است. مارتین فاولر و کنت بک در سال 1999 برای اولین بار 22 بو را در کتاب «Refactoring» سیستماتیک کردند و بیشتر آنها پس از دهها هنوز هم مرتبط هستند.
درک تفاوت بین Code Smell و باگ مهم است. بو خطا نیست: کد کامپایل میشود، کار میکند و نتیجه صحیح میدهد. مشکل این است که خواندن، تغییر و آزمایش چنین کدی دشوار است. با گذشت زمان هزینه هر تغییر افزایش مییابد و اطمینان از صحت بازآرایی کاهش مییابد. ابزارهای تحلیل ایستا (SonarQube, Detekt, SwiftLint) بسیاری از بوها را به طور خودکار شناسایی میکنند.
ماهیت اکتشافی Code Smell به این معنی است که هر روش طولانی نباید تقسیم شود و هر کلاس بزرگ نیازی به بازآرایی ندارد. تصمیم را برنامهنویس میگیرد و زمینه را ارزیابی میکند: فراوانی تغییرات، بحرانی بودن ماژول، برنامههای توسعه. مهندسان با تجربه بو را به طور شهودی حس میکنند — کد «بوی نامطبوعی» دارد، اگرچه رسماً همه قوانین رعایت شدهاند.
فاولر 22 بو را مشخص کرد که به چند دسته تقسیم میشوند. برای توسعه موبایل، بوهای ساختاری، بوهای طراحی شیگرا و مشکلات خاص مرتبط با محدودیتهای پلتفرم بیشترین اهمیت را دارند. بیایید هر گروه را با مثالهایی از عمل واقعی بررسی کنیم.
Long Method (روش طولانی) — رایجترین بو در برنامههای موبایل. صفحه با فرم ثبتنام اغلب شامل یک روش setupUI با 200+ خط است که تمام Viewها را ایجاد میکند، محدودیتها را تنظیم میکند، در رویدادها مشترک میشود و خطاها را مدیریت میکند. راهحل: تقسیم به روشها بر اساس بلوکهای منطقی — configureEmailField, configurePasswordField, setupConstraints, bindViewModel.
Large Class (کلاس بزرگ) — Activity یا ViewController که هم مسئول نمایش، هم ناوبری، هم منطق تجاری و هم ارتباطات شبکهای است. چنین کلاسی اصل Single Responsibility Principle را نقض میکند و دهها فیلد و روش دارد. در Android این اغلب Fragment با 1000+ خط است که منطق صفحههای مختلف را شامل میشود. راهحل: جدا کردن presenter/ViewModel، انتقال کار شبکه به مخزن، ناوبری به هماهنگکننده.
Duplicate Code (تکرار کد) — کپی کردن بلوکهای مشابه در بخشهای مختلف برنامه. مثال معمول: دو صفحه که کارت محصول را نمایش میدهند — در کاتالوگ و در علاقهمندیها. اگر منطق نمایش کپی شده باشد، رفع باگ در یک جا آن را در جای دیگر درست نمیکند. راهحل: انتقال منطق مشترک به یک جزء قابل استفاده مجدد یا توسعه.
Feature Envy (حسادت به کلاس دیگر) — روش یک کلاس به طور فشرده از دادههای کلاس دیگر استفاده میکند. در Android این زمانی خود را نشان میدهد که ViewModel مستقیماً به فیلدهای مدل User دسترسی پیدا میکند به جای اینکه متد مدل را فراخوانی کند. نشانه: اگر میتوان روش را به کلاسی که دادههایش را استفاده میکند منتقل کرد — منتقل کنید. Switch Statements (زنجیرههای شرطی) — ساختار switch یا زنجیره if-else که نوع شیء را بررسی میکند. به جای آن باید از چندریختی یا الگوی strategy استفاده کرد.
Data Class — کلاسی که فقط داده ذخیره میکند اما رفتار ندارد. خود data class (در Kotlin) یا ساختار (در Swift) بو نیستند. مشکل زمانی ایجاد میشود که منطق تجاری کار با این دادهها به جای کپسوله شدن در سراسر پایگاه کد پخش شده است. Refused Bequest — وراث بیشتر روشهای والد را استفاده نمیکند و آنها را با پوستههای خالی بازنویسی میکند. نشانه وراثت نادرست: وراثت را با ترکیب جایگزین کنید.
God Activity / God Fragment — Activity یا Fragment که همه چیز را میداند: درباره چرخه حیات، دادهها، ناوبری، مجوزها، DI. این گرانترین کلاس برنامه از نظر نگهداری است. راهحل: الگوهای معماری MVVM, MVI یا Clean Architecture مسئولیت را تقسیم میکنند. Giant ViewController — مشابه برای iOS، جایی که UIViewController تمام منطق صفحه را شامل میشود و اغلب از 500 خط فراتر میرود.
Hardcoded Resources — رشتهها، رنگها، اندازهها، URLهای API مستقیماً در کد جاسازی شدهاند. در Android این استفاده از سیستم منابع R را نقض میکند، در iOS — NSLocalizedString و Asset Catalog را. رفع: تمام رشتهها را به strings.xml یا Localizable.strings، URLها را به فایل پیکربندی، اندازهها را به dimens منتقل کنید. Leaking Context — نگه داشتن مرجع به Activity یا ViewController بیشتر از عمر خود جزء. منجر به نشت حافظه و کرش میشود. راهحل: مراجع ضعیف، Jetpack Lifecycle, RxSwift DisposeBag.
| بو | کجا یافت میشود | راهحل |
|---|---|---|
| Long Method | Android/iOS | Extract Method, تقسیم |
| Large Class | Activity, ViewController | MVVM, VIPER, Clean Arch |
| Duplicate Code | هر صفحه | Shared Component, DRY |
| Feature Envy | ViewModel, Presenter | Move Method |
| Leaking Context | Android | اجزای آگاه از چرخه حیات |
Code review — مطمئنترین راه تشخیص بوها. چشم انسان ساختارهای غیرطبیعی را میبیند که تحلیلگرهای خودکار از دست میدهند. اثربخشی بازبینی کد افزایش مییابد اگر تیم از چکلیست بوهای معمول استفاده کند. توصیه میشود در یک جلسه بیش از 200–400 خط کد بررسی نشود — بعد از این حد توجه کاهش مییابد و بوها شروع به فرار میکنند.
تحلیل ایستا جستجوی بوهای ساختاری را خودکار میکند. برای Android ابزار استاندارد Detekt (Kotlin) و Android Lint، برای iOS — SwiftLint و SonarQube است. این ابزارها روشهای طولانی، کلاسهای بزرگ، تکرار کد و بسیاری مشکلات دیگر را پیدا میکنند. مهم است که قوانین را برای پروژه پیکربندی کنید — پیکربندیهای پیشفرض اغلب خیلی سختگیر هستند یا برعکس، بوهای بحرانی را از دست میدهند.
معیارهای کد معیارهای عینی میدهند: Cyclomatic Complexity (آستانه >10 نیاز به توجه دارد), Lines of Code per Method (آستانه >30), Depth of Inheritance (>3 — دلیلی برای تفکر). ابزارهایی مانند CodeMetrics (Xcode) و Gradle Metrics Plugin نمودارهای تغییر معیارها را در طول زمان میسازند. اگر پیچیدگی روش بعد از آخرین commit از 5 به 15 افزایش یافته است — این سیگنالی برای بازآرایی است.
// مثال: روش با پیچیدگی Cyclomatic = 7 (بالاتر از آستانه 5)
fun processOrder(order: Order) {
if (order.status == Status.NEW) { /* 10 خط */ }
else if (order.status == Status.PAID) { /* 15 خط */ }
else if (order.status == Status.SHIPPED) { /* 20 خط */ }
else if (order.status == Status.DELIVERED) { /* 8 خط */ }
else if (order.status == Status.CANCELLED) { /* 5 خط */ }
else { throw IllegalStateException() }
}
// رفع: چندریختی به جای switch
interface OrderHandler {
fun handle(order: Order)
}
جستجوی خودکار بوها جایگزین بازبینی کد نمیشود: تحلیلگرهای ایستا فقط مشکلات ساختاری را پیدا میکنند اما بوهای معنایی (Feature Envy, Inappropriate Intimacy) را تشخیص نمیدهند. ترکیب ابزارهای خودکار و کنترل انسانی بهترین نتیجه را میدهد. خط لوله CI/CD را طوری پیکربندی کنید که ساخت هنگام تجاوز از آستانههای پیچیدگی یا طول روش شکست بخورد.
بازآرایی — روش اصلی رفع بوهای کد. فاولر دهها تکنیک بازآرایی را توصیف میکند که هر کدام برای بوی خاصی قابل استفاده است. Extract Method — برای روشهای طولانی، Extract Class — برای کلاسهای بزرگ، Move Method — برای Feature Envy. مهم است که بازآرایی را با گامهای کوچک انجام دهید و بعد از هر تغییر کارایی کد را حفظ کنید.
تست قبل از بازآرایی — شرط اجباری. اگر کد با تستهای واحد پوشش داده نشده باشد، بازآرایی به بازنویسی با نتیجه نامشخص تبدیل میشود. برای کد legacy که تست ندارد، از Characterisation Tests استفاده کنید — تستهایی بنویسید که رفتار فعلی را ثبت میکنند، سپس بازآرایی کنید. آزمایش اطمینان میدهد که بعد از بازآرایی منطق تجاری خراب نشده است.
تدریجی بودن — کلید موفقیت در رفع بوها در توسعه موبایل. سعی نکنید God Activity را به طور کامل بازنویسی کنید. ابتدا لایه ناوبری، سپس لایه داده، سپس منطق نمایش را جدا کنید. هر گام را با commit و اجرای تست همراه کنید. از feature toggle استفاده کنید تا بازآرایی را برای بخشی از کاربران فعال کنید و در صورت مشکل بازگردانید.
ابزارهای IDE بسیاری از تکنیکهای بازآرایی را خودکار میکنند. Android Studio و IntelliJ IDEA بازآراییهای داخلی ارائه میدهند: Extract Method, Extract Interface, Pull Members Up, Encapsulate Fields. Xcode (از نسخه 14) پشتیبانی از بازآرایی برای Swift را بهبود بخشیده است. استفاده از بازآراییهای خودکار خطر خطاها را در مقایسه با کپی دستی کد کاهش میدهد.
توسعه موبایل بوهای خاص خود را مرتبط با محدودیتهای پلتفرم اضافه میکند. در Android این نشت Context, Cursor بسته نشده، استفاده نادرست از Lifecycle است. در iOS — retain cycle از طریق closureها، کار نادرست با Auto Layout, ViewControllerهای غولپیکر. این بوها نه تنها قابلیت نگهداری را بدتر میکنند، بلکه مستقیماً بر عملکرد و پایداری برنامه تأثیر میگذارند.
Callback Hell — بوی مشخص برای کد کار با عملیات ناهمزمان. callbackهای تودرتو (callback inside callback) کد را غیرقابل خواندن و اشکالزدایی را دشوار میکند. راهحل: کوروتین (Kotlin), async/await (Swift 5.5+), RxJava/RxSwift یا Combine. بر اساس Google I/O 2023، پروژههایی که از سبک callback به کوروتین منتقل شدند، تعداد باگها را 30% کاهش داده و افزودن ویژگیهای جدید را تسریع میکنند.
Platform Coupling — اتصال سفت و سخت منطق تجاری به اجزای پلتفرم. آزمایش چنین منطقی نیاز به راهاندازی شبیهساز دارد که چرخه بازخورد را کند میکند. رفع: Clean Architecture کد را به لایههای Domain (Kotlin/Swift خالص بدون وابستگی به پلتفرم) و Data/UI (با وابستگیهای پلتفرمی) تقسیم میکند. منطق تجاری روی JVM بدون شبیهساز آزمایش میشود.
پرسشهای متداول
خیر — Code Smell یک خطا نیست. کد با بو به درستی کار میکند، اما نگهداری، تغییر و آزمایش آن دشوار است. باگ — رفتار نادرست، بو — هشدار درباره مشکلات بالقوه در آینده.
22 بو در ویرایش دوم کتاب «Refactoring» (2019). از جمله Long Method, Large Class, Primitive Obsession, Data Clumps, Switch Statements, Speculative Generality و دیگران. جامعه دهها بوی جدید برای پارادایمها و پلتفرمهای مدرن اضافه کرده است.
ترکیب بهترین نتیجه را میدهد: Detekt (Android/Kotlin), SwiftLint (iOS), SonarQube (هر دو) برای تحلیل خودکار و بازبینی کد برای بوهای معنایی. هیچ ابزاری 100% مشکلات را پیدا نمیکند — تجربه انسانی تعیینکننده باقی میماند.
میتوان اگر کد به ندرت تغییر میکند یا در آینده نزدیک به طور کامل بازنویسی خواهد شد. با این حال انباشت بوها به بدهی فنی تبدیل میشود: هر تغییر جدید دشوارتر میشود و هزینه رفع به طور نمایی افزایش مییابد.
بله — فریمورکهای اعلامی بوهای جدیدی ایجاد کردهاند: بلوکهای غولپیکر @State, کار نادرست با رندرهای مجدد, recomposition بیش از حد, عدم استخراج به Viewهای جداگانه. برای SwiftUI بوی معمول Massive View با دهها متغیر @State است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید