Модуль 5 · Урок 19 із 58

Exceptions: вузький handler і чесний failure

Exception — не незручність, яку треба приховати, а typed signal, що normal contract не може бути виконаний. Хороший handler знає, яку конкретну failure він може відновити або перекласти; усі інші exceptions мають зберегти traceback і дійти до boundary, де програма завершиться чесно.

try/exceptraisechainingfailure contract

Безпечна практика модуля 5

Пакет містить тільки synthetic JSON/CSV, explicit UTF-8, schema validation, deterministic output, safe logging і локальні tests. Жодних персональних чи production-даних, secrets, мережевих викликів або зовнішніх залежностей.

Завантажити практичний пакет →

SyntaxError і runtime exception — різні етапи

Parser зупиняє invalid syntax до normal execution. Runtime exceptions виникають під час операції: FileNotFoundError, UnicodeDecodeError, json.JSONDecodeError, ValueError. Handler має відповідати реальному failure type, а не ловити все «про всяк випадок».

Try block має бути вузьким

try:
    text = path.read_text(encoding="utf-8")
except FileNotFoundError as exc:
    raise InputError(f"missing input: {path.name}") from exc
else:
    return parse_records(text)

У try лишається operation, whose exception ви очікуєте. Parsing винесено в else, тому його bug не буде помилково оголошений «missing file». from exc зберігає causal chain для diagnosis, водночас public message може бути коротким.

Не ковтайте Exception

# Погано: приховує bugs і interrupts
try:
    run()
except Exception:
    pass

Broad catch без re-raise перетворює failure на неправдивий success. Якщо top-level CLI ловить Exception для final log, він має повернути non-zero exit і не називати partial output готовим. KeyboardInterrupt та SystemExit не є subclasses Exception, і не треба маскувати user cancellation.

else і finally мають окремий сенс

else виконується лише якщо try завершився без exception; він зменшує зону accidental catching. finally виконується незалежно від result і підходить для cleanup, але return у finally може приховати активну exception — цього слід уникати. File lifecycle зазвичай простіше виразити через with.

Custom exception — стабільний interface між layers

class InputError(Exception):
    """Expected user-correctable input failure."""

def main() -> int:
    try:
        build_report()
    except InputError as exc:
        print(f"error: {exc}", file=sys.stderr)
        return 2
    return 0

Adapter може перекласти low-level exceptions у domain-level InputError. Не створюйте class для кожної фрази; custom type корисний, коли caller має окрему behavior policy. Public error не повинен друкувати absolute paths, secrets або entire customer row.

Failure matrix і tests

CaseExpected signalOutput contract
Missing inputInputError / exit 2No report
Invalid UTF-8InputError із safe reasonNo partial report
Malformed JSONInputError + chained causeLine/column, no payload dump
Programmer bugUnhandled traceback in devNon-zero, no false success

Definition of Done

  • Expected failures мають specific exception types.
  • Try blocks не охоплюють unrelated code.
  • Cause збережено через chaining, коли exception перекладається.
  • CLI success/failure відображено exit status.
  • Logs/messages не приховують bug і не витікають sensitive values.

Методичні джерела

Урок, сценарії, пояснення й вправи створені SEOWORK. Посилання ведуть лише на офіційну документацію Python.

Практична перевірка · урок 19 з 47

Закріпіть матеріал уроку

Три сценарні питання. Для зарахування уроку потрібно дати щонайменше дві правильні відповіді.

1. Яка різниця між SyntaxError і FileNotFoundError важлива для handler design у CLI?
2. Try block охоплює file read, parse, transform і write, а except FileNotFoundError називає все 'input missing'. Чому це слабко?
3. Навіщо використовувати raise InputError(...) from exc під час перекладу low-level parser failure?