Модуль 33

Логування та обробка помилок

Ось твій урок у стилі CS50! 🚀


🎓 УРОК: Логування та Обробка Помилок

(Або: Як перестати боятися червоного тексту і полюбити "Чорну скриньку")


1. 🔥 Вступ: Чому код падає (і чому це нормально)

Уявіть ситуацію. П'ятниця, вечір. Ви написали ідеальний (на вашу думку) скрипт для інтернет-магазину. Ви закриваєте ноутбук і йдете відпочивати. А в суботу вранці вам дзвонить розлючений замовник: "Сайт лежить! Ніхто не може купити кросівки!".

Ви відкриваєте код. Він виглядає так само ідеально. Але сайт не працює. Питання до вас: Як ви дізнаєтесь, що саме сталося о 3-й годині ночі, поки ви спали?

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

Без цієї теми: * Ваші програми "вмирають" мовчки при найменшій проблемі. * Ви витрачаєте години (або дні), вгадуючи, де саме помилка. * Користувачі бачать страшні повідомлення на кшталт Error 500 замість "Вибачте, сервіс тимчасово недоступний".

Аналогія проста: Логування — це "Чорна скринька" літака. Якщо літак (ваша програма) падає, саме "чорна скринька" розповість експертам, чому це сталося, щоб наступного разу цього уникнути.


2. 🧠 Теоретична база: Що там "під капотом"?

Давайте розберемо два стовпи стабільності: Виключення (Exceptions) та Логи (Logs).

А. Виключення (Exceptions)

Коли програма працює і раптом стикається з чимось неможливим (наприклад, ділення на нуль або спроба відкрити файл, якого не існує), вона не може просто "проігнорувати" це. Вона створює Виключення.

Уявіть це як гарячу картоплю 🥔. 1. Функція, де сталася біда, кидає цю "гарячу картоплю" вгору — тій функції, яка її викликала. 2. Якщо та функція не знає, що робити, вона кидає її ще вище. 3. Це триває, доки хтось не спіймає картоплю (обробить помилку). 4. Якщо ніхто не спіймав і картопля долетіла до самого верху (операційної системи) — БАХ! Програма аварійно завершується (crashes).

Конструкція try / catch (або try / except): Це ваші "рукавиці" для гарячої картоплі. * TRY: "Спробуй виконати цей небезпечний код". * CATCH/EXCEPT: "Якщо вилетить гаряча картопля, спіймай її тут і зроби ось це (наприклад, скажи користувачеві ввести число ще раз), але не падай!"

Б. Логування (Logging)

Новачки використовують print(). Профі використовують Logger. Чому? Тому що print просто кричить у порожнечу консолі. А Логер — це організований архіваріус.

