JavaRush /Курси /Swift SELF /Небезпечний !

Небезпечний !

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

1. Дістаньте значення — або програма впаде

Коли ви вперше бачите !, легко сприйняти його як «магічну кнопку: компілюйся!». І компілятор іноді справді підморгує: “value of optional type … not unwrapped; did you mean to use ! or ??”. Але в ! дуже конкретний зміст: взяти Optional і силоміць витягти з нього значення. Якщо значення немає, там nil, програма аварійно завершується. Без «акуратного повідомлення користувачу» і без «ну гаразд, тоді 0». Просто стоп.

Уявімо це як перевірку перепустки на вході до клубу:

  • if let — «якщо в вас є перепустка, проходьте; якщо ні — ідіть додому».
  • guard let — «без перепустки не пропускаємо — наступний!» (і основний код далі без вкладеності).
  • ! — «я гарантую, що перепустка є; охоронець навіть не дивиться».

Мініприклад: ! працює, доки ви маєте рацію

import Foundation

let n = Int("42")!
print(n + 1) // 43

Рядок "42" прямо в коді. Ви бачите його очима; він не залежить ні від користувача, ні від погоди в Лісабоні. Тут ! виглядає терпимо: гарантія справді залізобетонна.

Мініприклад: ! падає, коли ви помилилися

import Foundation

let n = Int("сорок два")!
print(n)

Int("сорок два") поверне nil, і ! перетворить це на аварійне завершення. У реальному житті таке трапляється не через злий умисел, а через звичайне людське введення.

2. Чому ! — це «контракт»

Слово «контракт» тут дуже вдале. Коли ви пишете maybeValue!, ви підписуєтеся під обіцянкою: «у цей момент maybeValue не може бути nil». Якщо обіцянку порушено, програма падає. Тобто ! — це не обробка помилки, а заява про правильність логіки.

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

Саме тому ! не можна застосовувати лише тому, що компілятор свариться. Swift недарма змушує вас думати: Optional — це тип, який включає «немає значення», і вам потрібно вибрати поведінку. Автоматична вставка ! — це синтаксична латка, а не семантичне рішення: компілятор не може зрозуміти, що ви насправді хотіли зробити.

3. Антипатерн: Fix‑it вставив !, і «я переміг компілятор»

Майже в кожного новачка був такий момент: ви написали код, компілятор свариться на Optional, IDE пропонує “Insert !”, ви натискаєте — і все компілюється. Здається, ніби ви приручили Swift. Насправді ви просто перенесли проблему зі «зрозумілої помилки компіляції» у «раптовий крах під час виконання».

У тій самій дискусії Swift Evolution це прямо називають “moral hazard”: підказка «вставити !» — це ввічливість для досвідчених розробників і моральна пастка для новачків. Тому що ви виправляєте місце використання, хоча часто проблема в тому, що змінна стала optional «випадково», і розгортати її потрібно ближче до джерела даних.

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

4. Де ! доречний

Тут важливий баланс: ми не перетворюємо ! на «заборонений символ». Він існує тому, що іноді це справді найкращий інструмент. Просто в нього висока ціна помилки.

Нижче — таблиця, яка допомагає відрізнити «контракт» від самообману.

Ситуація Схоже на «контракт»? Зазвичай роблять так
Значення задано літералом у коді (Int("42")) Так ! допустимий, хоча можна й без нього, якщо переписати логіку
Значення надійшло від користувача (readLine(), Int(line)) Майже ніколи if let / guard let / ??
Ви вже перевірили й розгорнули значення (if let x = maybeX) Контракт уже виконано Не писати maybeX!, а працювати з x
Помилка означає баг у вашій логіці, а не звичайну ситуацію Іноді Краще структурувати код так, щоб Optional не тягнувся далі, а падіння було максимально локальним і очевидним

Зверніть увагу на тонкість. Іноді nil справді означає «неможливий стан». Але новачку майже завжди здається, що «неможливе» — це «малоймовірне». А програма влаштована так, що все малоймовірне колись стається. Особливо у пʼятницю ввечері.

5. Практика: команда square і безпечний парсинг

Зараз напишемо невеликий, але прикладний фрагмент коду: міні‑CLI, який читає рядки, очікує команди й іноді — число. Це не «велика архітектура», а маленький тренажер на акуратне введення.

Нехай користувач вводить такі команди:

  • square 12 → друкуємо 144
  • exit → завершуємо цикл
  • щось інше → друкуємо підказку

Варіант A: «аби тільки працювало» через !

import Foundation

while true {
    let line = readLine()!                 // небезпечно: може бути nil
    let parts = line.split(separator: " ")
    let command = parts[0]                 // небезпечно: parts може бути порожнім

    if command == "exit" { break }

    if command == "square" {
        let n = Int(parts[1])!             // небезпечно: немає аргументу або це не число
        print(n * n)
    }
}

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

Варіант B: спочатку перевіряємо, потім обчислюємо

import Foundation

