DRY (Don't Repeat Yourself) — یک اصل بنیادی توسعه است که توسط اندی هانت و دیو توماس در کتاب «The Pragmatic Programmer» فرموله شده است. میگوید: هر بخش از دانش در سیستم باید یک نمایش واحد، بدون ابهام و معتبر داشته باشد. بر اساس دادههای The Pragmatic Programmer, 20th Anniversary Edition، نقض DRY منجر به این میشود که تغییر یک عنصر نیاز به اصلاح در دهها مکان داشته باشد و هر قطعه از دست رفته به منبع باگ تبدیل شود.
نکات اصلی
DRY (Don't Repeat Yourself) — اصل توسعهای است که نیازمند ذخیرهسازی یکباره هر عنصر دانش در پروژه است. این بدان معناست که هر منطق، پیکربندی یا فراداده باید دقیقاً در یک مکان وجود داشته باشد.
این اصطلاح توسط اندی هانت و دیو توماس در سال ۱۹۹۹ در کتاب «The Pragmatic Programmer» معرفی شد. نویسندگان DRY را به عنوان «هر بخش از دانش باید یک نمایش واحد و سازگار در سیستم داشته باشد» تعریف کردند. برعکس DRY — رویکرد WET (Write Everything Twice) است که در آن تکرار هنجار محسوب میشود.
بر اساس تحقیق University of California, Davis (2019)، پروژههای با سطح بالای تکرار کد ۴۲٪ زمان بیشتری برای رفع باگها صرف میکنند. دلیل این است که توسعهدهنده باید تمام نسخههای یک قطعه را پیدا کرده و تغییر دهد — و در جستجوی دستی، جاانداختنها اجتنابناپذیر است.
DRY را به عنوان معیار کیفیت کد اعمال کنید. اگر متوجه میشوید که یک الگو سه بار در پروژه ظاهر میشود — آن را به یک انتزاع تبدیل کنید، بدون منتظر ماندن برای تکرار چهارم.
Single Responsibility Principle (SRP) از SOLID میگوید که یک کلاس باید یک دلیل برای تغییر داشته باشد. DRY گستردهتر است: نه تنها کلاسها، بلکه دادهها، پیکربندی، مستندات و حتی قوانین کسبوکار را نیز در بر میگیرد. SRP درباره مرزهای مسئولیت است، DRY — درباره غیرقابل قبول بودن کپیکردن.
در توسعه موبایل این تفاوت به ویژه قابل توجه است. اگر یک قانون کسبوکار (محاسبه مالیات، فرمت تاریخ) در بخشهای Android و iOS تکرار شود — این نقض DRY است، اگرچه SRP به طور رسمی در هر پلتفرم رعایت شده است. راهحل — جدا کردن منطق مشترک به یک ماژول اشتراکی (KMM, C++).
بر اساس گزارش Google Android Architecture Guidelines (2023)، تیمهایی که از ماژولهای مشترک برای منطق کسبوکار استفاده میکنند، تعداد باگها را هنگام تغییر نیازمندیها ۳۷٪ در مقایسه با پروژههای با تکرار منطق در پلتفرمها کاهش میدهند.
تکرار — منبع اصلی بدهی فنی در پروژههای موبایل. هر نسخه از کد یک وابستگی پنهان ایجاد میکند: برای تغییر رفتار، باید همه نسخهها را پیدا کرده و بهروزرسانی کرد. از دست رفتن حتی یکی به معنای باگ است.
یک موقعیت کلاسیک را در نظر بگیرید: در اپلیکیشن Android، فرمت تاریخ در سه Activity مختلف انجام میشود. هنگام تغییر به فرمت جدید (مثلاً ISO 8601)، توسعهدهنده دو فایل را اصلاح میکند، سومی را فراموش میکند — و کاربر تاریخ را در قالب قدیمی میبیند. رتبه کاربران اپلیکیشن کاهش مییابد و جستجوی باگ دو برابر زمان میبرد.
تحقیق Google Research (2020) نشان داد: ۶۸٪ از باگهای بحرانی در اپلیکیشنهای موبایل با تغییر غیرهمزمان کد تکراری مرتبط است. هزینه رفع چنین باگی در production ۴.۵ برابر بیشتر از حالتی است که کد از ابتدا یکپارچه بود.
از تحلیلگر استاتیک (Detekt, SwiftLint) با قوانینی که تشخیص copy-paste را ممنوع میکنند استفاده کنید. CI را طوری پیکربندی کنید که pull-requestهای با تکرار بیش از N خط بدون توجیه از review عبور نکنند.
یک ضدالگوی معمول — کپی کردن آداپتر RecyclerView با تغییرات جزئی. به جای یک آداپتر جهانی با پیکربندی، توسعهدهندگان برای هر صفحه یک کلاس جداگانه ایجاد میکنند. بازآرایی با جدا کردن یک کلاس پایه مشترک، کد را ۳۰–۵۰٪ کاهش میدهد.
// تکرار: دو آداپتر جداگانه
class UserAdapter {
fun bind(item: User) { /* ... */ }
}
class ProductAdapter {
fun bind(item: Product) { /* ... */ }
}
// بازآرایی DRY: کلاس پایه مشترک
abstract class BaseAdapter<T> {
abstract fun bind(item: T)
}
class UserAdapter : BaseAdapter<User>() { /* ... */ }
class ProductAdapter : BaseAdapter<Product>() { /* ... */ }
در مثال اول، هر آداپتر مکانیزم bind را از نو پیادهسازی میکند. هنگام افزودن منطق جدید (تحلیل، ثبت رویداد) باید هر فایل را تغییر داد. کلاس پایه این تکرار را حذف میکند: منطق مشترک در یک مکان زندگی میکند، منطق خاص — در کلاسهای مشتقشده.
در پروژههای iOS اغلب پیکربندی URLSession — هدرها، زمانهای انتظار، مدیریت خطا تکرار میشود. هر سرویس جلسه خود را با تنظیمات تکراری ایجاد میکند.
// تکرار: هر سرویس جلسه را از نو پیکربندی میکند
class UserService {
let session = URLSession(configuration: {
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 30
cfg.httpAdditionalHeaders = ["Authorization": "Bearer ..."]
return cfg
}())
}
// DRY: کارخانه یکپارچه جلسات
struct NetworkConfig {
static var session: URLSession {
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 30
cfg.httpAdditionalHeaders = ["Authorization": "Bearer ..."]
return URLSession(configuration: cfg)
}
}
جدا کردن پیکربندی به NetworkConfig یکپارچه تضمین میکند که همه سرویسها از هدرها و زمانهای انتظار یکسانی استفاده میکنند. تغییر در یک مکان به طور خودکار به همه درخواستها اعمال میشود — این خطر خطا را هنگام تغییر کلید API یا نسخه پروتکل کاهش میدهد.
وراثت — راه طبیعی حذف تکرار: منطق مشترک به کلاس پایه منتقل میشود و منطق خاص — به کلاسهای مشتقشده. اما در توسعه موبایل، سوءاستفاده از وراثت سلسلهمراتب خشکی ایجاد میکند که نگهداری آنها دشوار است. ترکیب (تزریق وابستگی) — جایگزین انعطافپذیرتری است.
تحلیل Google I/O 2023: Modern Android Architecture نشان داد که ۷۶٪ تیمهای Google ترکیب را به وراثت برای حذف تکرار ترجیح میدهند. به جای BaseViewModel با ده روش، توصیه میشود کلاسهای UseCase جداگانه برای هر عملیات تجاری ایجاد کرده و آنها را در جایی که نیاز است تزریق کنید.
در همه موارد به جز روابط «is-a» ترکیب را انتخاب کنید. اگر کلاس A تخصصیسازی کلاس B است — وراثت مناسب است. اگر A فقط از قابلیت B استفاده میکند — از ترکیب استفاده کنید.
کلاسهای کاربردی (Extensions, Helpers) — سادهترین راه برای جلوگیری از تکرار. نامزدهای معمول: فرمت تاریخ، اعتبارسنجی ایمیل، تبدیل واحدها، کار با SharedPreferences/UserDefaults.
// DRY: تابع یکپارچه فرمت تاریخ
fun Date.toDisplayFormat(): String {
val sdf = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
return sdf.format(this)
}
// استفاده در هر جای برنامه
textView.text = Date().toDisplayFormat()
افزونه Date.toDisplayFormat() یک بار اعلام شده و در کل پروژه قابل دسترسی است. اگر نیاز به تغییر فرمت از «dd.MM.yyyy» به «yyyy-MM-dd» باشد — اصلاح در یک فایل، نه در هر Activity یا Fragment که فرمتدهی در آن انجام میشود. این دقیقاً جوهر DRY است.
پروژههای چندماژولی Android اغلب نسخههای وابستگیها را در هر build.gradle تکرار میکنند. راهحل — version catalog (libs.versions.toml) که همه نسخهها را در یک فایل متمرکز میکند.
بر اساس Android Developer Documentation (2024)، مهاجرت به version catalog تضاد وابستگیها را ۵۲٪ کاهش میدهد و ساخت را با یک نقطه واحد برای اصلاحات سرعت میبخشد.
version catalog را در شروع پروژه یا در اولین بازسازی ماژولها پیادهسازی کنید. اگر پروژه از قبل دارای تکرار است — یک روز را به مهاجرت اختصاص دهید: این کار در بهروزرسانی بعدی کتابخانهها جواب میدهد.
انتزاع زودهنگام — رایجترین اشتباه مبتدیان. توسعهدهنده دو خط کد مشابه میبیند و بلافاصله آنها را به یک تابع مشترک تبدیل میکند. یک ماه بعد نیازمندیها تغییر میکند و تابع مشترک با پارامترها و پرچمها سنگین میشود — از تکرار اولیه پیچیدهتر میشود. Rule of Three دقیقاً از این محافظت میکند: آنچه را که یک یا دو بار دیده شده است انتزاع نکنید.
مارتین فاولر در کتاب Refactoring (2019) توصیه میکند: «تکرار کد همیشه بد نیست. تکرار دانش بد است». اگر دو خط به طور تصادفی یکسان هستند اما مفاهیم متفاوتی را بیان میکنند — این تکرار نیست، بلکه تصادف است. Rule of Three به تشخیص تصادف از تکرار سیستماتیک کمک میکند.
قبل از انتزاع، معناشناسی را ارزیابی کنید. کد کپی شده با معنای یکسان — نقض DRY. کد با معنای متفاوت اما نحو مشابه — تصادف است که نیازی به انتزاع ندارد.
پارامترسازی بیش از حد زمانی رخ میدهد که یک تابع تلاش میکند همه سناریوهای ممکن را از طریق پرچمها و پارامترهای بولی پوشش دهد. چنین کدی SRP را نقض کرده و غیرقابل خواندن میشود. نشانه: اگر تابع بیش از دو پارامتر بولی دارد — این بوی کد (code smell) انتزاع بیش از حد است.
به جای یک تابع با پرچم useCache: Boolean بهتر است دو تابع جداگانه با نامهای واضح ایجاد کنید: fetchFromNetwork() و fetchFromCache(). صراحت مهمتر از انتزاع خشک است — این با اصل KISS همخوانی دارد.
زمانی که تابع به ۳+ پارامتر بولی رسید، پارامترسازی بیش از حد را بازآرایی کنید. به توابع جداگانه با نامهای شفاف تقسیم کنید — هر فراخوانی خودمستندساز خواهد شد.
سوالات متداول
DRY (Don't Repeat Yourself) — اصلی که نیازمند ذخیره هر واحد منطقی در یک مکان واحد است. اگر کد یکسان در چند بخش پروژه ظاهر شود — این نقض DRY است. اصلاح: منطق تکراری را به یک تابع، کلاس یا ماژول جداگانه منتقل کنید.
WET (Write Everything Twice) — نقطه مقابل DRY که در آن تکرار قابل قبول تلقی میشود. در پروژههای WET یک قطعه کد میتواند در پنج نسخه وجود داشته باشد و هنگام تغییر نیازمندیها، توسعهدهنده هر نسخه را جداگانه اصلاح میکند. WET خطر باگ را افزایش داده و توسعه را کند میکند.
DRY در انتزاع زودهنگام مضر است: زمانی که دو بخش کد مشابه اما از نظر معنایی متفاوت به زور در یک تابع ترکیب میشوند. این کد پیچیده و سنگینی با پارامترهای زیاد ایجاد میکند. Rule of Three به جلوگیری از این اشتباه کمک میکند: فقط پس از تکرار سوم انتزاع کنید.
در Android DRY از طریق version catalog (libs.versions.toml)، کلاسهای پایه مشترک برای آداپترها، کارخانههای ViewModel و افزونههای کاربردی کاتلین اعمال میشود. توصیه میشود منطق کسبوکار را به ماژولهای اشتراکی (KMM) منتقل کرده و از View Binding برای حذف تکرار findViewById استفاده کنید.
در iOS DRY از طریق پروتکلهای با پیادهسازی پیشفرض، پیکربندیهای شبکه مشترک (NetworkConfig)، کارخانههای سلول UICollectionView و بستههای SPM با منطق کسبوکار مشترک به دست میآید. Extensions انواع استاندارد (Date, String, URL) تکرار قالببندی و اعتبارسنجی را کاهش میدهد.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید