Топ ошибок начинающих Kotlin разработчиков: как их избежать и писать чистый код
Kotlin — современный, лаконичный и безопасный язык, который быстро завоевал популярность в мире Android-разработки и бэкенда. Однако его гибкость и синтаксический сахар часто вводят новичков в заблуждение. В этой статье мы разберём 4 типичные ошибки, которые совершают начинающие разработчики на Kotlin, и покажем, как их исправить с помощью примеров кода.
1. Игнорирование null safety и неправильное использование оператора !!
Одна из главных фишек Kotlin — null safety. Но многие новички, привыкшие к Java, продолжают использовать !! (not-null assertion operator) бездумно. Это прямой путь к NullPointerException.
Плохой пример:
fun getUserName(user: User?): String {
return user!!.name // Если user == null, будет NPE
}
Хороший пример (безопасная работа с nullable):
fun getUserNameSafe(user: User?): String {
return user?.name ?: "Неизвестный пользователь"
}
Используйте ?.let{}, ?: (элвис-оператор) и ?.run{} для безопасного доступа. Оператор !! оправдан только в редких случаях, когда вы на 100% уверены, что значение не null (например, в тестах).
2. Неправильное использование sealed классов и when
Sealed классы — мощный инструмент для моделирования ограниченных иерархий. Но начинающие часто забывают, что when должен быть исчерпывающим (exhaustive) при работе с sealed классами. Или, наоборот, используют else там, где он не нужен.
Плохой пример (пропущена ветка):
sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
}
fun handleResult(result: Result) {
when (result) {
is Result.Success -> println(result.data)
// Ветка для Error отсутствует — будет предупреждение
}
}
Хороший пример (исчерпывающий when):
fun handleResultExhaustive(result: Result) {
when (result) {
is Result.Success -> println(result.data)
is Result.Error -> println(result.message)
}
// Компилятор не требует else, так как все варианты покрыты
}
Если вы добавляете новую ветку в sealed класс, компилятор подскажет, где нужно обновить when. Это защищает код от ошибок во время рефакторинга.
3. Злоупотребление корутинами: запуск без контекста и игнорирование structured concurrency
Корутины — отличный способ работы с асинхронностью, но новички часто запускают их без правильного контекста или забывают про structured concurrency. Это приводит к утечкам памяти и неконтролируемым фоновым задачам.
Плохой пример (глобальный scope и потеря контекста):
fun loadData() {
GlobalScope.launch { // Плохо! Живёт, пока живёт приложение
val data = fetchData() // Может выполняться после закрытия Activity
updateUI(data) // Ошибка: UI уже уничтожен
}
}
Хороший пример (structured concurrency с viewModelScope):
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = fetchData()
updateUI(data) // Автоматически отменится при очистке ViewModel
}
}
}
Всегда используйте coroutineScope, viewModelScope или свой собственный CoroutineScope с чётким жизненным циклом. Избегайте GlobalScope в production-коде.
4. Перегрузка операторов без понимания семантики
Kotlin позволяет перегружать операторы (+, -, in и др.) для своих классов. Это удобно, но новички часто злоупотребляют этой возможностью, делая код нечитаемым и неинтуитивным.
Плохой пример (неестественная перегрузка):
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector): Vector {
return Vector(x + other.x, y + other.y)
}
// Сложение векторов — логично. А что насчёт умножения?
operator fun times(other: Vector): Vector {
return Vector(x * other.x, y * other.y) // Покомпонентное умножение? Или скалярное?
}
}
Проблема: умножение векторов может быть интерпретировано по-разному (скалярное, векторное, покомпонентное). Лучше использовать именованные функции:
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
fun dot(other: Vector): Int = x * other.x + y * other.y
fun cross(other: Vector): Vector = Vector(x * other.x, y * other.y) // Или другой смысл
}
Перегружайте операторы только тогда, когда их значение очевидно и не вызывает разночтений. Для сложных операций используйте функции с понятными именами.
Заключение
Kotlin — язык, который прощает многие ошибки, но не все. Чтобы писать надёжный и поддерживаемый код, помните:
- Избегайте
!!— используйте безопасные вызовы и элвис-оператор. - Делайте
whenисчерпывающим для sealed классов. - Всегда привязывайте корутины к жизненному циклу (structured concurrency).
- Перегружайте операторы только с ясной семантикой.
Освоив эти принципы, вы не только избавитесь от типичных ошибок, но и сделаете свой код более чистым и понятным. Удачи в изучении Kotlin!