Ось готовий урок, створений за твоїм майстер-промптом.
🎓 CS50-Style Lesson: Обробка винятків і глобальні error handlers
Привіт, друзі! Радий бачити вас на цьому занятті. 👋
Сьогодні ми поговоримо про те, що відрізняє код, який просто працює, від коду, який живе у реальному світі. Ми поговоримо про хаос. Про помилки. Про моменти, коли все йде шкереберть.
1. 🔥 Вступ: Коли все йде не за планом
Уявіть, що ви замовили каву в кав’ярні. Ви чекаєте на свій лате. Але раптом у бариста закінчується молоко.
Що відбувається далі?
1. Варіант А (Поганий софт): Бариста мовчки завмирає, кав’ярня спалахує вогнем, усіх відвідувачів викидає на вулицю, а на дверях з’являється напис 500 INTERNAL SERVER ERROR.
2. Варіант Б (Хороший софт): Бариста каже: "Вибачте, молоко закінчилося. Бажаєте чорну каву або повернення коштів?"
У програмуванні помилки (або винятки/exceptions) — це те саме "закінчилося молоко". Це не катастрофа, це просто ситуація, яку треба обробити.
Але часто новачки пишуть код, сподіваючись на ідеальний світ. * А що, якщо файл, який ви відкриваєте, не існує? * А що, якщо інтернет зник саме в момент оплати? * А що, якщо користувач ввів літери замість цифр?
Якщо ви не передбачите ці сценарії, ваша програма просто впаде ("вкрашиться"). І користувач побачить страшний технічний текст або просто білий екран. Ми тут, щоб цього не допустити.
2. 🧠 Теоретична база: Як це працює "під капотом"
Давайте зазирнемо всередину. Коли ви запускаєте функцію, вона потрапляє у Стек викликів (Call Stack). Уявіть це як стопку тарілок.
Функція A викликає B, B викликає C.
Якщо у функції C стається помилка (виняток):
1. Вона "кидає" (throw/raise) цей виняток вгору.
2. Якщо C не знає, що робити, виняток летить до B.
3. Якщо B не знає — летить до A.
4. Якщо й A (головна функція) не знає — програма завершується аварійно.
Це називається Stack Unwinding (розкручування стека) або "спливання" помилки.
Ключові концепції:
- Try (Спробуй): Ви кажете комп'ютеру: "Спробуй виконати цей небезпечний код".
- Catch / Except (Спіймай): "Якщо станеться ось така конкретна помилка — не падай! Зроби ось це (наприклад, напиши повідомлення)".
- Finally (Нарешті): "Що б не сталося — була помилка чи ні — зроби це обов'язково (наприклад, закрий файл чи з’єднання з базою)".
- Global Error Handler: Це "воротар" на найвищому рівні. Якщо ніхто внизу не спіймав помилку, вона долітає сюди. Це ваш останній шанс врятувати ситуацію і красиво вибачитися перед користувачем.
Запам’ятайте: Винятки — це не вороги. Це механізм комунікації. Код "кричить", що щось не так, і ви маєте це почути.
3. 🧪 Приклади (Python edition)
Ми будемо використовувати Python, бо він читається як англійська, але логіка однакова і для Java, і для C#, і для JavaScript.
🐣 Рівень 1: Мінімальний приклад
Що ви очікуєте, якщо ми поділимо 10 на 0?
# Поганий код ❌
print(10 / 0)
# Результат: ZeroDivisionError: division by zero (Програма впала!)
Виправляємо:
# Хороший код ✅
try:
result = 10 / 0
print(f"Результат: {result}")
except ZeroDivisionError:
print("Ой! На нуль ділити не можна.")
print("Програма працює далі...")
Пояснення: Програма не впала. Вона "перестрибнула" в блок except, вивела повідомлення і спокійно пішла далі.
🚀 Рівень 2: Реальний світ (API запит)
Уявіть, що ми шукаємо користувача в базі даних.
def get_user_data(user_id):
database = {"1": "Ivan", "2": "Maria"} # Наша імітація БД
try:
user = database[user_id]
return f"Користувач знайдений: {user}"
except KeyError:
# Ловимо помилку, якщо ключа немає
return "Помилка: Такого користувача не існує."
# Тестуємо
print(get_user_data("1")) # Користувач знайдений: Ivan
print(get_user_data("99")) # Помилка: Такого користувача не існує.
🛡️ Рівень 3: Global Error Handler (Вищий пілотаж)
У великих веб-додатках ми не хочемо писати try-except у кожному рядку. Це робить код брудним.
Ми хочемо писати чисту логіку, а помилки нехай ловить Глобальний Обробник.
Уявіть це як веб-сервер (спрощено):
# Наша "бізнес-логіка" - тут ми не думаємо про помилки
def logic_buy_item(item_price, user_balance):
if item_price < 0:
raise ValueError("Ціна не може бути від'ємною!")
if user_balance < item_price:
raise Exception("Недостатньо коштів!")
return "Куплено успішно!"
# --- ГЛОБАЛЬНИЙ ОБРОБНИК ---
def global_handler(func, *args):
try:
# Пробуємо запустити будь-яку функцію
result = func(*args)
print(f"✅ {result}")
except ValueError as e:
# Специфічна помилка даних
print(f"⚠️ Помилка даних: {e}")
except Exception as e:
# Будь-яка інша помилка (наприклад, гроші)
print(f"🛑 Критична помилка: {e} (Повідомте адміна)")
# --- Запуск ---
print("Спроба 1:")
global_handler(logic_buy_item, 100, 50) # Мало грошей
print("\nСпроба 2:")
global_handler(logic_buy_item, -50, 100) # Невірна ціна
Чому це круто? Функція logic_buy_item чиста. Вона просто "кидає" проблеми вгору. А global_handler вирішує, як це показати користувачу.
4. 🛠 Практична частина
Час закотити рукави! Ось ваші завдання.
- 🔹 Повтори: Напиши функцію, яка перетворює рядок на число (
int("abc")). Огорни це вtry-except, щоб зловитиValueError. - 🔹 Виправ: У тебе є список
fruits = ["apple", "banana"]. Спробуй звернутися доfruits[5]. ОбробиIndexError, вивівши "Фрукт не знайдено". - 🔹 Cleanup: Додай блок
finallyдо завдання з файлами. Напиши код, який відкриває файл (навіть неіснуючий), а вfinallyпише: "Спроба доступу завершена". - 🔹 Міні-кейс: Уяви, що ти пишеш банкомат.
- Користувач вводить суму для зняття.
- Кинь (raise) помилку, якщо сума менша за 0.
- Кинь помилку, якщо сума більша за 1000.
- Оброби ці помилки й виведи ввічливі повідомлення.
- 🔹 А що, якщо...: Що станеться, якщо в блоці
exceptтеж виникне помилка? Спробуй це зробити навмисно. Як це вирішити?
5. 💡 Мислення як у розробника
Як відрізнити новачка ("джуна") від профі ("сеньйора")?
❌ Новачок пише:
try:
# Величезний шмат коду на 100 рядків
except:
pass # Ігнорує помилку! Найбільше зло.
Це як заклеїти індикатор "Check Engine" в машині чорною стрічкою. Машина горить, а ви цього не бачите.
✅ Професіонал думає:
1. Fail Fast (Падай швидко): Краще програма впаде одразу на етапі розробки, ніж буде працювати неправильно з прихованими даними.
2. Будь конкретним: Лови ZeroDivisionError, а не просто всі Exception. Ти маєш знати, що ти ловиш.
3. Не показуй нутрощі: Користувач має бачити "Сервіс тимчасово недоступний", а не "Stack trace: line 45, connection failed to 192.168...". Останнє — це дірка в безпеці!
4. Логуй: Якщо ти спіймав помилку глобальним хендлером — запиши її у файл (лог). Користувачу — посмішка, адміну — деталі трагедії.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили?
1. Зрозуміли, що помилки — це нормально.
2. Навчилися використовувати try-except, щоб програма не "вибухала".
3. Розібрали концепцію Global Error Handler — страховки для всього додатку.
4. Зрозуміли, чому не можна просто ігнорувати помилки (pass).
Тепер ви не просто пишете код, який працює за ідеальних умов. Ви пишете надійний (robust) код, готовий до реального життя.
🔜 У наступній серії: Ми згадували про те, що помилки треба кудись записувати. Наступного разу ми поговоримо про Логування та Моніторинг. Як дізнатися, що ваш сервер впав, поки ви спите?
А зараз — вперед до коду! 💻🚀