Рівні логування (запам'ятайте цю ієрархію!): 1. DEBUG: Дрібні деталі для розробника ("Змінна X зараз дорівнює 5"). 2. INFO: Звичайна робота ("Користувач залогінився"). 3. WARNING: Щось дивне, але працюємо далі ("Місце на диску закінчується"). 4. ERROR: Щось зламалося, операція не виконана ("Не вдалося зберегти файл"). 5. CRITICAL: Все погано, програма не може працювати ("База даних недоступна").

Інтуїтивно: Логи — це щоденник капітана корабля. Там записано все: від погоди (Info) до пробоїни в корпусі (Critical).


3. 🧪 Приклади: Від простого до реального

Приклад 1: Класика жанру (Python-style)

Уявіть, ми робимо калькулятор.

# Поганий варіант
def divide(a, b):
    return a / b

print(divide(10, 0)) # 💥 CRASH! ZeroDivisionError

Питання: Що станеться, якщо я запущу цей код? Відповідь: Програма впаде червоним текстом, користувач злякається.

Виправляємо:

import logging

# Налаштуємо логер
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

def divide_safe(a, b):
    try:
        result = a / b
        logging.info(f"Успішне ділення: {a} / {b} = {result}")
        return result
    except ZeroDivisionError:
        logging.error("Спроба ділення на нуль!") # Записали в журнал
        return "Ой! На нуль ділити не можна." # Ввічливо відповіли користувачу
    except Exception as e:
        logging.critical(f"Невідома помилка: {e}")
        return "Сталася невідома помилка."

print(divide_safe(10, 0))

Що тут відбулося? Ми "зловили" помилку. Програма не впала. Вона записала проблему в журнал (для нас) і повернула зрозуміле повідомлення (для користувача).


Приклад 2: Реальний світ (Запит до сервера)

Уявіть, що ви пишете бота, який тягне курс валют. Інтернет може зникнути будь-якої миті.

Питання: Що має зробити програма, якщо інтернет зник? Просто вимкнутись?

def get_exchange_rate():
    try:
        logging.info("Підключаюся до банку...")
        # ...тут код запиту до інтернету...
        # Уявімо, що тут виникла помилка ConnectionError
        raise ConnectionError("Сервер не відповідає") 

    except ConnectionError:
        logging.warning("Немає зв'язку. Пробую взяти дані з кешу (з пам'яті).")
        return 41.50 # Повертаємо старе збережене значення
    except Exception as e:
        logging.error(f"Критичний збій: {e}")
        return None

Результат: Користувач навіть не помітив, що інтернет блимнув. Він отримав курс валют (хоч і старий). Система стала стійкою (robust).


4. 🛠 Практична частина

Прийшов час бруднити руки кодом! Виберіть свою мову програмування і поїхали.

🔹 Завдання 1: "Анти-краш" Напишіть функцію, яка приймає від користувача число (input). Якщо користувач введе слово "привіт" замість числа, програма має не впасти, а ввічливо написати "Це не число, спробуйте ще раз" і попросити ввести знову.

🔹 Завдання 2: "Детектив" Додайте логування у файл app.log. Запишіть туди подію, коли програма запускається, і подію, коли виникає помилка введення.

🔹 Завдання 3: "Банкомат" (Міні-кейс) У вас є змінна balance = 100. Напишіть функцію withdraw(amount). * Якщо amount менше 0 — викиньте (raise) помилку ValueError. * Якщо amount більше balance — створіть власну помилку InsufficientFundsError. * Огорніть виклик функції в try/except, залогуйте спробу крадіжки (зняття більше, ніж є) як WARNING.

🔹 Завдання 4: А що, якщо...? Подумайте: чи варто логувати пароль користувача, якщо він ввів його неправильно, щоб потім проаналізувати помилку? (Спойлер: НІКОЛИ! Чому? Бо логи можуть читати інші адміни. Це дірка в безпеці).


5. 💡 Мислення як у розробника

Як відрізнити новачка від сеньйора (Senior Developer) у цій темі?

  1. Новачок пише try: ... except: pass. Це називається "ковтати помилки". Це найгірше, що можна зробити. Помилка сталася, але програма мовчить і працює далі неправильно. Це як заклеїти індикатор "Check Engine" в машині чорною ізострічкою. Ніколи так не робіть! Завжди логуйте помилку.
  2. Сеньйор думає про "Контекст". Поганий лог: Error: Failed. Гарний лог: Error: Failed to process Order #12345 for UserID: 99. Reason: Database Timeout. Без контексту ваші логи — це просто сміття.
  3. Fail Fast (Падай швидко), але м'яко. Якщо програма зайшла в глухий кут — краще зупинити процес і повідомити про це, ніж працювати з "битим" даними.

6. 🧩 Підсумок

Отже, що ми сьогодні розібрали?

  1. Помилки — це не кінець світу, це потік керування.
  2. try/except — це ваш щит від вильотів програми.
  3. Логування — це ваша "чорна скринька", яка рятує вам життя (і нерви) під час налагодження.

Тепер ваша програма не "крихка ваза", а "гумовий м'ячик". Вона може вдаритися об підлогу, але відскочить і продовжить роботу.

🚀 Наступна серія: Добре, ми навчилися обробляти помилки. Але як бути впевненим, що код працює правильно ще до того, як ми запустимо його на сервері? У наступному уроці ми поговоримо про Unit-тестування (Unit Testing). Готуйтеся ламати свій код спеціально!

А зараз — вперед до практики! Code on! 💻