Kotlin с нуля: основы языка и практическое программирование

Курс познакомит с синтаксисом Kotlin, ключевыми концепциями языка и стандартной библиотекой. Вы научитесь писать чистый, безопасный и идиоматичный код, а также применять Kotlin на практике через небольшие задания.

1. Введение в Kotlin и настройка окружения

Введение в Kotlin и настройка окружения

Что такое Kotlin и где он используется

Kotlin — это современный язык программирования, разработанный компанией JetBrains. Его часто выбирают за лаконичный синтаксис, хорошую читаемость кода и совместимость с экосистемой Java.

Kotlin применяют в нескольких основных направлениях:

  • Kotlin/JVM — разработка серверных приложений, консольных утилит и библиотек под Java Virtual Machine (JVM)
  • Android-разработка — официальный рекомендованный язык для Android
  • Kotlin Multiplatform — общая логика для разных платформ (например, Android и iOS)
  • Kotlin/JS — разработка под JavaScript (например, веб)
  • В этом курсе мы начнём с самого распространённого и удобного пути для старта: Kotlin/JVM.

    Почему Kotlin удобен для старта

    Несколько причин, почему Kotlin подходит новичкам:

  • Понятный и строгий синтаксис: язык помогает писать код аккуратно
  • Много обучающих материалов и активное сообщество
  • Инструменты от JetBrains: отличная поддержка в IDE
  • Совместимость с Java: при желании можно использовать Java-библиотеки
  • Важно: совместимость с Java не означает, что вам обязательно нужно знать Java прямо сейчас. Мы будем идти от простого к сложному.

    Как мы будем писать и запускать Kotlin-код

    Есть несколько вариантов работы с Kotlin. Для обучения важно выбрать способ, который:

  • легко установить
  • удобно писать код
  • просто запускать и отлаживать
  • В курсе будем опираться на IntelliJ IDEA (самый удобный вариант для Kotlin/JVM) и при необходимости кратко затронем запуск из командной строки.

    !Общая схема: как код на Kotlin превращается в запускаемую программу на JVM

    Что нужно установить

    JDK

    JDK (Java Development Kit) — набор инструментов для разработки под JVM. Kotlin/JVM использует JVM, поэтому JDK нужен для компиляции и запуска программ.

    Рекомендуемый вариант для обучения:

  • JDK 17 (LTS-версия, широко поддерживается)
  • Где взять:

  • Oracle JDK
  • Eclipse Temurin (Adoptium)
  • Любой из вариантов подойдёт. Если не хочется разбираться в лицензиях и вариантах, часто выбирают Eclipse Temurin.

    IntelliJ IDEA

    IntelliJ IDEA — среда разработки (IDE), в которой удобно писать Kotlin. Подойдёт версия:

  • IntelliJ IDEA Community (бесплатная)
  • Скачать:

  • IntelliJ IDEA
  • Kotlin (как язык)

    Отдельно устанавливать Kotlin вручную обычно не нужно, если вы создаёте проект в IntelliJ IDEA: нужная версия Kotlin подключается автоматически через систему сборки.

    Быстрый старт без установки (если хотите попробовать сразу)

    Если вы хотите посмотреть синтаксис и выполнить код без установки IDE, используйте браузер:

  • Kotlin Playground
  • Это полезно для экспериментов, но для полноценной разработки в курсе будем использовать IntelliJ IDEA.

    Настройка проекта в IntelliJ IDEA

    Наша цель — создать обычное приложение Kotlin/JVM, которое запускается как программа.

    Создание проекта

  • Откройте IntelliJ IDEA.
  • Выберите New Project.
  • В списке слева выберите Kotlin.
  • В шаблонах выберите Console Application.
  • Убедитесь, что выбран Build system: Gradle.
  • Выберите JDK (например, 17). Если JDK не найден, IDE предложит скачать его.
  • Нажмите Create.
  • Почему Gradle:

  • это стандартный инструмент сборки для JVM-проектов
  • он сам скачает нужные зависимости
  • проекты с Gradle легко переносить между компьютерами
  • Как устроен проект (минимально необходимое)

    После создания вы увидите структуру файлов. Важно понимать несколько вещей:

  • src/main/kotlin — здесь обычно лежит исходный код на Kotlin
  • build.gradle.kts — файл настроек сборки (на Kotlin DSL)
  • settings.gradle.kts — имя проекта и настройки Gradle
  • Пока достаточно запомнить: пишем код в src/main/kotlin, запускаем через IDE.

    Первая программа: Hello, World!

    Откройте файл Main.kt (он обычно создаётся автоматически) и убедитесь, что там есть функция main.

    Что здесь происходит:

  • fun — ключевое слово для объявления функции
  • main — точка входа в программу (с неё начинается выполнение)
  • println(...) — печать строки в консоль с переносом строки
  • Запуск программы

    Обычно есть два простых способа:

  • нажать зелёную кнопку запуска рядом с main
  • нажать Run в верхнем меню IDE
  • После запуска внизу откроется окно Run, где вы увидите вывод:

  • Hello, World!
  • Частые проблемы и их решение

    IDE не видит JDK или не запускает проект

    Проверьте:

  • выбран ли JDK в настройках проекта
  • установлен ли JDK вообще
  • Где посмотреть JDK в IntelliJ IDEA:

  • Откройте FileProject Structure.
  • В разделе Project проверьте Project SDK.
  • Не скачиваются зависимости Gradle

    Возможные причины:

  • нет доступа в интернет
  • корпоративный прокси
  • временные проблемы репозиториев
  • Что можно попробовать:

  • Нажать кнопку Reload All Gradle Projects (обычно справа в окне Gradle).
  • Перезапустить IDE.
  • Проверить настройки прокси в IDE.
  • Запускается не то, что вы ожидаете

    Убедитесь, что:

  • запускаете именно нужную конфигурацию Run
  • изменяете тот файл, который запускаете
  • Альтернатива: запуск Kotlin из командной строки (кратко)

    Иногда полезно понимать, что IDE делает за вас. В реальных проектах чаще используют Gradle, но в общих чертах процесс такой:

  • Есть файл Main.kt.
  • Kotlin-компилятор превращает код в байткод для JVM.
  • JVM запускает результат.
  • Если позже захотите углубиться, официальный способ установки компилятора описан в документации:

  • Kotlin command-line compiler
  • В курсе мы не будем требовать запуск из командной строки на старте, чтобы не усложнять обучение.

    Что дальше по курсу

    Теперь, когда окружение готово и вы умеете запускать программу, можно переходить к базовым элементам языка:

  • переменные и типы
  • операторы и выражения
  • условия и циклы
  • функции и работа со строками
  • Главная цель следующего шага — начать уверенно писать маленькие программы и понимать, как они выполняются.

    2. Базовый синтаксис: типы, переменные, операторы

    Базовый синтаксис: типы, переменные, операторы

    В прошлой статье вы настроили окружение и запустили первую программу в IntelliJ IDEA. Теперь закрепим базовые элементы языка Kotlin, чтобы вы могли уверенно писать небольшие консольные программы: переменные, типы и операторы.

    Минимальная структура программы

    Точка входа в приложение Kotlin/JVM — функция main.

  • fun объявляет функцию
  • main запускается первой
  • println печатает текст в консоль и добавляет перенос строки
  • Переменные: val и var

    В Kotlin есть два способа объявлять переменные.

  • val — значение нельзя изменить после присваивания (неизменяемая переменная)
  • var — значение можно менять (изменяемая переменная)
  • Почему в Kotlin часто предпочитают val:

  • код проще понимать, когда значения не меняются
  • меньше неожиданных ошибок из-за изменений состояния
  • !Схема различий между val и var

    Имена переменных: базовые правила

  • используйте стиль camelCase: userName, totalPrice
  • имя должно отражать смысл: count, maxValue, isActive
  • для булевых значений удобно начинать с is, has, can: isReady, hasAccess
  • Типы данных: что хранится в переменной

    Тип — это “вид данных”, который хранится в переменной: число, строка, логическое значение и т.д. Kotlin — язык со строгой типизацией: типы важны, но часто их можно не писать явно, потому что работает вывод типа.

    Вывод типа (type inference)

    Явное указание типа

    Иногда полезно написать тип явно: для читаемости или когда значение пока неизвестно.

    Базовые типы, которые встречаются чаще всего

    | Тип | Пример значения | Для чего обычно используют | |---|---:|---| | Int | 42 | целые числа в большинстве задач | | Long | 42L | большие целые числа (например, миллисекунды, идентификаторы) | | Double | 3.14 | дробные вычисления | | Boolean | true, false | флаги, условия | | Char | 'A' | один символ | | String | "text" | текст |

    Важно:

  • String пишется в двойных кавычках: "..."
  • Char — в одинарных: 'A'
  • Long-литерал часто записывают с суффиксом L: 1000L
  • Официальная справка по типам:

  • Basic types
  • Преобразования типов

    В Kotlin нет “магических” неявных преобразований между числовыми типами. Например, Int сам по себе не станет Double.

    Часто используемые методы:

  • toInt(), toLong(), toDouble()
  • Операторы: как мы “действуем” над значениями

    Операторы — это символы и конструкции, которые выполняют действия над данными: складывают, сравнивают, объединяют условия.

    Арифметические операторы

  • + сложение
  • - вычитание
  • * умножение
  • / деление
  • % остаток от деления
  • Особенность: если оба числа целые (Int, Long), то / даёт целую часть. Для дробного результата хотя бы одно число должно быть Double.

    Операторы присваивания

  • = присваивание
  • +=, -=, *=, /=, %= — “обновить значение”
  • Операторы сравнения

    Результат сравнения — это Boolean.

  • == равно
  • != не равно
  • <, >, <=, >= меньше/больше
  • Важно: в Kotlin == сравнивает значения (структурное равенство). Для сравнения ссылок (когда нужно узнать, один и тот же ли это объект) используется ===, но на старте чаще всего достаточно ==.

    Логические операторы

    Используются с Boolean:

  • && логическое И (оба условия должны быть true)
  • || логическое ИЛИ (хотя бы одно условие true)
  • ! логическое НЕ
  • Строки и шаблоны строк

    В Kotlin удобно формировать строки через шаблоны.

    Правила:

  • {...} позволяет вставить выражение (например, age + 1)
  • Конкатенация (склеивание) строк тоже возможна через +, но шаблоны обычно читаются лучше.

    Частые ошибки новичков

  • Попытка изменить val
  • Ожидание, что 10 / 3 даст дробь (для Int это будет 3)
  • Смешивание числовых типов без преобразования (Int и Double)
  • Путаница между "A" (это String) и 'A' (это Char)
  • Что дальше

    Теперь у вас есть база, чтобы писать выражения и хранить данные в переменных. Следующий логичный шаг — научиться управлять ходом программы: условия и циклы, где операторы сравнения и логика используются чаще всего.

    Полезная официальная справка по синтаксису:

  • Basic syntax
  • 3. Управляющие конструкции и функции

    Управляющие конструкции и функции

    В прошлой статье вы познакомились с типами, переменными и операторами. Теперь пора научиться управлять ходом выполнения программы и упаковывать логику в функции.

    Управляющие конструкции отвечают на вопросы:

  • Что делать, если условие истинно или ложно?
  • Как повторить действия несколько раз?
  • Как выбрать один вариант из нескольких?
  • А функции помогают:

  • не дублировать код
  • давать понятные имена кускам логики
  • проще тестировать и поддерживать программу
  • !Блок-схема, показывающая ветвление (if/when) и повторение (циклы)

    Условные конструкции

    if как выражение

    В Kotlin if — это не только конструкция управления, но и выражение, то есть оно может возвращать значение.

    Почему это удобно:

  • можно сразу присвоить результат в val
  • код становится короче и читаемее
  • Важно:

  • результатом if становится последнее выражение внутри блока
  • ветка else обязательна, если вы хотите присвоить результат в переменную
  • Цепочки else if

    Когда условий несколько, используйте else if.

    while

    while подходит, когда число повторов заранее неизвестно.

    break и continue

  • break — полностью завершает цикл
  • continue — пропускает текущую итерацию и переходит к следующей
  • Здесь:

  • name: String — параметр и его тип
  • функция ничего не возвращает, значит её возвращаемый тип — Unit (обычно его не пишут)
  • Возврат значения

    Чтобы вернуть значение, укажите тип после ) и используйте return.

    Короткая запись (expression body)

    Если функция состоит из одного выражения, её можно записать короче.

    Unit и явный return

    Unit означает: значимого результата нет. Это примерно как void в других языках.

    Параметры по умолчанию и именованные аргументы

    Kotlin позволяет задавать значения по умолчанию.

    Также можно вызывать функцию, указывая имена аргументов. Это повышает читаемость.

    Здесь:

  • when выбирает размер скидки по условиям
  • функция возвращает Double
  • именованные аргументы делают вызов понятнее
  • Что дальше

    Теперь вы умеете:

  • ветвить выполнение с if и when
  • повторять действия циклами for, while, do-while
  • управлять циклами через break и continue
  • писать функции, возвращать значения, использовать параметры по умолчанию и vararg
  • Следующий шаг обычно — научиться работать с коллекциями (списки, множества, словари) и строками более глубоко, чтобы писать более полезные программы.

    4. Коллекции и работа со стандартной библиотекой

    Коллекции и работа со стандартной библиотекой

    В прошлых статьях вы освоили переменные и типы, управляющие конструкции (if, when, циклы) и функции. Следующий шаг к практическим программам — научиться работать с набором данных: списками покупок, оценками, пользователями, настройками.

    Для этого в Kotlin есть коллекции и богатая стандартная библиотека, которая позволяет обрабатывать данные коротко и читаемо.

    !Схема, помогающая быстро запомнить виды коллекций и различие read-only и mutable

    Что такое коллекции

    Коллекция — это структура данных, которая хранит несколько значений.

    В базовых задачах чаще всего используются:

  • List — упорядоченная коллекция, повторы допускаются
  • Setповторы не допускаются
  • Map — хранит пары ключ → значение
  • Ключевая особенность Kotlin: часто вы работаете с read-only интерфейсами (List, Set, Map), а при необходимости изменения берёте mutable варианты (MutableList, MutableSet, MutableMap).

    Важно понимать:

  • List не гарантирует, что исходные данные физически нельзя изменить где-то ещё, но вы через эту ссылку не можете вызвать методы изменения (например, add).
  • MutableList даёт операции изменения: добавление, удаление, замену.
  • Создание коллекций

    Списки: listOf и mutableListOf

    Полезные свойства и операции:

  • size — количество элементов
  • isEmpty() — пустой ли список
  • first(), last() — первый/последний элемент (упадут с ошибкой, если список пуст)
  • firstOrNull(), lastOrNull() — безопасные варианты, возвращают null при пустой коллекции
  • Множества: setOf и mutableSetOf

    Set автоматически убирает повторы.

    Словари: mapOf и mutableMapOf

    Map хранит соответствия ключа значению.

    Практические подсказки:

  • map[key] возвращает значение или null, если ключа нет.
  • Если нужен запасной вариант, используйте элвис-оператор ?:
  • Перебор коллекций

    for по элементам

    Что здесь важно:

  • filter оставил только оплаченные заказы
  • sumOf посчитал сумму по полю amount
  • groupBy разбил заказы по статусам
  • when превратил код статуса в понятную подпись
  • Полезные официальные материалы

  • Документация Kotlin: Коллекции
  • Документация Kotlin: Функции высшего порядка и лямбды
  • Документация Kotlin: Обзор стандартной библиотеки
  • Что дальше

    После коллекций обычно переходят к двум важным темам:

  • классы и объекты (чтобы хранить данные структурно и писать более крупные программы)
  • null-безопасность и работа с ошибками (чтобы программа была надёжной)
  • Коллекции и функции стандартной библиотеки будут использоваться практически в каждом следующем примере кода.

    5. ООП в Kotlin: классы, наследование, интерфейсы

    ООП в Kotlin: классы, наследование, интерфейсы

    До этого в курсе вы писали программы из функций, условий, циклов и обрабатывали данные коллекциями. Теперь сделаем следующий шаг: научимся объединять данные и поведение в модели, которые удобно расширять и поддерживать. Для этого используется объектно-ориентированное программирование (ООП): классы, наследование и интерфейсы.

    Зачем в Kotlin нужно ООП

    ООП помогает, когда программа растёт и данных становится много.

  • Класс описывает что хранит сущность и что она умеет делать
  • Объект это конкретный экземпляр класса
  • Интерфейс описывает контракт: какие функции должен иметь класс
  • Наследование позволяет переиспользовать код и расширять поведение
  • В Kotlin ООП обычно сочетают с функциями стандартной библиотеки, которые вы уже знаете: вы храните объекты в List, фильтруете их через filter, сортируете через sortedBy, суммируете через sumOf.

    !Схема связей: класс, наследование и интерфейс

    Классы: свойства, конструктор, методы

    Простейший класс

    Класс объявляется ключевым словом class.

    Пока класс пустой: у него нет данных и поведения.

    Свойства (properties)

    Свойство это переменная, привязанная к объекту.

  • val в классе означает, что свойство нельзя переназначить после создания объекта
  • var можно изменять
  • Конструктор и init

    Параметры в скобках после имени класса это первичный конструктор. Если нужно выполнить проверку или подготовку данных при создании, используйте блок init.

  • require(condition) бросает исключение, если условие ложно
  • это удобно для ранней валидации данных
  • Методы (функции внутри класса)

    Функции внутри класса описывают поведение объекта.

    Data class: удобные классы для данных

    Во многих задачах класс нужен, чтобы хранить данные и удобно их сравнивать или печатать. Для этого есть data class.

    data class автоматически генерирует:

  • корректный toString()
  • сравнение equals и hashCode по значениям свойств
  • функцию copy(...) для создания копии с изменениями
  • Официальная справка:

  • Документация Kotlin: Classes
  • Документация Kotlin: Data classes
  • Инкапсуляция: модификаторы видимости

    Инкапсуляция это принцип, по которому внутренние детали прячут, а наружу дают понятный интерфейс.

    Основные модификаторы:

  • public доступно везде (в Kotlin это значение по умолчанию)
  • private доступно только внутри класса или файла
  • protected доступно в классе и его наследниках
  • internal доступно внутри одного модуля
  • Пример: снаружи нельзя напрямую менять баланс, только через метод.

    Официальная справка:

  • Документация Kotlin: Visibility modifiers
  • Наследование: расширяем классы

    Наследование позволяет создать новый класс на базе существующего.

    В Kotlin важное правило: класс по умолчанию нельзя наследовать. Чтобы разрешить наследование, базовый класс нужно пометить как open.

    Базовый класс и наследник

    Что здесь важно:

  • open у класса разрешает наследование
  • open у функции разрешает переопределение
  • override обязательно при переопределении
  • Абстрактные классы

    Если базовый класс задаёт общий смысл, но не может дать реализацию некоторых методов, используют abstract.

  • abstract класс нельзя создать напрямую
  • abstract функция обязана быть реализована в наследнике
  • Официальная справка:

  • Документация Kotlin: Inheritance
  • Интерфейсы: контракт вместо жёсткой иерархии

    Интерфейс описывает набор функций и свойств, которые должен поддерживать класс. Главное отличие от абстрактного класса: класс может реализовать несколько интерфейсов.

    Простой интерфейс

    Официальная справка:

  • Документация Kotlin: Interfaces
  • Когда наследование, а когда интерфейс

    Практическое правило для старта:

  • используйте наследование, когда есть отношение является: Manager является Employee
  • используйте интерфейс, когда есть отношение умеет: ConsoleNotifier умеет уведомлять
  • Часто лучше начать с интерфейса: он меньше связывает код и проще расширяется.

    Практический пример: обработка заказов с коллекциями

    Соединим ООП с тем, что вы уже умеете из прошлых тем: коллекции и функции стандартной библиотеки.

    Почему это хорошая архитектура для обучения:

  • Order это data class, удобно хранить и печатать
  • DiscountPolicy это интерфейс, можно легко добавить новые скидки без переписывания Checkout
  • map и sum применяются к списку заказов как в прошлой статье про коллекции
  • Что дальше

    Вы освоили основу ООП в Kotlin:

  • классы, свойства, методы, init
  • data class для моделей данных
  • модификаторы видимости для инкапсуляции
  • наследование с open и переопределением через override
  • интерфейсы как контракты и способ расширять поведение
  • Следующие логичные темы после ООП для уверенной практики:

  • null-безопасность и обработка ошибок
  • обобщения (generics) и более глубокая работа с коллекциями
  • объектные выражения и object для одиночек, фабрик и утилит
  • Полезная ссылка для продолжения:

  • Документация Kotlin: Object declarations
  • 6. Null-safety, исключения и корутины: основы

    Null-safety, исключения и корутины: основы

    После тем про коллекции и ООП вы уже умеете строить модели (data class), обрабатывать списки (map, filter, sumOf) и организовывать код через функции и классы. Чтобы писать надёжные и реально используемые программы, нужно освоить ещё три базовые вещи:

  • Null-safety: как Kotlin помогает избегать NullPointerException
  • Исключения: как сигнализировать об ошибках и корректно их обрабатывать
  • Корутины: как выполнять задачи асинхронно без сложных колбэков и потоков
  • !Быстрая карта операторов null-safety в Kotlin

    Null-safety в Kotlin

    Почему null вообще проблема

    null означает отсутствие значения. Во многих языках ошибка возникает в момент обращения к полю или методу у null. Kotlin решает это на уровне типов: если переменная может быть null, это явно отражено в её типе.

    Nullable и non-null типы

  • String означает: значение не может быть null
  • String? означает: значение может быть null
  • Это сразу влияет на то, какие операции разрешены:

    Безопасный вызов ?.

    Если объект может быть null, используйте ?.. Тогда при null результатом выражения тоже станет null, а не будет падения.

    Цепочки тоже работают:

    Элвис-оператор ?:

    ?: позволяет указать значение по умолчанию, если слева получилось null.

    Частый приём: ранний выход из функции.

    !! — небезопасное утверждение

    Оператор !! говорит компилятору: я уверен, что здесь не null. Если вы ошиблись, будет исключение.

    Правило для старта: используйте !! только когда вы действительно гарантируете не-null (и можете объяснить почему).

    let для работы с не-null значением

    let удобно применять, когда нужно выполнить блок только если значение не null.

  • try содержит код, который может выбросить исключение
  • catch перехватывает исключение определённого типа
  • finally выполняется всегда (полезно для освобождения ресурсов)
  • try как выражение

    Как и if или when, try в Kotlin может возвращать значение.

    Как выбрасывать исключения: throw

    На практике часто используют стандартные функции для проверок:

  • require(...) подходит для проверки аргументов функции
  • check(...) подходит для проверки внутренних инвариантов состояния
  • Свои исключения

    Иногда полезно выразить ошибку отдельным типом.

    Важная особенность Kotlin

    В Kotlin нет checked exceptions, то есть компилятор не заставляет вас явно указывать и перехватывать исключения, как это бывает в некоторых языках. Поэтому важно договориться с собой об обработке ошибок на уровне архитектуры: где вы ловите исключения, а где даёте им подняться выше.

    Официальная справка:

  • Exceptions
  • Корутины: асинхронность простыми средствами

    Зачем нужны корутины

    Корутины помогают выполнять задачи, которые могут ждать:

  • сетевые запросы
  • чтение файлов
  • работа с базой данных
  • задержки и таймеры
  • Если делать это в одном потоке напрямую, программа может “замереть”. Корутины позволяют не блокировать поток, при этом писать код почти как обычный последовательный.

    !Как корутина приостанавливается на delay и продолжает выполнение

    Важно: корутины — это библиотека

    Базовый язык Kotlin поддерживает ключевое слово suspend, но для практической работы обычно используют библиотеку kotlinx.coroutines.

    Для проекта Gradle (Kotlin/JVM) добавьте зависимость в build.gradle.kts:

    Актуальные сведения и официальные материалы:

  • Coroutines overview
  • suspend функции

    Функция с suspend может приостанавливать выполнение корутины без блокировки потока.

    Ключевой момент: suspend функцию нельзя вызвать из обычного кода напрямую, только из другой корутины или suspend функции.

    runBlocking для консольных примеров

    В учебных консольных программах удобно использовать runBlocking, чтобы “мостиком” войти в мир корутин.

    launch: запустить задачу, результат не нужен

    launch возвращает Job и подходит для задач, которые просто выполняются.

    async и await: запустить задачу и получить результат

    async возвращает Deferred<T>, а await() получает результат.

    Исключения и корутины: базовая идея

    Если исключение возникает внутри корутины, важно понимать где его ловить:

  • если вы хотите обработать ошибку рядом с источником, используйте try/catch внутри корутины
  • для некоторых сценариев есть CoroutineExceptionHandler, но на старте достаточно правила: ловите исключение там, где можете принять решение, что делать дальше
  • Пример:

    Здесь:

  • fetchUserNickName может вернуть null, и мы подставляем значение через ?:
  • fetchUserAge может выбросить исключение, и мы обрабатываем его через try/catch
  • всё выполняется в корутинах через runBlocking и suspend функции
  • Что дальше

    После этой темы вы можете писать код, который:

  • корректно работает с отсутствующими значениями (String?, ?., ?:, let)
  • валидирует входные данные (require) и обрабатывает ошибки (try/catch)
  • выполняет асинхронные операции в понятном стиле (корутины, suspend, launch, async/await)
  • Дальнейшие логичные шаги для практики:

  • более глубокая работа с корутинами: Dispatchers, scopes в приложениях, таймауты
  • моделирование ошибок без исключений: Result, sealed-классы
  • обобщения (generics) и продвинутые функции стандартной библиотеки
  • 7. Практика: мини-проект и лучшие практики кода

    Практика: мини-проект и лучшие практики кода

    Эта статья завершает базовый блок курса: вы уже знаете синтаксис, управляющие конструкции, функции, коллекции, ООП, null-safety, исключения и основы корутин. Теперь соберём всё вместе в мини-проекте и заодно закрепим практики, которые помогают писать код проще, безопаснее и понятнее.

    Цель: сделать небольшое консольное приложение «Список задач» (ToDo), где можно:

  • добавлять задачи
  • отмечать выполненные
  • выводить список
  • удалять
  • сохранять и загружать (с имитацией задержки через корутины)
  • !Как разделить мини-проект на понятные слои

    Что мы построим

    Мы разделим код на роли (это не обязательное правило, но очень полезная привычка):

  • модель данных: Task
  • хранилище в памяти: TaskRepository
  • сервис с проверками и удобными операциями: TaskService
  • консольный интерфейс: цикл чтения команд в main
  • псевдо-сохранение и псевдо-загрузка через suspend и delay
  • Это поможет не превращать main в один огромный файл с логикой всего приложения.

    Модель данных

    Начнём с data class: она отлично подходит для сущностей, которые в основном хранят данные.

    Здесь:

  • id — идентификатор задачи
  • title — короткое название
  • note: String? — необязательное описание (практика null-safety)
  • isDone — статус выполнения
  • Почему note nullable: описание у задачи может отсутствовать, и это нормальная ситуация, а не ошибка.

    Репозиторий: хранение задач

    Сделаем класс, который хранит список задач и даёт базовые операции. Внутри используем MutableList, а наружу постараемся выдавать read-only List.

    Обратите внимание:

  • private скрывает детали хранения
  • findById возвращает Task?, потому что задача может отсутствовать
  • update и delete возвращают Boolean, чтобы вызывающая сторона понимала, получилось ли выполнить операцию
  • Сервис: проверки, удобные сценарии и понятные ошибки

    Репозиторий не обязан знать про правила вроде «заголовок не должен быть пустым». Поэтому добавим сервис, который делает проверки через require и собирает операции в удобные методы.

    Что здесь закрепляется:

  • require(...) из темы про исключения: если аргумент неверный, это ошибка использования функции
  • нормализация nullable-значений: note?.takeIf { it.isNotBlank() }
  • copy(...) из data class для безопасного изменения состояния
  • filter из темы про коллекции
  • Псевдо-сохранение и загрузка через корутины

    Сделаем отдельный класс, который симулирует работу с внешним хранилищем. В реальности тут мог бы быть файл или база, но для обучения достаточно задержки через delay.

    Для корутин нужна зависимость kotlinx-coroutines-core (как в предыдущей статье). Документация:

  • Kotlin Coroutines overview
  • Код:

    Что здесь используется из курса:

  • управляющие конструкции: when, if, цикл while
  • функции и разбиение на маленькие части: render, parseId, printHelp
  • коллекции: split, map, forEach, isEmpty
  • ООП: классы TaskRepository, TaskService
  • null-safety: readlnOrNull(), ?.let, ?:
  • исключения: try/catch, require выбрасывает IllegalArgumentException
  • корутины: runBlocking, suspend, delay
  • Лучшие практики, которые стоит закрепить

    Делайте данные неизменяемыми по умолчанию

    Практика:

  • используйте val везде, где не требуется изменение
  • изменение состояния делайте явно через создание новой версии объекта (copy)
  • Так меньше шансов запутаться, откуда взялось новое значение.

    Избегайте !!

    Если тип String?, значит значение может отсутствовать. В большинстве случаев вместо !! есть безопасные альтернативы:

  • ?. безопасный вызов
  • ?: значение по умолчанию
  • ?.let { ... } выполнить блок только для не-null
  • Документация:

  • Kotlin Null safety
  • Проверяйте входные данные на границе системы

    Граница системы — это место, где данные приходят извне: консольный ввод, файл, сеть.

    Практика:

  • парсинг делайте через try/catch и возвращайте null, если это ожидаемая ошибка пользователя
  • правила вроде «строка не пустая» проверяйте через require
  • Документация:

  • Kotlin Exceptions
  • Разбивайте логику на небольшие функции

    Ориентир:

  • функция делает одно понятное действие
  • имя функции отвечает на вопрос: что она делает
  • Это упрощает чтение и повторное использование.

    Отделяйте хранение данных от правил

    В примере:

  • репозиторий хранит и обновляет
  • сервис проверяет и описывает сценарии
  • Так проще менять правила, не переписывая хранение.

    Используйте стандартную библиотеку, но без чрезмерных цепочек

    Функции вроде map, filter, sumOf очень полезны, но если цепочка становится длинной и плохо читается:

  • вынесите шаги в промежуточные val
  • дайте промежуточным результатам понятные имена
  • Документация:

  • Kotlin Collections overview
  • Следуйте соглашениям о стиле

    Даже в маленьком проекте привычка к стилю улучшает качество кода.

    Официальные рекомендации:

  • Kotlin Coding Conventions
  • Как развивать мини-проект дальше

    Идеи улучшений (каждая тренирует важные навыки):

  • добавить команду list open, которая показывает только невыполненные (filter { !it.isDone })
  • добавить поиск по тексту в заголовке или заметке (работа со строками и null-safety)
  • добавить сортировку: сначала невыполненные, затем выполненные (комбинация sortedBy)
  • заменить TaskStorage на реальную запись в файл (после знакомства с вводом-выводом)
  • сделать параллельное сохранение и вывод статуса через launch
  • Итог

    Вы собрали полноценное консольное приложение и закрепили ключевые темы курса на практике:

  • базовый Kotlin-синтаксис и функции
  • коллекции и стандартную библиотеку
  • ООП для структуры
  • null-safety и обработку ошибок
  • основы корутин
  • Если этот мини-проект получается написать без подсказок и вы уверенно добавляете к нему новые команды, значит фундамент Kotlin у вас уже прочный.