Code Smell у мобільній розробці: суть, види та принципи виправлення

Автор: IT Sectr Опубліковано: 2026-05-13 Час читання: 9 хв

Code Smell — це поверхнева ознака в коді, яка сигналізує про потенційну проблему в дизайні або архітектурі застосунку. Термін ввів Кент Бек і популяризував Мартін Фаулер у книзі «Refactoring: Improving the Design of Existing Code». За даними Martin Fowler, запах коду не обов'язково означає баг, але майже завжди вказує на необхідність рефакторингу для поліпшення підтримуваності.

Головне

  • Code Smell — зовнішня ознака проблеми в коді, яка не є помилкою, але ускладнює підтримку та розвиток
  • Довгий метод — найчастіший запах: метод, який робить забагато і потребує розбиття на декілька
  • Великий клас — клас, що порушує принцип єдиної відповідальності та містить логіку різних доменів
  • Duplicate code — повторювані фрагменти коду, які при зміні потрібно правити в кількох місцях
  • Feature envy — метод, який більше використовує дані іншого класу, ніж свого власного

Що таке Code Smell

Code Smell (запах коду) — це метафора для позначення симптомів у вихідному коді, які з високою ймовірністю вказують на глибші проблеми. Сам термін не має формального визначення — це евристика, заснована на досвіді розробників. Мартін Фаулер і Кент Бек у 1999 році вперше систематизували 22 запахи в книзі «Refactoring», і більшість із них залишаються актуальними десятиліття потому.

Важливо розуміти різницю між Code Smell і багом. Запах — це не помилка: код компілюється, працює і видає коректний результат. Проблема в тому, що такий код важко читати, змінювати та тестувати. З часом вартість кожної зміни зростає, а впевненість у коректності рефакторингу падає. Інструменти статичного аналізу (SonarQube, Detekt, SwiftLint) виявляють багато запахів автоматично.

Евристичний характер Code Smell означає, що не кожен довгий метод потрібно розбивати, і не кожен великий клас потребує рефакторингу. Рішення приймає розробник, оцінюючи контекст: частота змін, критичність модуля, плани на розвиток. Досвідчені інженери бачать запах інтуїтивно — код «неприємно пахне», хоча формально всі правила дотримані.

Основні види Code Smell

Фаулер виділив 22 запахи, які діляться на кілька категорій. Для мобільної розробки найбільш актуальні структурні запахи, запахи об'єктно-орієнтованого дизайну та специфічні проблеми, пов'язані з платформеними обмеженнями. Розглянемо кожну групу на прикладах із реальної практики.

Структурні запахи

Long Method (довгий метод) — найпоширеніший запах у мобільних застосунках. Екран з формою реєстрації часто містить один метод setupUI довжиною 200+ рядків, який створює всі View, налаштовує констрейнти, підписується на події та обробляє помилки. Рішення: розбити на методи за логічними блоками — configureEmailField, configurePasswordField, setupConstraints, bindViewModel.

Large Class (великий клас) — Activity або ViewController, який відповідає і за відображення, і за навігацію, і за бізнес-логіку, і за мережеву взаємодію. Такий клас порушує принцип єдиної відповідальності та містить десятки полів і методів. В Android це часто Fragment з 1000+ рядків, що містить логіку різних екранів. Рішення: виділити презентер/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, які знають про все: про життєвий цикл, про дані, про навігацію, про Permissions, про DI. Це найдорогий у підтримці клас застосунку. Рішення: архітектурні патерни MVVM, MVI або Clean Architecture розділяють відповідальність. Giant ViewController — аналог для iOS, де UIViewController містить всю логіку екрана і часто перевищує 500 рядків.

Hardcoded Resources — рядки, кольори, розміри, API URL, вбудовані безпосередньо в код. В Android це порушує використання ресурсної системи R, в iOS — NSLocalizedString і Asset Catalog. Виправлення: виносити всі рядки в strings.xml або Localizable.strings, URL в конфігураційний файл, розміри в dimens. Leaking Context — зберігання посилання на Activity або ViewController довше, ніж живе сам компонент. Призводить до витоків пам'яті та кражів. Рішення: слабкі посилання, Jetpack Lifecycle, RxSwift DisposeBag.

ЗапахДе зустрічаєтьсяРішення
Long MethodAndroid/iOSExtract Method, розбиття
Large ClassActivity, ViewControllerMVVM, VIPER, Clean Arch
Duplicate CodeБудь-які екраниShared Component, DRY
Feature EnvyViewModel, PresenterMove Method
Leaking ContextAndroidLifecycle-aware компоненти

Як знаходити Code Smell

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 після останнього коміту — це сигнал до рефакторингу.

kotlin
// Приклад: метод зі складністю 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 пайплайн так, щоб білд падав при перевищенні порогів складності або довжини методу.

Як виправляти Code Smell

Рефакторинг — основний метод усунення запахів коду. Фаулер описує десятки технік рефакторингу, кожна з яких застосовна до певного запаху. Extract Method — для довгих методів, Extract Class — для великих класів, Move Method — для Feature Envy. Важливо виконувати рефакторинг маленькими кроками, зберігаючи працездатність коду після кожної зміни.

Тести перед рефакторингом — обов'язкова умова. Якщо код не покритий Unit-тестами, рефакторинг перетворюється на переписування з невідомим результатом. Для legacy-коду, де тестів немає, використовуйте Characterisation Tests — напишіть тести, які фіксують поточну поведінку, а потім рефакторте. Тестування дає впевненість, що після рефакторингу бізнес-логіка не зламалася.

Поступовість — ключ до успішного виправлення запахів у мобільній розробці. Не намагайтеся переписати God Activity цілком. Виділіть спочатку шар навігації, потім шар даних, потім логіку відображення. Кожен крок супроводжуйте комітом і прогоном тестів. Використовуйте feature toggle, щоб вмикати рефакторинг для частини користувачів і відкочувати при проблемах.

  • Extract Method — розбити довгий метод на кілька коротких зі зрозумілими назвами
  • Extract Class — виділити пов'язану групу полів і методів в окремий клас
  • Replace Conditional with Polymorphism — замінити switch на ієрархію класів
  • Introduce Parameter Object — об'єднати групу параметрів в об'єкт
  • Replace Inheritance with Delegation — замінити extends на композицію

Інструменти IDE автоматизують багато технік рефакторингу. Android Studio і IntelliJ IDEA пропонують вбудовані рефакторинги: Extract Method, Extract Interface, Pull Members Up, Encapsulate Fields. Xcode (починаючи з версії 14) поліпшив підтримку рефакторингу для Swift. Використання автоматичних рефакторингів знижує ризик помилок порівняно з ручним копіюванням коду.

Code Smell у мобільній розробці

Мобільна розробка додає свої специфічні запахи, пов'язані з платформеними обмеженнями. В Android це витік Context, незакриті Cursor, неправильне використання Lifecycle. В iOS — retain cycle через замикання, неправильна робота з Auto Layout, гігантські ViewController. Ці запахи не тільки погіршують підтримуваність, але й безпосередньо впливають на продуктивність і стабільність застосунку.

Callback Hell — характерний запах для коду, що працює з асинхронними операціями. Вкладені колбеки (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 — це те саме, що баг?

Ні — Code Smell не є помилкою. Код із запахом працює коректно, але його важко підтримувати, змінювати та тестувати. Баг — це некоректна поведінка, запах — попередження про потенційні проблеми в майбутньому.

Скільки запахів виділив Мартін Фаулер?

22 запахи у другому виданні книги «Refactoring» (2019). Серед них Long Method, Large Class, Primitive Obsession, Data Clumps, Switch Statements, Speculative Generality та інші. Спільнота доповнила список десятками нових запахів для сучасних парадигм і платформ.

Який інструмент найкраще знаходить Code Smell?

Комбінація дає найкращий результат: Detekt (Android/Kotlin), SwiftLint (iOS), SonarQube (обидва) для автоматичного аналізу та код-рев'ю для семантичних запахів. Жоден інструмент не знаходить 100% проблем — людський досвід залишається вирішальним.

Чи можна ігнорувати Code Smell?

Можна, якщо код змінюється рідко або буде повністю переписаний найближчим часом. Однак накопичення запахів перетворюється на технічний борг: кожна нова зміна дається все важче, а вартість виправлення зростає експоненційно.

Чи є запахи, специфічні для SwiftUI та Jetpack Compose?

Так — Declarative-фреймворки породили нові запахи: гігантські @State-блоки, неправильна робота з повторними рендерами, надмірні recomposition, відсутність Extraction в окремі View. Для SwiftUI типовий запах — Massive View із десятками @State змінних.

Підсумки

  • Code Smell — поверхнева ознака глибокої проблеми в коді, що не є багом, але знижує підтримуваність
  • Long Method і Large Class — найчастіші запахи в мобільній розробці, що потребують Extract Method і Extract Class
  • Duplicate Code — дублювання логіки, яке подвоює роботу при кожній зміні
  • Feature Envy і Switch Statements — ознаки неправильного розподілу відповідальності між класами
  • Специфічні запахи — God Activity, Giant ViewController, Leaking Context — унікальні для мобільних платформ
  • Рефакторинг без тестів небезпечний: спочатку Characterisation Tests, потім маленькі кроки з комітами
  • Статичний аналіз (Detekt, SwiftLint) автоматизує пошук, але не замінює код-рев'ю

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також