Рефакторинг legacy кода на Kotlin: от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему legacy код — это не приговор

Каждый разработчик сталкивался с legacy кодом — той самой «чёрной коробкой», которую боишься трогать, потому что «оно работает, и не надо лезть». Однако, когда кодовая база на Kotlin (или Java, которую постепенно мигрируют) начинает тормозить развитие, накапливать баги и пугать новых членов команды, приходит время рефакторинга. Рефакторинг legacy кода на Kotlin — это не просто переписывание, а системный процесс улучшения внутренней структуры без изменения внешнего поведения.

В этой статье мы разберём полный путь: от анализа текущего состояния до внедрения современных практик Kotlin. Вы узнаете, как безопасно преобразовать устаревший код, используя возможности языка (нулабельность, extension functions, sealed classes) и не сломать продакшен.

1. Диагностика: как оценить масштаб legacy кода

Прежде чем браться за рефакторинг, необходимо понять, с чем вы имеете дело. Legacy код на Kotlin часто приходит из проектов, которые начинали писать на Java, а затем частично мигрировали. Типичные признаки:

  • God-объекты — классы, которые делают всё: от парсинга до бизнес-логики.
  • Отсутствие тестов — код не покрыт unit-тестами, и любое изменение рискованно.
  • Мутабельные данные — обилие var вместо val, mutable списки, которые передаются между модулями.
  • Null-кошмар — !! оператор, разбросанный по всему коду, и бесконечные проверки if (x != null).

Для диагностики используйте инструменты: Detekt (статический анализ Kotlin), SonarQube (метрики качества) и IntelliJ IDEA Inspections. Создайте чек-лист проблемных мест и приоритизируйте их по риску и частоте использования.

1.1. Составление карты зависимостей

Возьмите лист бумаги или используйте PlantUML для визуализации связей между классами. Если граф зависимостей напоминает паутину — это звоночек. Особое внимание уделите циклическим зависимостям: они делают код хрупким и сложным для тестирования.

2. Стратегия рефакторинга: пошаговый план действий

Рефакторинг без плана — это путь к катастрофе. Используйте подход «Техника малых шагов» (baby steps). Алгоритм действий:

  1. Задокументируйте поведение — напишите характеристические тесты (characterization tests), которые фиксируют текущее поведение системы.
  2. Изолируйте изменения — работайте в отдельной ветке, используйте feature toggles для сложных участков.
  3. Рефакторинг по паттернам — применяйте проверенные техники (Extract Method, Replace Conditional with Polymorphism, Introduce Parameter Object).
  4. Постепенная миграция — не пытайтесь переписать всё сразу. Используйте @Deprecated аннотации для старых методов.

2.1. Пример: замена if-else на when и sealed classes

Типичный legacy код на Kotlin содержит цепочки if-else для обработки состояний:

// Плохо: мутабельный флаг и if-else
var status = "active"
if (status == "active") {
    // ...
} else if (status == "inactive") {
    // ...
} else {
    throw Exception("Unknown status")
}

Рефакторинг с sealed классами:

// Хорошо: типобезопасная модель
sealed class Status {
    object Active : Status()
    object Inactive : Status()
    data class Custom(val reason: String) : Status()
}

fun handleStatus(status: Status) = when (status) {
    is Status.Active -> // обработка
    is Status.Inactive -> // обработка
    is Status.Custom -> // обработка с reason
}

Это устраняет необходимость в else и делает код расширяемым.

3. Безопасная работа с nullable и коллекциями

Legacy код часто полон null проверок и мутабельных коллекций. Kotlin предлагает элегантные решения:

  • Замените !! на ?.let или ?: — это предотвратит NPE в рантайме.
  • Используйте List<T> вместо MutableList<T> — если коллекция не должна изменяться, сделайте её иммутабельной.
  • Примените scope-функции — apply, with, run для группировки операций.

3.1. Пример рефакторинга коллекций

// До: мутабельный список и цикл
val names = mutableListOf<String>()
for (user in users) {
    if (user.name != null) {
        names.add(user.name)
    }
}

// После: функциональный стиль
val names = users
    .mapNotNull { it.name }
    .toList() // иммутабельный список

Такой код короче, безопаснее и легче читается.

4. Тестирование и интеграция: как не сломать продакшен

Рефакторинг без тестов — это игра в русскую рулетку. Вот обязательные шаги:

  • Покрытие тестами критических путей — используйте JUnit 5 и MockK (аналог Mockito для Kotlin).
  • Золотые тесты (snapshot testing) — для участков, где сложно написать assert, сохраняйте результат и сравнивайте с эталоном.
  • CI/CD пайплайн — добавьте линтер (Detekt) и запуск тестов перед мержем.

4.1. Пример характеристического теста

@Test
fun `legacy method should return correct format`() {
    val legacy = LegacyService()
    val result = legacy.oldMethod("test")
    // Фиксируем текущее поведение
    assertEquals("TEST_2023", result)
}

После рефакторинга тест должен проходить, если поведение не изменилось.

Заключение: ре

Блоги

Рекомендаций по книгам