Перейти к содержанию
Innovialabs

Обработка ошибок: исключения или возвращаемые значения

Практика3 мин

Коротко

Исключения удобны, когда ошибку нужно передать далеко наверх, а ошибки-значения и Result делают каждый сбой явным в сигнатуре. Важнее выбора подхода — не терять ошибки, добавлять контекст и обрабатывать их там, где есть что делать.

Короткий ответ

Универсально лучшего подхода нет — обычно язык выбирает за вас. Исключения (Python, Java, C#, JavaScript) прерывают выполнение и всплывают вверх по стеку, пока их не поймают. Ошибки-значения (Go) и тип Result (Rust) возвращаются как обычный результат функции, и вызывающий код обязан с ними что-то сделать.

Хорошая обработка ошибок держится на трёх правилах: не глушить сбои, добавлять контекст и обрабатывать ошибку там, где можно принять решение.

Как это выглядит в разных языках

Python — исключения:

def load_config(path):
    try:
        with open(path) as f:
            return json.load(f)
    except FileNotFoundError as e:
        raise ConfigError(f"config not found: {path}") from e

Go — ошибка как значение:

func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("load config %s: %w", path, err)
    }
    // ...
}

Rust — Result и оператор ?:

fn load_config(path: &str) -> Result<Config, ConfigError> {
    let data = std::fs::read_to_string(path)?;
    let config = parse(&data)?;
    Ok(config)
}

Сравнение подходов

ИсключенияОшибки-значения (Go)Result (Rust)
Видно ли в сигнатуречастично (checked exceptions в Java)дада
Можно ли забыть обработатьлегкоможно проигнорировать errкомпилятор предупредит
Объём кодаменьшебольше if err != nilкомпактно с ?
Передача далеко вверхавтоматическивручнуючерез ?

Исключения хороши для по-настоящему исключительных ситуаций. Ошибки-значения удобны там, где сбой — нормальный сценарий: файла нет, пользователь ввёл неверные данные, сервис недоступен.

Правила, которые работают в любом языке

  1. Не глушите ошибки. Пустой except: pass или _ = err прячет проблему, и она всплывёт позже в непонятном месте.
  2. Ловите конкретные типы. except Exception перехватит и ваши собственные баги. Ловите FileNotFoundError, TimeoutError — то, что вы реально умеете обработать.
  3. Добавляйте контекст. Сообщение «file not found» бесполезно без имени файла и операции. Сохраняйте исходную причину: raise ... from e в Python, %w в Go, cause в Java и JavaScript.
  4. Создавайте свои типы ошибок для бизнес-ситуаций: InsufficientFundsError, OrderNotFound. Тогда вызывающий код отличит их от технических сбоев.
  5. Обрабатывайте на правильном уровне. Низкоуровневая функция передаёт ошибку наверх с контекстом. Решение — повторить, показать сообщение, вернуть HTTP 404 — принимает слой, который знает сценарий.
  6. Логируйте один раз. Если каждый слой пишет ошибку в лог и пробрасывает дальше, в логах будет пять копий одного сбоя.
  7. Разделяйте сообщения. Пользователю — понятный текст без деталей реализации. В лог — стек, параметры, идентификатор запроса.

Частые ошибки

  • Возвращать null или -1 вместо ошибки: вызывающий код не узнает причину.
  • Использовать исключения для обычного управления потоком, например для выхода из цикла.
  • Показывать пользователю стек вызовов или текст SQL-ошибки — это ещё и риск безопасности.
  • Повторять запрос бесконечно без паузы и лимита попыток.

Частые вопросы

Следуйте идиомам выбранного языка. В Python и Java работайте с исключениями, в Go — с ошибками-значениями, в Rust — с Result. Смешивать стили внутри одного проекта хуже, чем последовательно использовать любой из них.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.