Code Smell — површни знак у коду који сигнализира о потенцијалном проблему у дизајну или архитектури апликације. Термин је увео Кент Бек и популаризовао Мартин Фаулер у књизи „Refactoring: Improving the Design of Existing Code“. Према Мартину Фаулеру, мирис кода не значи нужно грешку, али скоро увек указује на потребу за рефакторисањем ради побољшања одржавања.
Главно
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 pattern.
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 граде графиконе промене метрика током времена. Ако је сложеност методе порасла са 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 у целини. Издвојте прво слој навигације, затим слој података, потом логику приказа. Сваки корак пропратите комитом и покретањем тестова. Користите 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 преко затварања, неправилан рад са 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 блокови, неправилан рад са поновљеним рендерима, прекомерна композиција, недостатак издвајања у засебне View-ове. За SwiftUI типичан мирис је Massive View са десетинама @State променљивих.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође