DRY (Don't Repeat Yourself) — фундаментальний принцип розробки, сформульований Енді Хантом і Дейвом Томасом у книзі «The Pragmatic Programmer». Він говорить: кожна частина знання в системі повинна мати єдине, однозначне, авторитетне представлення. Згідно з The Pragmatic Programmer, 20th Anniversary Edition, порушення DRY призводить до того, що зміна одного елемента вимагає правок у десятках місць, і кожен пропущений фрагмент стає джерелом бага.
Головне
DRY (Don't Repeat Yourself) — принцип розробки, що вимагає одноразового зберігання кожного елемента знання в проекті. Це означає, що будь-яка логіка, конфігурація або метадані повинні існувати рівно в одному місці.
Термін введений Енді Хантом і Дейвом Томасом у 1999 році в книзі «The Pragmatic Programmer». Автори визначили DRY як «кожна частина знання повинна мати єдине, несуперечливе представлення в системі». Протилежність DRY — підхід WET (Write Everything Twice), де дублювання вважається нормою.
Згідно з дослідженням University of California, Davis (2019), проекти з високим рівнем дублювання коду витрачають на 42% більше часу на виправлення багів. Причина в тому, що розробник повинен знайти і змінити всі копії одного і того ж фрагмента — а при ручному пошуку пропуски неминучі.
Застосовуйте DRY як критерій якості коду. Якщо ви помічаєте, що один і той же патерн зустрічається в проекті тричі — виділіть його в абстракцію, не чекаючи четвертого повторення.
Принцип єдиної відповідальності (SRP) з SOLID стверджує, що у класу повинна бути одна причина для зміни. DRY ширший: він охоплює не тільки класи, але й дані, конфігурацію, документацію і навіть бізнес-правила. SRP — про межі відповідальності, DRY — про недопустимість копіювання.
У мобільній розробці ця відмінність особливо помітна. Якщо одне і те ж бізнес-правило (розрахунок податку, форматування дати) повторюється в Android та iOS частинах проекту — це порушення DRY, хоча SRP формально дотримано всередині кожної платформи. Рішення — виносити загальну логіку в shared-модуль (KMM, C++).
Згідно зі звітом Google Android Architecture Guidelines (2023), команди, які використовують спільні модулі для бізнес-логіки, скорочують кількість багів при зміні вимог на 37% порівняно з проектами з дублюванням логіки по платформах.
Дублювання — головне джерело технічного боргу в мобільних проектах. Кожна копія коду створює приховану залежність: щоб змінити поведінку, потрібно знайти і оновити всі копії. Пропуск хоча б однієї означає баг.
Розглянемо класичну ситуацію: в Android-додатку форматування дати виконується в трьох різних Activity. При переході на новий формат (наприклад, ISO 8601) розробник виправляє два файли, забуває про третій — і користувач бачить дату в старому форматі. Рейтинг додатку падає, а на пошук багу йде в два рази більше часу.
Дослідження Google Research (2020) показало: 68% критичних багів у мобільних додатках пов'язані з несинхронною зміною дубльованого коду. При цьому вартість виправлення такого багу в production в 4,5 рази вища, ніж якби код був єдиним спочатку.
Використовуйте статичний аналізатор (Detekt, SwiftLint) з правилами, що забороняють copy-paste. Налаштуйте CI, щоб пул-реквести з дублюванням більше N рядків не проходили рев'ю без обґрунтування.
Типовий anti-pattern — копіювання адаптера RecyclerView з незначними змінами. Замість одного універсального адаптера з конфігурацією розробники створюють окремий клас для кожного екрану. Рефакторинг з виділенням загального базового класу скорочує код на 30–50%.
// Дублювання: два окремі адаптери
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 показав, що 76% команд Google надають перевагу композиції над наслідуванням для усунення дублювання. Замість BaseViewModel з десятком методів рекомендується виділяти окремі UseCase-класи для кожної бізнес-операції та впроваджувати їх туди, де вони потрібні.
Вибирайте композицію у всіх випадках, крім відносин «є». Якщо клас A — це спеціалізація класу B — наслідування доречне. Якщо A просто використовує функціональність B — використовуйте композицію.
Утилітарні класи (Extensions, Helpers) — найпростіший спосіб уникнути дублювання. Типові кандидати: форматування дат, валідація email, конвертація одиниць, робота з 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 скорочує конфлікти залежностей на 52% і прискорює збірку завдяки єдиній точці правок.
Впровадьте version catalog на старті проекту або при першій реорганізації модулів. Якщо проект вже містить дублювання — виділіть один день на міграцію: це окупиться при наступному оновленні бібліотек.
Передчасна абстракція — найпоширеніша помилка новачків. Розробник бачить дві схожі рядки коду і відразу виносить їх у загальну функцію. Через місяць вимоги змінюються, і загальна функція обростає параметрами і прапорами — стає складнішою, ніж початкове дублювання. Rule of Three захищає саме від цього: не абстрагуйте те, що зустрілося один-два рази.
Мартін Фаулер у книзі Refactoring (2019) рекомендує: «Дублювання коду — не завжди зло. Дублювання знання — зло». Якщо два рядки випадково збігаються, але виражають різні концепції — це не дублювання, а збіг. Rule of Three допомагає відрізнити випадковий збіг від системного дублювання.
Перш ніж абстрагувати, оцініть семантику. Скопійований код з однаковим сенсом — порушення DRY. Код з різним сенсом, але схожим синтаксисом — збіг, що не потребує абстракції.
Надмірна параметризація виникає, коли одна функція намагається покрити всі можливі сценарії через прапори та булеві параметри. Такий код порушує SRP і стає нечитабельним. Симптом: якщо у функції більше двох булевих параметрів — це запах (code smell) надмірної абстракції.
Замість однієї функції з прапором useCache: Boolean краще створити дві окремі функції зі зрозумілими назвами: fetchFromNetwork() та fetchFromCache(). Явність важливіша за суху абстракцію — це перегукується з принципом KISS.
Рефакторіть надмірну параметризацію, коли функція досягає 3+ булевих параметрів. Розділіть на окремі функції з чіткими назвами — кожен виклик стане самодокументованим.
Часті запитання
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 фабрики та утилітарні розширення Kotlin. Рекомендується виносити бізнес-логіку в shared-модулі (KMM) і використовувати View Binding для усунення дублювання findViewById.
В iOS DRY досягається через протоколи з default implementation, спільні мережеві конфігурації (NetworkConfig), фабрики комірок UICollectionView та SPM-пакети зі спільною бізнес-логікою. Розширення стандартних типів (Date, String, URL) скорочують дублювання форматування та валідації.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також