JavaRush /Курси /Swift SELF /switch — структура, default і читабельність

switch — структура, default і читабельність

Swift SELF
Рівень 8 , Лекція 2
Відкрита

1. Навіщо взагалі потрібен switch, якщо є if/else

Коли ви тільки починаєте програмувати, здається, що if/else — це відповідь майже на всі запитання. Потрібно перевірити команду? if. Потрібно обробити три варіанти? else if. Потрібно обробити десять варіантів? Ну… «тоді ще вісім else if» — і ось тут уже починаються сумні зітхання. switch у Swift створений як читабельніший спосіб розгалуження, коли варіантів багато й усі вони пов’язані з одним конкретним значенням.

Уявіть простий сценарій: користувач вводить текстову команду, а ви хочете реагувати на неї.

Через if/else if це швидко перетворюється на «сходинку»:

import Foundation

let command = (readLine() ?? "").lowercased()

if command == "help" {
    print("Показати довідку") // Показати довідку
} else if command == "exit" {
    print("Вихід")            // Вихід
} else if command == "version" {
    print("Версія 0.1")       // Версія 0.1
} else {
    print("Невідома команда") // Невідома команда
}

Код працює, але є проблема: він читається як ланцюжок порівнянь, а не як «вибір гілки за значенням».

switch дозволяє читати це майже як меню: «якщо "help" — сюди, якщо "exit" — туди, інакше — ось так».

2. Анатомія switch: як він влаштований

У switch усе побудовано на простій ідеї: ми беремо одне значення і зіставляємо його з набором варіантів. Важливо розуміти: switch — це не «розумний if», а окрема конструкція зі своїм стилем. Якщо if — це «перевіримо умову», то switch — це «підберемо для значення відповідний шаблон».

Базова форма виглядає так:

switch someValue {
case ...:
    // дії
case ...:
    // дії
default:
    // дії «про всяк випадок»
}
Загальна структура switch у Swift

Давайте візьмемо мініприклад: «номер дня тижня → коротка назва».

import Foundation

let dayNumber = 3

switch dayNumber {
case 1:
    print("Mon")      // Mon
case 2:
    print("Tue")      // Tue
case 3:
    print("Wed")      // Wed
default:
    print("Unknown")  // Unknown
}

Тут важливо запамʼятати три правила роботи switch у Swift:

  1. switch обирає першу відповідну гілку зверху вниз.
  2. Виконується рівно одна гілка. У Swift немає автоматичного переходу до наступної.
  3. default — це «якщо нічого не підійшло».

Саме тому switch так приємно читати: ви не очікуєте сюрпризів на кшталт «спрацювали два case підряд».

3. Вичерпність: чому switch змушує думати про «всі варіанти»

У Swift switch влаштований так, щоб ви не забували про випадки «а що, якщо прийде щось інше?». Спочатку це дратує («та відчепись, компіляторе!»), а потім рятує («дякую, компіляторе!»).

Термін вичерпність означає: switch зобовʼязаний покрити всі можливі варіанти значення. Якщо варіантів потенційно нескінченно багато (наприклад, Int), то без default ви просто фізично не перелічите все. Тому на числах і рядках default майже завжди потрібен.

Подивіться на приклад із командою користувача. Ми читаємо рядок, приводимо його до нижнього регістру і розгалужуємося:

import Foundation

let command = (readLine() ?? "").lowercased()

switch command {
case "help":
    print("Доступні команди: help, version, exit") // Доступні команди: help, version, exit
case "version":
    print("LibraryCLI v0.1") // LibraryCLI v0.1
case "exit":
    print("Поки!") // Поки!
default:
    print("Невідома команда: \(command)") // Невідома команда: ...
}

Тут default — це ваш страховий варіант. Причому не лише від неуважного користувача, а й від уважного, який втомився і випадково натиснув пробіл.

До речі, іноді вичерпність можна забезпечити і без default. Наприклад, для Bool у нас лише два значення: true і false. Отже, можна явно покрити обидва випадки:

import Foundation

let isAdmin = false

switch isAdmin {
case true:
    print("Доступ: адміністратор")     // (не виконається)
case false:
    print("Доступ: користувач") // Доступ: користувач
}

Такий switch теж вичерпний: більше варіантів у Bool немає.

4. default: гілка «на все інше»

default потрібен часто, але він же — головне джерело лінощів і хаосу. Дуже легко зробити так: обробити один улюблений випадок, а все інше скинути в default без чіткої логіки. Це працює, але за тиждень ви самі не згадаєте, чому деякі випадки «не підтримуються».

Гарне правило для новачка таке: default має бути зрозумілим і безпечним. У простих CLI-програмах «зрозумілим і безпечним» зазвичай означає: вивести повідомлення, що команда невідома, і, можливо, підказати "help".

Приклад трохи дружелюбніший:

import Foundation

let command = (readLine() ?? "").lowercased()

switch command {
case "help":
    print("Команди: help, exit") // Команди: help, exit
case "exit":
    print("Завершення роботи...") // Завершення роботи...
default:
    print("Не зрозумів команду. Введіть help.") // Не зрозумів команду. Введіть help.
}

Зверніть увагу: ми не намагаємося вгадати за користувача. Ми просто чесно кажемо, чого очікували.

Ще одна корисна дрібниця: обробляти порожній ввід як окремий випадок. Це робить UX кращим, а код — читабельнішим:

import Foundation

let command = (readLine() ?? "").lowercased()

switch command {
case "":
    print("Порожній ввід. Введіть help.") // Порожній ввід. Введіть help.
case "help":
    print("Команди: help, exit") // Команди: help, exit
case "exit":
    print("Поки!") // Поки!
default:
    print("Невідомо: \(command)") // Невідомо: ...
}

Технічно "" — це такий самий звичайний рядок, просто порожній.

5. Кілька значень в одному case

Дуже типовий сценарій: користувач вводить «так», але іноді пише "y", іноді "yes", іноді "т", а іноді взагалі CAPS LOCK вирішив, що він головний. Ми вже вміємо робити lowercased(), тож із великими літерами впораємося. Але все одно лишається кілька рівнозначних варіантів.

У switch можна перелічити кілька значень через кому — і це значно чистіше, ніж дублювати один і той самий print() у кількох гілках:

import Foundation

let answer = (readLine() ?? "").lowercased()

switch answer {
case "yes", "y", "так", "т":
    print("Прийнято") // Прийнято
case "no", "n", "ні", "н":
    print("Відхилено") // Відхилено
default:
    print("Не зрозумів відповідь") // Не зрозумів відповідь
}

З точки зору читання це просто подарунок: «ось набір синонімів, робимо одне й те саме».

6. Порожня гілка: чому іноді потрібен break

Іноді вам потрібно явно сказати: «у цьому випадку нічого не робимо». Наприклад, ви обробляєте ввід числа і хочете не шуміти, якщо воно дорівнює нулю.

У Swift не можна залишати case зовсім порожнім. Але можна написати break, щоб гілка була синтаксично повноцінною, а ваш намір читався буквально як «нічого не робимо».

import Foundation

let x = 0

switch x {
case 0:
    break
default:
    print("x не дорівнює 0") // (не виконається)
}

Спочатку це виглядає трохи дивно, ніби ви зупиняєте switch. Але сенс простий: break тут означає «порожня дія».

7. Читабельність switch: порядок case і «плоска логіка»

switch сам по собі не гарантує красивого коду. Він лише дає інструмент. А ось як ви його використовуєте — це вже ваша відповідальність і, якщо чесно, ваш майбутній головний біль.

Найчастіша проблема — невдалий порядок гілок. Оскільки switch перевіряє case зверху вниз і бере перший збіг, логічно тримати такий стиль: спочатку найконкретніші варіанти, потім загальніші. Для default це правило майже завжди означає «ставимо default в кінці», бо інакше він перехопить усе, а інші гілки стануть недосяжними.

Ще один прийом читабельності — намагатися, щоб кожна гілка робила одну зрозумілу річ. Якщо всередині case починається «роман у трьох томах» із вкладених if, зазвичай це сигнал, що ви змішали кілька рівнів логіки. Поки ми не обговорюємо просунуті прийоми, але навіть на базовому рівні можна собі допомогти: виносити повідомлення користувачу в зрозумілі print(), а не будувати всередині одного case мініпроєкт.

Ось невелика таблиця, щоб візуально порівняти підходи:

Ситуація Частіше краще if/else Частіше краще switch
Один-два варіанти, складні умови (&&, ||) Так Іноді, але не обовʼязково
Багато варіантів для одного значення (команда, оцінка, код) Можна, але буде «сходинка» Так, читається як меню
Потрібно «обробити все інше» else default

8. Мініверсія застосунку: LibraryCLI і команди на switch

Зараз ми складемо цілісний фрагмент програми, який виглядатиме як справжній застосунок, а не як набір окремих прикладів. Ми поки не зберігаємо книги і не будуємо справжню базу — у нас ще немає масивів і словників. Але ми вже можемо зробити каркас: цикл вводу команд і розгалуження через switch.

Ось проста «оболонка»:

import Foundation

var isRunning = true

while isRunning {
    print("Введіть команду (help/exit):", terminator: " ")
    let command = (readLine() ?? "").lowercased()

    switch command {
    case "help":
        print("Команди: help, exit") // Команди: help, exit
    case "exit":
        print("Завершення...") // Завершення...
        isRunning = false
    case "":
        print("Порожня команда. Спробуйте help.") // Порожня команда. Спробуйте help.
    default:
        print("Невідома команда: \(command)") // Невідома команда: ...
    }
}

Зверніть увагу на кілька речей.

По-перше, switch робить наш код «плоским». Нам не доводиться будувати драбину if/else if/else, і гілки виглядають як список команд.

По-друге, у нас є явна обробка порожнього вводу — це дрібниця, але вона робить застосунок дружелюбнішим.

По-третє, "exit" не просто друкує повідомлення, а змінює isRunning, щоб вийти з циклу: switch зручно поєднується з циклом.

І так, це саме той момент, коли починаєш поважати контроль потоку виконання. Ви буквально керуєте життям програми, як диспетчер в аеропорту, тільки без відповідальності за реальні літаки — поки що.

9. Типові помилки під час роботи зі switch

Помилка №1: очікувати, що switch виконає кілька гілок підряд.
Якщо ви прийшли в Swift із мов, де за замовчуванням є «провалювання» з case у case, то підсвідомо можете чекати, що після першого збігу виконається ще один блок. У Swift так не відбувається: виконується рівно одна гілка. Тому, якщо вам потрібно кілька дій для одного випадку, просто запишіть їх підряд усередині одного case.

Помилка №2: забути про default, коли варіантів нескінченно багато.
Для Int або String неможливо перелічити всі значення, тому default — це не «опція», а необхідність. Зазвичай це проявляється як помилка компіляції: switch must be exhaustive. Гарна реакція — не злитися, а додати акуратний default із зрозумілим повідомленням.

Помилка №3: ставити default не в кінці і випадково «перехопити все».
Іноді новачок намагається «спочатку обробити все інше», а потім дописати спеціальні команди нижче. Але switch іде зверху вниз, і якщо default стоїть рано, далі вже ніхто не дістанеться. Якщо ви бачите, що якісь case підсвічені як недосяжні, насамперед перевірте порядок.

Помилка №4: порівнювати «як у if» і намагатися писати умови замість шаблонів.
У switch не можна писати case command == "help": — це стиль if. У сьогоднішній лекції ми використовуємо лише прості значення в case. Пізніше switch стане ще потужнішим, але навіть зараз корисно памʼятати: case — це зіставлення з варіантом, а не довільна логіка.

Помилка №5: не нормалізувати ввід і потім дивуватися, що "Help" не працює.
Якщо ви порівнюєте рядки напряму, то "Help" і "help" — різні значення. Тому перед switch майже завжди варто зробити нормалізацію: хоча б lowercased(), а іноді й обробку порожніх рядків. Це не «костиль», а звичайна турбота про користувача, який не зобовʼязаний памʼятати ваш улюблений регістр.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