Ось твій урок, згенерований у стилі CS50 — енергійний, з живими прикладами та фокусом на розумінні суті.
🎓 CS50: Робота з помилками та повторна доставка (Retries & Error Handling)
Привіт усім! Це CS50 (умовно 😉), і сьогодні ми поговоримо про те, що відрізняє хороший софт від софту, який падає при першому ж подиху вітру.
1. 🔥 Вступ: Коли все йде не за планом
Уявіть ситуацію: ви замовляєте піцу через додаток. Ви голодні, вечірка в розпалі. Ви натискаєте кнопку "Оплатити", гроші списуються, але... в цей момент у вашому телефоні на секунду зникає інтернет.
Додаток каже: "Ой, щось пішло не так". Ви перевіряєте додаток: замовлення немає. Перевіряєте банк: гроші списані.
Як ви себе почуваєте? Розчарування? Гнів? Паніка?
А тепер уявіть, що ви — розробник цього бекенду. Чому це сталося? Тому що мережа — річ ненадійна. Сервери перезавантажуються. Бази даних блокуються.
Якби кур'єр прийшов до вас додому, подзвонив у двері, а ви не відкрили за 3 секунди — він же не викидає піцу в смітник і не йде звільнятися з роботи, правда? Він почекає. Або подзвонить вам на телефон. Він спробує здійснити повторну доставку.
Питання до вас: Чому ж наш код часто поводиться як той нервовий кур'єр, який при першій помилці "кидає все" і видає Exception?
Сьогодні ми навчимося писати код, який вміє вставати після падіння.
2. 🧠 Теоретична база: Що там "під капотом"?
Давайте розберемося. У світі розподілених систем (коли один сервіс спілкується з іншим) помилки діляться на дві великі категорії. Це критично важливо зрозуміти.
1. Тимчасові помилки (Transient Errors)
Це помилки типу "спробуй пізніше — і все вийде". * Приклад: Мережа моргнула, база даних перевантажена, сторонній API "приліг" на 5 секунд. * Ліки: Retry (Повторна спроба).
2. Постійні помилки (Permanent Errors)
Це помилки типу "скільки не пробуй — результату не буде". * Приклад: Ви намагаєтеся відправити JSON, у якому пропущена дужка. Або ви стукаєте на адресу, якої не існує (404). * Ліки: Fail Fast (Швидка відмова) і запис у лог. Немає сенсу довбатися головою об стіну.
Стратегії повтору (Retries)
Коли ми ловимо тимчасову помилку, ми не повинні просто спамити запитами (якщо сервер "лежить", ваші 1000 запитів за секунду його тільки доб'ють).
Ми використовуємо Backoff (Відкат): 1. Immediate: Спробувати одразу (погано для навантаження). 2. Fixed Interval: Спробувати через 5 с, потім знову через 5 с. 3. Exponential Backoff (Золотий стандарт 🏆): * Спроба 1: чекаємо 1с. * Спроба 2: чекаємо 2с. * Спроба 3: чекаємо 4с. * Спроба 4: чекаємо 8с. * Аналогія: Ви дзвоните другу, він не бере. Ви дзвоните одразу? Ні, ви чекаєте хвилину. Потім 5 хвилин. Потім годину.
❗️ Запам'ятайте інтуїтивно: Мета Retry — дати системі час на самовідновлення, а не "заспамити" її до смерті.
3. 🧪 Приклади (Python Style)
Давайте подивимось на код. Уявіть, що у нас є функція send_data(), яка намагається відправити звіт на сервер.
Рівень 0: Наївний підхід (Як робити не треба)
Чого ви очікуєте, якщо сервер вимкнений?
def send_data():
if network_is_down():
raise Exception("Помилка мережі!")
print("Успіх!")
# Головна програма
send_data() # БАХ! Програма впала.
Результат: Користувач бачить "Error 500". Бізнес втрачає гроші.
Рівень 1: Простий цикл (Трохи краще, але небезпечно)
Ми додаємо цикл while, щоб спробувати ще раз.
import time
def resilient_send():
attempts = 0
while attempts < 3:
try:
send_data()
return # Успіх, виходимо
except Exception:
print("Не вийшло, спробую ще раз...")
attempts += 1
time.sleep(1) # Чекаємо секунду
print("Я здався. Сервер мертвий.")
Чому це краще? Ми переживемо короткочасний збій. Що погано? Якщо 10 000 користувачів одночасно зловлять помилку, вони всі одночасно "ударять" по серверу через 1 секунду. Це називається Thundering Herd (Проблема громового стада).
Рівень 2: Експоненційний відкат (Як у Facebook/Google)
import time
def professional_send():
max_retries = 5
wait_time = 1 # Починаємо з 1 секунди
for i in range(max_retries):
try:
send_data()
print("Відправлено!")
return
except Exception as e:
print(f"Спроба {i+1} провалилась. Чекаю {wait_time} сек...")
time.sleep(wait_time)
wait_time = wait_time * 2 # 🔥 Подвоюємо час очікування!
raise Exception("Всі спроби вичерпано. Відправте в Dead Letter Queue.")
Результат: Перша пауза — 1с, друга — 2с, третя — 4с. Ми даємо серверу "дихати".
4. 🛠 Практична частина
Прийшов час замастити руки кодом. Уявіть, що ви пишете модуль оплати.
Завдання 1: "Ванька-встанька" Напишіть функцію, яка емулює кидання кубика (випадкове число 1-6). Якщо випадає 1 або 2 — це "помилка". Напишіть обгортку, яка буде "кидати кубик", поки не випаде успіх (3-6).
Завдання 2: "Ліміт терпіння" Модифікуйте попередній код. Якщо "помилка" випадає 5 разів поспіль — програма має зупинитися і сказати: "Сьогодні не твій день". Не можна допустити нескінченного циклу!
Завдання 3: "Розумний фільтр"
Уявіть, що функція повертає різні коди помилок:
* 503 (Service Unavailable)
* 404 (Not Found)
* 401 (Unauthorized)
Реалізуйте логіку:
* Якщо 503 -> Робимо Retry.
* Якщо 404 або 401 -> Одразу кидаємо помилку (немає сенсу пробувати ще раз, якщо пароль неправильний або сторінки не існує).
Завдання 4: Міні-кейс "Dead Letter Queue"
Створіть список failed_messages = []. Якщо після всіх спроб повідомлення не відправилось, не просто виводьте помилку, а додавайте дані в цей список. Це імітація "черги мертвих листів" для подальшого аналізу адміном.
Питання "А що, якщо...": Що, якщо під час Retry ми все-таки відправили запит на оплату, але не отримали відповідь через обрив зв'язку? Ми спробуємо ще раз. Чи не знімемо ми гроші з клієнта двічі? (Це підказка до наступного розділу).
5. 💡 Мислення як у розробника
Ось що відрізняє джуна від сеньйора в цій темі.
1. Помилка новачка: "Retry всього підряд" Новачок ставить retry на все. * Результат: Користувач вводить неправильний пароль, а система 10 разів довбає сервер авторизації, хоча пароль від цього правильним не стане. * Порада: Завжди перевіряй тип помилки. Retry — тільки для тимчасових проблем!
2. Ідемпотентність (Idempotency) — це святе Це страшне слово означає просту річ: Скільки б разів ти не повторював операцію, результат має бути таким самим, як після першого разу. * Приклад: Операція "Зробити баланс 100 грн" — ідемпотентна (можна повторювати скільки завгодно). * Приклад: Операція "Додати 10 грн" — НЕ ідемпотентна (повториш 3 рази — додаси 30 грн). * Як думає сеньйор: "Якщо я роблю Retry, я маю бути впевнений, що не створю дублікат замовлення". Вони додають унікальні ID до кожного запиту (Idempotency Key).
3. Логування Тихе падіння — це зло. Якщо система зробила 3 спроби і здалася — це має бути записано червоними літерами в логах. Інакше ви ніколи не дізнаєтесь, чому клієнти йдуть.
6. 🧩 Підсумок
Отже, друзі, що ми маємо в сухому залишку:
- Помилки неминучі. Питання не в тому, чи вони стануться, а коли.
- Transient vs Permanent. Розрізняйте "глюк мережі" (повторюємо) і "помилку логіки" (фіксимо код).
- Exponential Backoff. Не будьте настирливими. Дайте серверам відпочити між спробами.
- Ідемпотентність. Переконайтеся, що повторна спроба не ламає дані.
Тепер ви вмієте робити системи, які не падають від легкого поштовху, а пружно повертаються в стрій.
👉 На наступному уроці: Ми згадали про ситуацію, коли Retry не допоміг і повідомлення треба кудись зберегти. Ми поговоримо про Черги повідомлень (Message Queues) та RabbitMQ. Як зберегти повідомлення, навіть якщо сервер згорів фізично?
А поки — успіхів у коді, і не забувайте обробляти ваші винятки! Це був CS50! 🚀