while true {
    guard let line = readLine() else { break }

    let parts = line.split(separator: " ")
    if parts.isEmpty { continue }

    let command = String(parts[0])

    if command == "exit" { break }

    if command == "square" {
        if parts.count < 2 {
            print("Потрібне число: square 12")
            continue
        }

        guard let n = Int(parts[1]) else {
            print("Це не ціле число")
            continue
        }

        print(n * n)
    } else {
        print("Невідома команда")
    }
}

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

6. Корисні нюанси: перевірки, локалізація ризику та ??

Чому if maybe != nil { maybe! } — погана звичка

Цей патерн трапляється часто: ви перевіряєте nil, а потім усе одно використовуєте !. Технічно це може працювати, але за стилем і за змістом гірше, ніж if let.

По-перше, код виходить довшим і шумнішим. По-друге, ви втрачаєте головну перевагу if let: усередині гілки у вас зʼявляється нормальна змінна типу T, а не T?. По-третє, такий стиль привчає мозок до думки: «ну ! же нормально, я ж перевірив». І потім цей ! починає зʼявлятися там, де перевірки вже немає.

Поганий стиль

import Foundation

let maybeText: String? = "Привіт"

if maybeText != nil {
    print(maybeText!) // працює, але стиль так собі
}

Нормальний стиль

import Foundation

let maybeText: String? = "Привіт"

if let text = maybeText {
    print(text) // Привіт
}

Тут важливо не «бо так заведено», а тому, що if let чітко показує намір: «якщо значення є — працюємо з ним». А != nil + ! виглядає як «я сперечаюся з компілятором».

Локалізація ризику: якщо вже потрібен !, робіть його коротким і помітним

Іноді ! усе-таки потрібен. Але навіть тоді краще, щоб це місце було:

  • коротким (одна операція),
  • очевидним (читач коду розуміє, чому ви впевнені),
  • поруч із причиною гарантії (щоб не шукати по всьому файлу, звідки це взялося).

Покажу це на прикладі з «константним» числом.

Добре: гарантію видно неозброєним оком

import Foundation

let maxRetries = Int("3")!
print(maxRetries) // 3

Смішно виглядає? Так. Але зате видно: це справді константа, і nil тут неможливий, доки ви самі не зламаєте рядок.

Погано: гарантія схована в логіці

import Foundation

let text = readLine()
let n = Int(text!)!     // подвійний ризик і жодної виразної причини
print(n)

Це приклад, де ! не «контракт», а «надія». Надія в програмуванні — річ романтична, але недостовірна.

Чому «підставити значення за замовчуванням» через ?? теж буває помилкою

Є ще одна пастка: коли ви боїтеся ставити !, ви починаєте ліпити ?? де завгодно: Int(line) ?? 0. І програма перестає падати… але починає мовчки робити неправильні речі.

Тут важливо відчувати різницю:

  • ! — ризик падіння, але ви точно побачите проблему.
  • беззмістовний ?? — програма «продовжить жити», але результат може виявитися хибним, а ви навіть не помітите.

Тому правило просте: якщо ви можете вибрати осмислену заміну — використовуйте ??. Якщо 0 або "" — це просто заглушка, краще явно обробити nil через if let/guard let і повідомити користувачу, що введення неправильне.

Приклад: осмислений дефолт

import Foundation

let name = readLine() ?? "Анонім"
print("Привіт, \(name)!") // якщо введення немає, вітаємо "Анонім"

Тут дефолт логічний: відсутність імені можна замінити на «Анонім».

Приклад: беззмістовний дефолт

import Foundation

let line = readLine() ?? ""
let age = Int(line) ?? 0
print("Вік: \(age)")

Якщо користувач увів "abc", ви покажете «Вік: 0». Користувач не вводив 0. Ви просто втратили інформацію про помилку.

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

Помилка № 1: використовувати ! на даних із «зовнішнього світу».
readLine(), Int(line), будь-які значення, що залежать від користувача, майже завжди можуть бути nil. Якщо ви ставите ! на таких даних, ви пишете програму, яка працює лише для ідеального користувача.

Помилка № 2: натискати «Insert !» як спосіб пройти компіляцію.
Це найпопулярніша пастка. Swift справді може підказати вставити !, але це лише синтаксична правка: вона не розуміє вашого задуму й не розв’язує питання «що робити, якщо nil — нормальна ситуація».

Помилка № 3: перевіряти nil, а потім усе одно писати !.
Патерн if x != nil { x! } майже завжди гірший, ніж if let x = x. Він менш читабельний і легко перетворюється на звичку «скрізь ставити ! після будь-якої перевірки».

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

Помилка № 5: замінювати всі перевірки на ?? зі страху перед !.
Іноді люди, злякавшись падінь, починають підставляти значення за замовчуванням «на автоматі»: ?? "", ?? 0. Це не завжди краще: так ви не падаєте, але й не дізнаєтеся, що дані були хибними. Якщо неправильне введення має призводити до повідомлення про помилку, використовуйте if let/guard let, а не мовчазні значення за замовчуванням.

1
Опитування
Optional-типи Swift, рівень 10, лекція 5
Недоступний
Optional-типи Swift
Основи роботи з Optional
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