День из жизни Kotlin-разработчика: код, кофе и корутины

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

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

Начать курс

Утро начинается не с кофе, а с Merge Request'а

7:45 утра. Телефон вибрирует — это не будильник, это GitLab прислал уведомление: ваш вчерашний Merge Request прошёл CI/CD пайплайн. Пока чайник закипает, я открываю ноутбук и вижу зелёную галочку. Первая победа дня. Доброе утро, Kotlin.

Меня зовут Алексей, я Kotlin-разработчик в продуктовой компании. Последние три года я пишу на Kotlin под Android и немного на бэкенде с Ktor. Если вы думаете, что работа программиста — это бесконечное "Hello World", то сегодня я разрушу этот миф. Добро пожаловать в мой реальный рабочий день.

8:30 — Daily Standup: синхронизация и контекст

Первое, что происходит — ежедневный стендап. Мы собираемся в Zoom, но без лишних "вчера-сегодня-проблемы". У нас лаконично: что сделано, что в плане, есть ли блокеры. Сегодня я работаю над фичей Push-уведомлений для пользователей.

— Вчера закончил рефакторинг NotificationService, переписал на корутины. Сегодня подключаю Firebase Cloud Messaging и тестирую Flow. Блокеров нет.

Кстати, именно на стендапах часто рождаются лучшие идеи по архитектуре. Например, на прошлой неделе коллега предложил заменить RxJava на Kotlin Flow — и мы сократили количество boilerplate кода на 40%. Вот как это выглядит сейчас:

// Вместо старого RxJava:
// Observable.just(data).subscribeOn(Schedulers.io())

// Используем Kotlin Flow:
fun observeNotifications(): Flow<Notification> = flow {
    val service = NotificationService()
    while (true) {
        emit(service.getNextNotification())
        delay(5000) // опрос каждые 5 секунд
    }
}.flowOn(Dispatchers.IO)

Разница очевидна: читаемость, безопасность типов и никакого callback hell.

9:15 — Разработка: пишем код на Kotlin

После стендапа я ныряю в код. Сегодня задача — реализовать обработку входящих push-уведомлений. Использую Kotlin Coroutines и StateFlow для управления состоянием UI. Вот как выглядит мой ViewModel:

class PushViewModel(
    private val repository: NotificationRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<PushUiState>(PushUiState.Loading)
    val uiState: StateFlow<PushUiState> = _uiState.asStateFlow()

    fun loadNotifications() {
        viewModelScope.launch {
            repository.getNotifications()
                .catch { e ->
                    _uiState.value = PushUiState.Error(e.message ?: "Unknown error")
                }
                .collect { notifications ->
                    _uiState.value = PushUiState.Success(notifications)
                }
        }
    }
}

sealed class PushUiState {
    object Loading : PushUiState()
    data class Success(val data: List<Notification>) : PushUiState()
    data class Error(val message: String) : PushUiState()
}

Обратите внимание: я использую sealed class для UI-состояния. Это стандартная практика в современной Android-разработке на Kotlin. Никаких if-else цепочек — только чистая типизация.

Кстати, если вы новичок в Kotlin, советую обратить внимание на Scope functions: let, apply, also. Они делают код лаконичнее. Например:

// Без scope functions
val user = User()
user.name = "Alex"
user.age = 30
saveUser(user)

// С apply
val user = User().apply {
    name = "Alex"
    age = 30
}
saveUser(user)

11:00 — Code Review: учимся у коллег

После обеда (да, я обедаю в 11 — привычка с удалёнки) я открываю Merge Request от коллеги. Он переписывает модуль авторизации на Kotlin Multiplatform. Читаю код и вижу интересный момент:

// Было (только Android):
class AuthRepository(private val api: ApiService) {
    suspend fun login(email: String, password: String): Result<User>
}

// Стало (KMP):
expect class AuthRepository {
    suspend fun login(email: String, password: String): Result<User>
}

// В Android-модуле:
actual class AuthRepository(private val api: ApiService) {
    actual suspend fun login(email: String, password: String): Result<User> {
        // логика для Android
    }
}

Я комментирую: "Отличное использование expect/actual, но давай добавим sealed class Result вместо стандартного kotlin.Result — будет безопаснее". Коллега соглашается. Code review — это не критика, это совместное улучшение кода.

13:00 — Бэкенд на Ktor: микросервис на Kotlin

Во второй половине дня я переключаюсь на бэкенд. Мы используем Ktor — фреймворк от JetBrains. Пишу простой эндпоинт для получения списка уведомлений:

fun Application.notificationModule() {
    routing {
        get("/notifications") {
            val userId = call.parameters["userId"] ?: return@get call.respondText(
                "Missing userId",
                status = HttpStatusCode.BadRequest
            )
            val notifications = notificationService.getByUserId(userId)
            call.respond(notifications)
        }
    }
}

Ktor на Kotlin — это элегантно. Никакой магии аннотаций, всё явно. Если вы пишете на Kotlin бэкенд, рекомендую попробовать Ktor вместо Spring Boot — он легче и быстрее стартует.

15:00 — Тестирование: пишем Unit-тесты

Без тестов никуда. Я пишу тесты на Kotlin Test для своей ViewModel. Использую Turbine — библиотеку для тестирования Flow:

@Test
fun `when load notifications success then show success state`() = runTest {
    val repository = FakeNotificationRepository()
    val viewModel = PushViewModel(repository)

    viewModel.loadNotifications()

    val state = viewModel.uiState.take(2).toList()
    assertTrue(state[0] is PushUiState.Loading)
    assertTrue(state[1] is PushUiState.Success)
}

Тесты на Kotlin пишутся с удовольствием. Kotlin Test + MockK — идеальное комбо. Кстати, MockK понимает корутины из коробки, в отличие от Mockito.

17:00 — Рефакторинг и документация

Последний час я посвящаю рефакторингу. Нашёл в коде старый Listener на Java, который пора переписать на Kotlin:

// Java (старый код):
public interface OnDataLoadedListener {
    void onSuccess(List<Data> data);
    void onError(Exception e);
}

// Kotlin:
typealias OnDataLoaded = (Result<List<Data>>) -> Unit

Одна строка вместо целого интерфейса. Kotlin — это язык,

Блоги

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