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