Модуль 10

Retries, time limits та error handling

Ось урок, створений спеціально для тебе у стилі CS50. Уяви, що ми зараз в Sanders Theatre, я ходжу сценою, закочую рукава сорочки й починаю...


🏛️ CS50: Мистецтво падіння (і як піднятися)

Тема: Retries, Time Limits та Error Handling

Привіт, друзі!

1. 🔥 Вступ: Чому світ такий ненадійний?

Уявіть, що ви дзвоните своєму другу, щоб дізнатися, де він. Ви набираєте номер... і тиша. Або дивні звуки. Або "абонент поза зоною".

Що ви робите? 1. Викидаєте телефон у вікно і кажете: "Ну все, дружбі кінець, я більше ніколи не буду з ним спілкуватися"? 2. Чи ви передзвонюєте?

Звісно, ви передзвонюєте! Можливо, не одразу. Можливо, через хвилину.

Але уявіть, що ви пишете код. Ваш застосунок робить запит до бази даних або зовнішнього API (наприклад, курс валют). Сервер мовчить. Що робить ваша програма за замовчуванням? Вона падає. Вона викидає помилку і "вмирає".

Чому так відбувається? Тому що мережа — це брехня. Ми звикли думати, що інтернет стабільний, як електрика в розетці. Але насправді мережа — це як передавати записки через переповнений рок-концерт. Пакети губляться, сервери перевантажуються, кабелі перегризають акули (серйозно, таке буває!).

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

Без цього ваш "ідеальний код" зламається в першу ж п'ятницю ввечері. Поїхали!


2. 🧠 Теоретична база: Три кити надійності

Давайте розберемо три поняття, без яких не будують серйозні системи.

А. Retries (Повторні спроби)

Це просто "спробуй ще раз". Але тут є нюанс. Якщо сервер "лежить" і ви почнете довбати його мільйоном запитів за секунду, ви зробите тільки гірше (це називається DDoS-атака на самого себе). Що треба знати: Ми використовуємо Backoff (відступ). Спробував — не вийшло — почекав 1 сек — спробував — не вийшло — почекав 2 сек — потім 4 сек. Це називається Exponential Backoff.

Б. Time Limits / Timeouts (Тайм-аути)

Уявіть, що ви замовили піцу, а кур'єр не приїжджає. Ви чекаєте годину, дві, тиждень, рік... Ви все ще чекаєте біля дверей? Ні! У вас є "ліміт терпіння". У комп'ютера терпіння нескінченне. Якщо ви не скажете йому "чекай максимум 5 секунд", він може зависнути навічно, блокуючи всі ресурси. Що треба знати: Завжди, чуєте, ЗАВЖДИ ставте timeout на будь-який зовнішній виклик.

В. Error Handling (Обробка помилок)

Це план "Б". Якщо ми спробували 3 рази (retries), почекали (timeout), і все одно нічого не працює — що робити? Впасти? Показати юзеру білий екран? Чи показати красиве повідомлення "Вибачте, сервіс тимчасово недоступний"? Що треба знати: Ми не ховаємо помилки, ми їх обробляємо.


3. 🧪 Приклади: Від крихкого до незламного

Давайте писати код (на Python, бо це як англійська мова програмування).

Сценарій 0: Наївний код (Як не треба)

У нас є функція get_data(), яка звертається до нестабільного сервера.

def main():
    # Якщо тут станеться помилка мережі, програма впаде
    response = internet.get_data("https://api.weather.com")
    print(f"Погода: {response}")

Питання до вас: Що побачить користувач, якщо Wi-Fi блимне на 0.1 секунди? Відповідь: Unhandled Exception: ConnectionError. Користувач видаляє ваш додаток.


Сценарій 1: Додаємо Retry (Настирливість)

Давайте спробуємо 3 рази перед тим, як здатися.

import time

def get_data_with_retry():
    attempts = 0
    max_attempts = 3

    while attempts < max_attempts:
        try:
            return internet.get_data("https://api.weather.com")
        except ConnectionError:
            print("Ой, помилка... пробуємо ще раз!")
            attempts += 1
            time.sleep(1) # Даємо серверу передихнути (Simple Backoff)

    raise Exception("Ми спробували все, але сервер мертвий :(")

