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 као критеријум квалитета кода. Ако приметите да се исти образац појављује у пројекту три пута — издвојите га у апстракцију, не чекајући четврто понављање.
Single Responsibility Principle (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 тако да pull-реквестови са дуплирањем већим од 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. При додавању нове логике (аналитика, логирање) морали би да се мењају сви фајлови. Базнa класа елиминише ово дуплирање: заједничка логика живи на једном месту, специфична — у наследницима.
У 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 класа за сваку пословну операцију и њихово убризгавање тамо где су потребне.
Бирајте композицију у свим случајевима осим релација „јесте“. Ако је класа А специјализација класе Б — наслеђивање је прикладно. Ако А само користи функционалност Б — користите композицију.
Утилитарне класе (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 смањује конфликте зависности за 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 се постиже кроз протоколе са подразумеваном имплементацијом, заједничке мрежне конфигурације (NetworkConfig), фабрике ћелија UICollectionView и SPM пакете са заједничком пословном логиком. Extensions стандардних типова (Date, String, URL) смањују дуплирање форматирања и валидације.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође