1. when як інструмент валідації
Навіщо потрібна валідація
Якщо ви коли-небудь вводили «вік = 999» або «оцінка = -12», а застосунок це «проковтнув», ви вже знайомі з болем від відсутності валідації. Валідація — це шар, який відокремлює «застосунок приймає дані» від «застосунок вірить даним». І саме тут when несподівано стає не просто розгалуженням, а маленькою таблицею правил: які значення допустимі, які — ні, а які потребують окремого повідомлення.
Уявіть, що вам потрібно перевірити число й віднести його до категорії: «0..12», «13..17», «18..120», інакше — «помилка». Якщо писати це на if, вийде ланцюжок, який читається майже як юридичний документ. Натомість when сприймається як охайна шкала.
Діапазони та in / !in
Діапазон у Kotlin — це об’єкт виду a..b, який означає «усі значення від a до b включно». Слово «включно» тут справді важливе: 0..10 містить і 0, і 10. А оператор in — це перевірка «чи належить значення діапазону». Відповідно, !in означає «не належить».
Поки що це звучить як шкільна математика, але в коді все перетворюється на дуже зрозумілі фрази: age in 0..120 читається буквально як «вік у діапазоні від 0 до 120». А тепер головне: у when можна писати гілки не лише для конкретних значень, а й для перевірок належності до діапазону.
Невеликий розігрів:
fun main() {
val x = 7
println(x in 1..10) // true
println(x !in 1..10) // false
}
Тут немає жодної магії: in повертає Boolean, і цим Boolean зручно керувати логікою застосунку.
Валідація Int за діапазонами
Коли число потрібно не просто «прийняти/відхилити», а розкласти за категоріями, when із діапазонами виявляється майже ідеальним варіантом. Ви записуєте шкалу зверху вниз, а застосунок вибирає першу відповідну гілку. Для початківця це зручно ще й тим, що код виглядає як «правила на папері»: спочатку діти, потім підлітки, потім дорослі, а інакше — помилка.
Почнімо з мініфункції, яка класифікує вік. Зверніть увагу: це саме валідація + інтерпретація. Тобто ми не просто кажемо «погано/добре», а повертаємо осмислений результат.
fun ageCategory(age: Int): String {
return when (age) {
in 0..12 -> "дитина"
in 13..17 -> "підліток"
in 18..120 -> "дорослий"
else -> "некоректний вік"
}
}
fun main() {
println(ageCategory(10)) // дитина
println(ageCategory(17)) // підліток
println(ageCategory(200)) // некоректний вік
}
Тут ви бачите одразу три важливі речі. По‑перше, in 0..12 — це «умова гілки» у when. По‑друге, гілки йдуть зверху вниз, і спрацьовує перша відповідна. По‑третє, else — наша страховка на випадок «усього іншого».
Ранній захист через !in
Є дуже практичний стиль валідації: спочатку ми перевіряємо, чи число взагалі схоже на правду, а вже потім ділимо його на категорії. У when це зручно робити через !in ... у найпершій гілці. Це виглядає як охоронець на вході: «із паспортом — проходьте, без паспорта — до побачення, і неважливо, який у вас стиль одягу».
Приклад із температурою (тут діапазон умовний — просто щоб побачити ідею):
fun temperatureLabel(t: Int): String {
return when (t) {
!in -50..60 -> "поза розумним діапазоном"
in -50..0 -> "холодно"
in 1..25 -> "нормально"
else -> "спекотно"
}
}
fun main() {
println(temperatureLabel(10)) // нормально
println(temperatureLabel(200)) // поза розумним діапазоном
}
Чому це зручно? Тому що ви явно відокремлюєте «погані дані» від «хороших». А потім не доводиться думати, куди «втиснути» -999 або 999 у вашу шкалу.
Діапазони для Char
У реальних задачах ви валідовуватимете не лише числа, а й формат введення. Навіть без регулярних виразів (до них ми дійдемо пізніше) можна зробити багато корисного, просто перевіряючи символи. Наприклад: «чи є символ цифрою», «чи є це латинська літера», «велика чи мала». І тут раптом з’ясовується, що діапазони працюють і для Char.
Це особливо корисно, коли ви берете «перший символ відповіді» й очікуєте щось на кшталт Y/N, A/B/C або 1/2/3.
fun charKind(ch: Char): String {
return when (ch) {
in '0'..'9' -> "цифра"
in 'a'..'z' -> "мала латинська літера"
in 'A'..'Z' -> "велика латинська літера"
else -> "інший символ"
}
}
fun main() {
println(charKind('7')) // цифра
println(charKind('Q')) // велика латинська літера
println(charKind('?')) // інший символ
}
Важливо розуміти: діапазон '0'..'9' — це не «магія про ASCII», а просто порядок символів в Unicode. Він улаштований так, що цифри йдуть підряд. Для навчальних і практичних CLI-введень цього більш ніж достатньо.
2. Групування та комбінування правил
Групування значень через кому
Іноді валідація — це не діапазони, а набір окремих допустимих значень. Наприклад: «команда може бути лише 1, 2 або 3», «місяць може бути 1..12», «для цих місяців 31 день, а для цих — 30». Якщо писати окрему гілку на кожне значення, ви швидко відчуєте себе принтером, який друкує ті самі рядки.
У when можна перелічувати кілька значень в одній гілці через кому. Це не лише економить рядки — так правило ще й читається очевидніше: «ці значення — одна група».
Приклад (дні в місяці, без високосних тонкощів — не ображайте лютий, він і так старається):
fun daysInMonth(month: Int): Int {
return when (month) {
1, 3, 5, 7, 8, 10, 12 -> 31
4, 6, 9, 11 -> 30
2 -> 28
else -> 0
}
}
fun main() {
println(daysInMonth(11)) // 30
println(daysInMonth(2)) // 28
println(daysInMonth(99)) // 0
}
Тут else -> 0 — теж форма валідації: ми повертаємо «сигнал помилки» числом. Це неідеально (краще було б віддати окреме повідомлення), але на поточному етапі курсу — чесний і зрозумілий спосіб показати: «місяць некоректний».
Діапазони та окремі значення в одному when
Часто правило виглядає так: є загальний діапазон допустимих значень, але всередині нього є «особливі випадки». Наприклад: «бали від 0 до 100, але 100 — це окреме привітання» або «знижка за віком, але 0 — це явно помилка введення».
Суть у тому, що when дає вам змогу розташувати гілки так, щоб окремі випадки стояли вище, а широкі діапазони — нижче. Це читається майже як пріоритет правил: «якщо рівно 100 — ось так, інакше якщо в 90..99 — ось так…».
fun scoreComment(score: Int): String {
return when (score) {
!in 0..100 -> "некоректний результат"
100 -> "ідеально! (і так, можна трохи пишатися)"
in 90..99 -> "відмінно"
in 70..89 -> "добре"
else -> "є над чим попрацювати"
}
}
fun main() {
println(scoreComment(100)) // ідеально! (і так, можна трохи пишатися)
println(scoreComment(105)) // некоректний результат
}
Зверніть увагу на порядок. Якби ми поставили in 90..100 вище, то 100 «проковтнулася» б цією гілкою, і окреме повідомлення для 100 ніколи б не спрацювало.
Мінісценарій: налаштування консольного застосунку
Тепер зберімо все в маленький цілісний фрагмент. Уявімо, що ми робимо консольний застосунок «Study Helper», який запитує в користувача «рівень складності» (1..3) і «відсоток правильних відповідей» (0..100). Наше завдання — не дати застосунку мовчки прийняти сміття й водночас видати зрозумілі повідомлення.
Ні колекції, ні складну архітектуру ми не чіпаємо. Використовуємо лише readln(), trim(), toIntOrNull() і when.
fun difficultyLabel(level: Int): String {
return when (level) {
1 -> "легко"
2 -> "нормально"
3 -> "складно"
else -> "некоректний рівень"
}
}
fun main() {
print("Рівень складності (1-3): ")
val level = readln().trim().toIntOrNull()
val message = when (level) {
null -> "Це не число"
in 1..3 -> "Гаразд, обрано: ${difficultyLabel(level)}"
else -> "Число є, але потрібно 1..3"
}
println(message)
}
Зверніть увагу на спокійну «драбинку». Спочатку ми перевіряємо null (не вдалося розпарсити), потім — діапазон, і лише після цього — все інше. Такий порядок майже завжди дає найлюдяніше повідомлення: користувач розуміє, що саме він зробив не так.
Перетини діапазонів і порядок гілок
when перевіряє гілки зверху вниз і бере першу відповідну. Звучить просто, але саме тут народжується один із найпідступніших багів початківця: «я написав усі правила правильно, але застосунок поводиться неправильно». Зазвичай це означає, що правила перетинаються, а ширша гілка стоїть вище за вужчу.
Подивімося на приклад, де помилка майже непомітна:
fun badGrade(score: Int): String {
return when (score) {
in 0..100 -> "якийсь результат"
in 90..100 -> "відмінно"
else -> "помилка"
}
}
fun main() {
println(badGrade(95)) // якийсь результат
}
Чому так? Тому що 95 уже потрапило в 0..100, а до гілки 90..100 застосунок навіть не дійшов.
Правильний варіант — переставити гілки:
fun goodGrade(score: Int): String {
return when (score) {
in 90..100 -> "відмінно"
in 0..89 -> "якийсь результат"
else -> "помилка"
}
}
fun main() {
println(goodGrade(95)) // відмінно
}
Якщо ви запам’ятаєте лише одну думку з цієї лекції, нехай це буде вона: у when порядок гілок — частина логіки, а не просто «гарне оформлення».
3. Шпаргалки та візуалізація
Таблиця-шпаргалка з валідації в when
Коли ви пишете валідацію, корисно тримати в голові кілька «формул». Нижче — маленька шпаргалка з найуживаніших конструкцій. Вона не замінить розуміння, але заощадить час, коли ви в сотий раз пишете перевірку «0..100».
| Що хочемо перевірити | Як пишемо в when (x) | Як це читається |
|---|---|---|
| Належність діапазону | |
«x від 0 до 100» |
| Не належить діапазону | |
«x не від 0 до 100» |
| Кілька конкретних значень | |
«x дорівнює 1 або 3 або 5» |
| Окремий «особливий випадок» | |
«якщо рівно 100 — особлива логіка (ставимо вище за діапазон)» |
| Усе інше | |
«будь-які інші значення» |
Зауважте: Kotlin заохочує використання when саме як виразу, коли ви хочете отримати результат, а не просто друкувати в кожній гілці. Це і про читабельність, і про дисципліну в коді.
Блок‑схема конвеєра валідації
Щоб не перетворювати введення на хаос, зручно мислити валідацію як маленький конвеєр. Ми вже багато разів будували його вручну, а тепер можна зафіксувати це візуалізацією: ми рухаємося від сирого рядка до коректного значення або зрозумілої помилки.
flowchart TD
A["readln(): сирий рядок"] --> B["trim(): прибрали пробіли"]
B --> C["toIntOrNull(): число або null"]
C --> D{"when (value)"}
D -->|null| E["Повідомлення: 'Це не число'"]
D -->|in діапазоні| F["Ок: працюємо далі"]
D -->|else| G["Повідомлення: 'Число є, але не підходить'"]
Думка проста: when стає останнім кроком, де ви перетворюєте «просто значення» на «валідне значення або зрозумілу реакцію».
4. Типові помилки під час валідації через when
Помилка №1: діапазони, що перетинаються, де ширша гілка стоїть вище за вужчу.
Це той самий випадок, коли ви впевнені, що «все описали», але частина логіки ніколи не виконується. Якщо у вас є гілка in 0..100, то всі вужчі діапазони всередині неї мають стояти вище. Інакше вони стануть недосяжними за змістом.
Помилка №2: забута межа через «включність» ...
Початківці часто сприймають діапазон як «до, але не включно». Особливо якщо раніше бачили такі діапазони в інших мовах або в математичних позначеннях. У Kotlin 0..10 включає 10, тому помилки «на 1» трапляються регулярно. Лікується просто: промовляйте крайні значення та подумки перевіряйте їх окремо.
Помилка №3: відсутність осмисленого else або спроба обійтися без нього.
У валідації else — це не «щоб компілятор відчепився», а обов’язкова гілка «що робити з неправильними значеннями». Якщо else повертає щось беззмістовне на кшталт порожнього рядка, ви приховуєте помилку замість того, щоб повідомити про неї. Потім це «вистрілить» у найнесподіваніший момент.
Помилка №4: змішування в одній гілці обчислень, друку та зміни стану.
Коли when використовують для валідації, він особливо добрий як вираз: повернули повідомлення/статус/число, а друк зробили окремо. Якщо в гілках починається «і надрукувати, і змінити змінну, і ще раз прочитати введення», ваш when перетворюється на мінісеріал на 12 сезонів, який складно тестувати й читати.
Помилка №5: немає перевірки null після toIntOrNull().
Якщо ви розпарсили рядок через toIntOrNull(), то null — це нормальний результат, а не виняток. Часто найзрозуміліша структура — окрема гілка null -> "Це не число", а вже потім — діапазони. Так користувач отримає точне пояснення, а ви не боротиметеся з компілятором і nullable‑типами.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