Чому це краще? Ми переживаємо короткочасні збої. Якщо сервер "чхнув", наш код цього навіть не помітить.


Сценарій 2: Timeout (Ліміт терпіння)

А що, якщо сервер не видає помилку, а просто мовчить? Він прийняв запит і "думає" нескінченно. Наш while з попереднього прикладу зависне на першій же спробі назавжди.

def get_data_safe():
    try:
        # Чекаємо відповідь максимум 2 секунди
        return internet.get_data("https://api.weather.com", timeout=2)
    except TimeoutError:
        print("Сервер надто повільний, я не буду чекати!")
        return None # Повертаємо пусте значення замість падіння

Інтуїція: Timeout рятує вашу систему від перетворення на "цвинтар очікування".


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

Час закачати рукава. Ось ваші завдання.

Завдання 1: "Лінивий офіціант" Напишіть функцію, яка імітує замовлення їжі. Зробіть так, щоб вона з ймовірністю 50% викидала помилку. Напишіть обгортку (wrapper), яка буде викликати цю функцію доти, доки не отримає успішний результат, АЛЕ не більше 5 разів.

Завдання 2: "Експоненціальне зростання" Змініть попередній код. Замість фіксованого очікування (time.sleep(1)), зробіть так, щоб час очікування зростав: 1 сек, 2 сек, 4 сек, 8 сек. Навіщо: Це стандарт індустрії, щоб не "покласти" сервер, який і так ледве дихає.

Завдання 3: "Критичне мислення" (Кейс) У вас є функція pay_money(user_id, amount). Чи безпечно використовувати для неї автоматичний Retry? Підказка: Що станеться, якщо перший запит пройшов успішно (гроші зняли), але відповідь загубилася в мережі, і ваш код вирішив спробувати зняти гроші ще раз?

Завдання 4: Реальний виклик Використайте бібліотеку requests у Python. Спробуйте зробити запит на неіснуючий сайт (наприклад, http://thissitedoesnotexist.com). Перехопіть цю помилку і виведіть: "Сайт не знайдено, але я живий!".


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

Як відрізнити новачка від сеньйора?

1. Ідемпотентність (Idempotency) Пам'ятаєте Завдання 3 про гроші? * Новачок: Просто ставить retry на все підряд. Результат: клієнт заплатив двічі. * Профі: Знає, які запити можна повторювати (читання даних), а які ні (платежі), якщо вони не ідемпотентні (тобто повторний виклик не змінює стан системи).

2. Типи помилок * Новачок: Ловить всі помилки except Exception:. * Профі: Ловить конкретно. Якщо помилка 404 Not Found (сторінки немає), немає сенсу робити retry — вона не з'явиться через секунду. Якщо помилка 503 Service Unavailable — ось тут робимо retry.

3. Fail Gracefully (Красиве падіння) Якщо Netflix не може завантажити рекомендації фільмів, він не показує помилку. Він просто показує список "Популярне". Користувач навіть не знає, що щось зламалось. Це вищий пілотаж.


6. 🧩 Підсумок

Отже, що ми маємо в сухому залишку?

  1. Мережа бреше. Завжди готуйтеся до того, що запит не дійде.
  2. Retry — це ваш другий шанс. Але не будьте нав'язливі (використовуйте backoff).
  3. Timeout — це ваш захист від вічного зависання.
  4. Error Handling — це те, що відрізняє професійний софт від студентської лабораторної.

Що ви тепер вмієте? Ви вмієте писати код, який "тримає удар". Код, який працює у реальному, недосконалому світі.

Що далі? Ми навчилися обробляти помилки одного запиту. Але що робити, якщо у вас 1000 запитів на хвилину, і всі вони падають? Як зробити так, щоб одна помилка не потягнула за собою весь сервер? Наступного разу ми поговоримо про Circuit Breaker (автоматичний запобіжник) та Черги повідомлень.

А поки що... це був CS50! 👋