Почему отладка Kotlin — это искусство, а не рутина?
Представьте: вы пишете элегантный код на Kotlin, используете корутины, null-безопасность и extension-функции. Всё выглядит идеально, пока приложение не падает с NullPointerException в самом неожиданном месте. Или ещё хуже — корутина просто «засыпает» навсегда, не оставляя следов. Знакомо?
Отладка Kotlin кода — это не просто поиск багов. Это умение «подсматривать» за работой вашего приложения в реальном времени, понимать, как работают корутины, и быстро находить узкие места. В этой статье мы разберём продвинутые техники отладки Kotlin, которые сэкономят вам часы нервов и сделают код стабильнее.
Базовые инструменты отладки Kotlin в IntelliJ IDEA
IntelliJ IDEA — это не просто IDE, это настоящий швейцарский нож для Kotlin-разработчика. Давайте начнём с того, что лежит на поверхности, но часто используется неправильно.
Точки останова: больше, чем просто пауза
Обычная точка останова (line breakpoint) — это классика. Но Kotlin предлагает гораздо больше:
- Conditional breakpoints — срабатывают только при выполнении условия. Например, остановиться, когда
userId == 42. - Exception breakpoints — ловят исключения до того, как они упадут в лог.
- Method breakpoints — срабатывают на входе и выходе из функции. Идеально для отладки сложных цепочек вызовов.
Пример настройки conditional breakpoint:
fun processOrder(order: Order) {
// Ставим breakpoint и добавляем условие: order.amount > 1000
val discount = calculateDiscount(order.amount)
// ...
}
Просто кликните правой кнопкой мыши по точке останова и введите условие. Экономит кучу времени!
Отладка корутин: заглядываем внутрь async-мира
Корутины — это мощно, но отладка может превратиться в кошмар. IntelliJ IDEA (начиная с версии 2020.3) предлагает Coroutines Debugger. Он показывает:
- Состояние каждой корутины (Active, Suspended, Completed).
- Стек вызовов для каждой корутины.
- Значения локальных переменных внутри корутины.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job1 = launch {
delay(1000)
println("Job 1 done")
}
val job2 = launch {
delay(500)
println("Job 2 done")
}
// Поставьте breakpoint здесь и посмотрите вкладку "Coroutines"
job1.join()
job2.join()
}
Совет: включите опцию Show coroutines в панели отладки — это спасёт вас от головной боли с асинхронным кодом.
Продвинутые техники: отладка без боли
Теперь перейдём к техникам, которые отличают новичка от профи.
Логирование как первый эшелон обороны
Не всегда можно поставить точку останова (например, в продакшене). Тут на помощь приходит логирование. Но не просто println(), а осмысленное логирование.
import org.slf4j.LoggerFactory
private val log = LoggerFactory.getLogger(MyService::class.java)
fun fetchUserData(userId: String): User? {
log.debug("Fetching user data for userId: {}", userId)
return try {
val user = repository.findById(userId)
log.info("User found: {}", user?.name)
user
} catch (e: Exception) {
log.error("Failed to fetch user: {}", userId, e)
null
}
}
Используйте уровни логирования: TRACE для деталей, DEBUG для отладки, INFO для важных событий. И никогда не оставляйте println() в продакшене — это моветон.
Профилирование памяти: охота за утечками
Kotlin, благодаря JVM, подвержен утечкам памяти. Используйте встроенный Profiler в IntelliJ IDEA Ultimate. Он покажет:
- Количество объектов в куче.
- Ссылки на объекты (чтобы понять, кто держит объект).
- GC activity (сборку мусора).
Типичный сценарий: вы забыли отменить корутину, и она держит ссылку на Activity. Profiler покажет это в виде Retained Size. Просто сделайте snapshot и сравните до и после действия.
Unit-тесты как отладка наоборот
Написание тестов — это тоже форма отладки. Вы проверяете гипотезы о поведении кода. Используйте JUnit 5 с Kotlin:
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class CalculatorTest {
@Test
fun `addition should work correctly`() {
val result = Calculator.add(2, 3)
assertEquals(5, result)
}
}
Пишите тесты до того, как нашли баг. Это называется TDD (Test-Driven Development). И да, это работает — проверено тысячами разработчиков.
Инструменты, которые стоит держать под рукой
IntelliJ IDEA — это база, но есть и другие инструменты, которые облегчат отладку Kotlin.
Kotlin REPL: песочница для экспериментов
Встроенный REPL (Read-Eval-Print Loop) позволяет быстро проверить кусок кода без компиляции всего проекта. Нажмите Tools → Kotlin → Kotlin REPL. Идеально для проверки lambda-выражений или работы с коллекциями.
// В REPL
val numbers = listOf(1, 2, 3, 4, 5)
numbers.filter { it % 2 == 0 }.map { it * 2 }
// Результат: [4, 8]
Kotlin Debugger в командной строке
Если вы работаете без IDE (например, на сервере), используйте Kotlin Script с флагом -Xdebug:
kotlin -J -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 myScript.kts
Затем подключитесь через IntelliJ IDEA к удалённой JVM через Remote JVM Debug. Это спасёт вас, когда баг воспроизводится только на сервере.
Типичные ошибки в Kotlin и как их отлавливать
Давайте разберём три самых частых сценария, которые валят Kotlin-код.
NullPointerException (несмотря на null-безопасность)
Да, Kotlin обещал нам безопасность, но if вы используете !! — вы сами напрашиваетесь. Как отловить?