Code Smell este un semn superficial în cod care semnalează o potențială problemă în designul sau arhitectura aplicației. Termenul a fost introdus de Kent Beck și popularizat de Martin Fowler în cartea „Refactoring: Improving the Design of Existing Code“. Potrivit lui Martin Fowler, mirosul de cod nu înseamnă neapărat o eroare, dar aproape întotdeauna indică necesitatea refactorizării pentru îmbunătățirea mentenanței.
Principalele
Code Smell (miros de cod) — este o metaforă pentru simptomele în codul sursă care indică cu probabilitate mare probleme mai profunde. Termenul în sine nu are o definiție formală — este o euristică bazată pe experiența dezvoltatorilor. Martin Fowler și Kent Beck în 1999 au sistematizat pentru prima dată 22 de mirosuri în cartea „Refactoring“, iar majoritatea rămân actuale după decenii.
Este important să înțelegem diferența dintre Code Smell și o eroare. Mirosul nu este o eroare: codul se compilează, funcționează și produce rezultatul corect. Problema este că un astfel de cod este greu de citit, modificat și testat. în timp, costul fiecărei modificări crește, iar încrederea în corectitudinea refactorizării scade. Instrumentele de analiză statică (SonarQube, Detekt, SwiftLint) detectează automat multe mirosuri.
Caracterul euristic al Code Smell înseamnă că nu orice metodă lungă trebuie împărțită și nu orice clasă mare necesită refactorizare. Decizia o ia dezvoltatorul, evaluând contextul: frecvența modificărilor, criticitatea modulului, planurile de dezvoltare. Inginerii experimentați simt mirosul intuitiv — codul „miroase neplăcut“, deși formal toate regulile sunt respectate.
Fowler a evidențiat 22 de mirosuri care se împart în mai multe categorii. Pentru dezvoltarea mobilă, cele mai relevante sunt mirosurile structurale, mirosurile de design orientat pe obiecte și problemele specifice legate de limitările platformei. Să examinăm fiecare grup cu exemple din practica reală.
Long Method (metodă lungă) — cel mai răspândit miros în aplicațiile mobile. Ecranul cu formular de înregistrare conține adesea o singură metodă setupUI de 200+ linii care creează toate View-urile, configurează constrângerile, se abonează la evenimente și gestionează erorile. Soluția: împărțirea în metode după blocuri logice — configureEmailField, configurePasswordField, setupConstraints, bindViewModel.
Large Class (clasă mare) — Activity sau ViewController care răspunde atât de afișare, cât și de navigare, logică de business și comunicare rețea. O astfel de clasă încalcă Single Responsibility Principle și conține zeci de câmpuri și metode. în Android, acesta este adesea un Fragment cu 1000+ linii care conține logica diferitelor ecrane. Soluția: separarea presenter/ViewModel, mutarea lucrului cu rețeaua în repository, navigarea în coordinator.
Duplicate Code (duplicare de cod) — copierea acelorași blocuri în diferite părți ale aplicației. Exemplu tipic: două ecrane care afișează cardul unui produs — în catalog și în favorite. Dacă logica de afișare este copiată, corectarea unei erori într-un loc nu o va repara în celălalt. Soluția: mutarea logicii comune într-o componentă reutilizabilă sau extensie.
Feature Envy (invidie față de altă clasă) — metoda unei clase utilizează intens datele altei clase. în Android, aceasta se manifestă când ViewModel accesează direct câmpurile modelului User în loc să apeleze o metodă a modelului. Semnal: dacă metoda poate fi mutată în clasa ale cărei date le folosește — mutați-o. Switch Statements (lanțurile de condiții) — construcția switch sau lanțul if-else care verifică tipul obiectului. în loc de aceasta, trebuie folosit polimorfismul sau pattern-ul strategy.
Data Class — o clasă care doar stochează date, dar nu conține comportament. în sine, data class (în Kotlin) sau structurile (în Swift) nu sunt un miros. Problema apare atunci când logica de business care lucrează cu aceste date este răspândită în întreaga bază de cod în loc să fie încapsulată. Refused Bequest — moștenitorul nu folosește majoritatea metodelor părintelui și le suprascrie cu implementări goale. Semn de moștenire incorectă: înlocuiți moștenirea cu compoziție.
God Activity / God Fragment — Activity sau Fragment care știe totul: despre ciclul de viață, date, navigare, permisiuni, DI. Aceasta este cea mai costisitoare clasă de întreținut a aplicației. Soluția: modelele arhitecturale MVVM, MVI sau Clean Architecture împart responsabilitatea. Giant ViewController — analog pentru iOS, unde UIViewController conține întreaga logică a ecranului și depășește adesea 500 de linii.
Hardcoded Resources — șiruri, culori, dimensiuni, URL-uri API încorporate direct în cod. în Android, aceasta încalcă utilizarea sistemului de resurse R, în iOS — NSLocalizedString și Asset Catalog. Corectare: mutați toate șirurile în strings.xml sau Localizable.strings, URL-urile în fișierul de configurare, dimensiunile în dimens. Leaking Context — păstrarea referinței către Activity sau ViewController mai mult decât trăiește componentul în sine. Duce la scurgeri de memorie și crash-uri. Soluția: referințe slabe, Jetpack Lifecycle, RxSwift DisposeBag.
| Miros | Unde apare | Soluție |
|---|---|---|
| Long Method | Android/iOS | Extract Method, împărțire |
| Large Class | Activity, ViewController | MVVM, VIPER, Clean Arch |
| Duplicate Code | Orice ecran | Shared Component, DRY |
| Feature Envy | ViewModel, Presenter | Move Method |
| Leaking Context | Android | Componente conștiente de ciclul de viață |
Code review — cel mai sigur mod de detectare a mirosurilor. Ochiul uman observă construcții nenaturale pe care analizoarele automate le ratează. Eficiența revizuirii codului crește atunci când echipa folosește o listă de verificare a mirosurilor tipice. Se recomandă verificarea a nu mai mult de 200–400 de linii de cod într-o singură sesiune — după acest prag, atenția scade și mirosurile încep să scape.
Analiza statică automatizează căutarea mirosurilor structurale. Pentru Android, instrumentele standard sunt Detekt (Kotlin) și Android Lint, pentru iOS — SwiftLint și SonarQube. Aceste instrumente găsesc metode lungi, clase mari, duplicare de cod și multe alte probleme. Este important să configurați regulile pentru proiect — configurările implicite sunt adesea prea stricte sau, dimpotrivă, ratează mirosuri critice.
Metricile codului oferă criterii obiective: Cyclomatic Complexity (pragul >10 necesită atenție), Lines of Code per Method (pragul >30), Depth of Inheritance (>3 — motiv de reflecție). Instrumente precum CodeMetrics (Xcode) și Gradle Metrics Plugin construiesc grafice ale modificării metricilor în timp. Dacă complexitatea unei metode a crescut de la 5 la 15 după ultimul commit — acesta este un semnal pentru refactorizare.
// Exemplu: metodă cu complexitate Cyclomatic = 7 (peste pragul de 5)
fun processOrder(order: Order) {
if (order.status == Status.NEW) { /* 10 linii */ }
else if (order.status == Status.PAID) { /* 15 linii */ }
else if (order.status == Status.SHIPPED) { /* 20 linii */ }
else if (order.status == Status.DELIVERED) { /* 8 linii */ }
else if (order.status == Status.CANCELLED) { /* 5 linii */ }
else { throw IllegalStateException() }
}
// Corectare: polimorfism în loc de switch
interface OrderHandler {
fun handle(order: Order)
}
Căutarea automată a mirosurilor nu înlocuiește revizuirea codului: analizoarele statice găsesc doar probleme structurale, dar nu detectează mirosuri semantice (Feature Envy, Inappropriate Intimacy). Combinarea instrumentelor automate și a controlului uman oferă cel mai bun rezultat. Configurați pipeline-ul CI/CD astfel încât construcția să eșueze la depășirea pragurilor de complexitate sau lungime a metodei.
Refactorizarea — metoda principală de eliminare a mirosurilor de cod. Fowler descrie zeci de tehnici de refactorizare, fiecare aplicabilă unui anumit miros. Extract Method — pentru metode lungi, Extract Class — pentru clase mari, Move Method — pentru Feature Envy. Este important să efectuați refactorizarea în pași mici, păstrând funcționalitatea codului după fiecare modificare.
Testele înainte de refactorizare — o condiție obligatorie. Dacă codul nu este acoperit de teste unitare, refactorizarea se transformă în rescriere cu rezultat necunoscut. Pentru codul legacy fără teste, folosiți Characterisation Tests — scrieți teste care fixează comportamentul curent, apoi refactorizați. Testarea oferă certitudinea că după refactorizare logica de business nu s-a stricat.
Gradualitatea — cheia succesului în eliminarea mirosurilor în dezvoltarea mobilă. Nu încercați să rescrieți God Activity în totalitate. Separați mai întâi stratul de navigare, apoi stratul de date, apoi logica de afișare. Fiecare pas însoțiți-l cu un commit și rularea testelor. Folosiți feature toggle pentru a activa refactorizarea pentru o parte din utilizatori și a o retrage în caz de probleme.
Instrumentele IDE automatizează multe tehnici de refactorizare. Android Studio și IntelliJ IDEA oferă refactorizări încorporate: Extract Method, Extract Interface, Pull Members Up, Encapsulate Fields. Xcode (începând cu versiunea 14) a îmbunătățit suportul pentru refactorizare în Swift. Utilizarea refactorizărilor automate reduce riscul de erori în comparație cu copierea manuală a codului.
Dezvoltarea mobilă adaugă propriile mirosuri specifice legate de limitările platformei. în Android, acestea sunt scurgerea de Context, Cursor-ul neînchis, utilizarea incorectă a Lifecycle. în iOS — retain cycle prin closure-uri, lucrul incorect cu Auto Layout, ViewController-uri gigantice. Aceste mirosuri nu doar înrăutățesc mentenanța, ci afectează direct performanța și stabilitatea aplicației.
Callback Hell — un miros caracteristic pentru codul care lucrează cu operații asincrone. Callback-urile încuibărite (callback inside callback) fac codul ilizibil și greu de depanat. Soluția: corutine (Kotlin), async/await (Swift 5.5+), RxJava/RxSwift sau Combine. Potrivit Google I/O 2023, proiectele care au trecut de la stilul callback la corutine reduc numărul de erori cu 30% și accelerează adăugarea de noi funcționalități.
Platform Coupling — legarea rigidă a logicii de business de componentele platformei. Testarea unei astfel de logici necesită pornirea emulatorului, ceea ce încetinește ciclul de feedback. Corectare: Clean Architecture împarte codul în straturi Domain (Kotlin/Swift pur, fără dependențe de platformă) și Data/UI (cu dependențe de platformă). Logica de business se testează pe JVM fără emulator.
întrebări frecvente
Nu — Code Smell nu este o eroare. Codul cu miros funcționează corect, dar este greu de întreținut, modificat și testat. Eroarea este un comportament incorect, mirosul este o avertizare despre potențiale probleme în viitor.
22 de mirosuri în a doua ediție a cărții „Refactoring“ (2019). Printre ele se numără Long Method, Large Class, Primitive Obsession, Data Clumps, Switch Statements, Speculative Generality și altele. Comunitatea a adăugat zeci de mirosuri noi pentru paradigme și platforme moderne.
Combinația oferă cel mai bun rezultat: Detekt (Android/Kotlin), SwiftLint (iOS), SonarQube (ambele) pentru analiza automată și code review pentru mirosuri semantice. Niciun instrument nu găsește 100% din probleme — experiența umană rămâne decisivă.
Se poate, dacă codul se modifică rar sau va fi complet rescris în viitorul apropiat. în orice caz, acumularea mirosurilor se transformă în datorie tehnică: fiecare modificare nouă devine tot mai dificilă, iar costul corectării crește exponențial.
Da — framework-urile declarative au generat mirosuri noi: blocuri @State gigantice, lucrul incorect cu randări repetate, recompoziție excesivă, lipsa extragerii în View-uri separate. Pentru SwiftUI, mirosul tipic este Massive View cu zeci de variabile @State.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și