Как писать обработчики ошибок?
Функции в программах возвращают ошибки. Что с ними делать?
1️⃣ Запиши в лог. Логи позволят расследовать влияние ошибки на сервис. Вместе с сообщением об ошибке запиши имя функции, которая её вернула:
Не добавляй в сообщение вводные слова: “failed foo()”, “foo() returned error” или “unable to call foo()”. Это не несёт новой информации и мешает чтению. Лучше в лог добавить аргументы, с которыми функция была вызвана. Это поможет в отладке.
2️⃣ Ограничь влияние на сервис. Ошибкам свойственно распространяться. Например, необработанное исключение в одном запросе аварийно завершает всю программу. Когда программа обслуживает нескольких клиентов, это нежелательное поведение. Лучше ограничить влияние ошибки одним запросом:
3️⃣ Разным ошибкам - разные обработчики. Ошибки отличаются между собой. В случае временных ошибок можно повторить попытку:
Часть ошибок может быть вариантом нормы. Например, ошибка
Ошибки можно сравнивать по типу или числовому коду при выборе обработчика. В примере выше, стандартные коды ошибок errno. ⚠️ Не следует сравнивать ошибки по текстовому сообщению. В нём может быть изменяемая часть.
Обработчики будут выглядеть одинаково вне зависимости от выбранной модели обработки ошибок. Пиши код так, чтобы он был готов к ошибкам. Это повысит отказоустойчивость программы и твой авторитет в глазах коллег 🔥
#hardskills #coding #errorhandling #logging #bestpractice
Функции в программах возвращают ошибки. Что с ними делать?
1️⃣ Запиши в лог. Логи позволят расследовать влияние ошибки на сервис. Вместе с сообщением об ошибке запиши имя функции, которая её вернула:
err = foo()
if err != nil {
log.Error("foo: %s", err)
return
}
Не добавляй в сообщение вводные слова: “failed foo()”, “foo() returned error” или “unable to call foo()”. Это не несёт новой информации и мешает чтению. Лучше в лог добавить аргументы, с которыми функция была вызвана. Это поможет в отладке.
2️⃣ Ограничь влияние на сервис. Ошибкам свойственно распространяться. Например, необработанное исключение в одном запросе аварийно завершает всю программу. Когда программа обслуживает нескольких клиентов, это нежелательное поведение. Лучше ограничить влияние ошибки одним запросом:
while (true) {
try {
ServeClient();
} catch (const Exception e) {
log.Error("ServeClient: %s", e);
// Больше ничего сделать не можем
// ¯\_(ツ)_/¯
}
}
3️⃣ Разным ошибкам - разные обработчики. Ошибки отличаются между собой. В случае временных ошибок можно повторить попытку:
int err = EINVAL;
// 3 попытки
for (int i = 0; i < 3; i++) {
err = do_some_stuff();
switch (err) {
case 0:
// Успех
return 0;
case EINTR:
// Временная ошибка, сработало прерывание
// Можно сразу пробовать ещё раз
continue;
case EAGAIN:
// Временная ошибка, сокет не готов к вводу/выводу
// Можно пробовать ещё раз, чуть позже
sleep(1); // TODO: лучше подписаться на событие вместо слипа
continue;
default:
// Непредвиденная ошибка
return err;
}
}
return err;
Часть ошибок может быть вариантом нормы. Например, ошибка
ENOENT
, файл не найден.Ошибки можно сравнивать по типу или числовому коду при выборе обработчика. В примере выше, стандартные коды ошибок errno. ⚠️ Не следует сравнивать ошибки по текстовому сообщению. В нём может быть изменяемая часть.
Обработчики будут выглядеть одинаково вне зависимости от выбранной модели обработки ошибок. Пиши код так, чтобы он был готов к ошибкам. Это повысит отказоустойчивость программы и твой авторитет в глазах коллег 🔥
#hardskills #coding #errorhandling #logging #bestpractice
Что под капотом у исключений
Пост для тех, кто раньше об этом не задумывался. Программисты думают об исключениях как о фиче языков программирования. ЯП поддерживает исключения, если предоставляет операторы
Рассмотрим фичи исключений.
- Исключение может прервать поток выполнения программы.
- Исключение должно раскручивать стек до ближайшего обработчика.
- Обработчик исключений, если установлен, должен выполниться незамедлительно при срабатывании исключения.
- Если обработчика нет, процесс должен аварийно завершиться.
- Исключения должны быть совместимы с фатальными ошибками ОС, такими как Segmentation fault.
- Обработчик исключений должен возвращать управление в точку после
Исключения реализуются с помощью UNIX-сигналов и
Наколдуем свои исключения.
🐱 Листинг кода: gist.github.com/maksimuimin/c14b066b0b454ac7a1962631c3a238bb
▶️ Результат выполнения:
Практические выводы.
📌 ОС сообщает о фатальных ошибках с помощью UNIX-сигналов. Такие ошибки можно обработать и как исключения, и как сигналы. Обработка фатальных ошибок - хорошая практика безопасного программирования, даже если ваш ЯП не поддерживает исключения. Пример из реального мира, почтовый сервер Exim: github.com/Exim/exim/blob/exim-4.97.1/src/src/receive.c#L3805
📌
Исключения - сложный механизм. Я предпочитаю модель обработки ошибок на основе возвращаемых значений - там всё просто. Однако, эта модель никак не обрабатывает фатальные ошибки ОС. Поэтому, на практике используется комбинация двух моделей: возвращаемые значения для стандартных сбоев и исключения/сигналы для фатальных ошибок. Это хороший компромисс между простотой и отказоустойчивостью 🔥
#theory #coding #Linux #errorhandling
===
Мои любимые посты в канале t.me/uimindev/37
Пост для тех, кто раньше об этом не задумывался. Программисты думают об исключениях как о фиче языков программирования. ЯП поддерживает исключения, если предоставляет операторы
try
, catch
, fially
, throw
. Но всё не так однозначно. Если бы ты писал свою реализацию исключений, как бы ты это сделал?Рассмотрим фичи исключений.
- Исключение может прервать поток выполнения программы.
- Исключение должно раскручивать стек до ближайшего обработчика.
- Обработчик исключений, если установлен, должен выполниться незамедлительно при срабатывании исключения.
- Если обработчика нет, процесс должен аварийно завершиться.
- Исключения должны быть совместимы с фатальными ошибками ОС, такими как Segmentation fault.
- Обработчик исключений должен возвращать управление в точку после
try/catch
.Исключения реализуются с помощью UNIX-сигналов и
longjmp
. Обе фичи есть стандартной библиотеке С, хотя готовых исключений там нет.Наколдуем свои исключения.
▶️ Результат выполнения:
Try 1
Got signal: Aborted
Exception 1
Try 2
Try 3
Got signal: Segmentation fault
Exception 3
Практические выводы.
📌 ОС сообщает о фатальных ошибках с помощью UNIX-сигналов. Такие ошибки можно обработать и как исключения, и как сигналы. Обработка фатальных ошибок - хорошая практика безопасного программирования, даже если ваш ЯП не поддерживает исключения. Пример из реального мира, почтовый сервер Exim: github.com/Exim/exim/blob/exim-4.97.1/src/src/receive.c#L3805
📌
longjmp
работает как goto
, но может передавать управление между функциями. Позволяет реализовать оператор throw
без сигналов. Пример из реального мира, функция error
в Lua: pgl.yoyo.org/luai/i/lua_errorИсключения - сложный механизм. Я предпочитаю модель обработки ошибок на основе возвращаемых значений - там всё просто. Однако, эта модель никак не обрабатывает фатальные ошибки ОС. Поэтому, на практике используется комбинация двух моделей: возвращаемые значения для стандартных сбоев и исключения/сигналы для фатальных ошибок. Это хороший компромисс между простотой и отказоустойчивостью 🔥
#theory #coding #Linux #errorhandling
===
Мои любимые посты в канале t.me/uimindev/37
Please open Telegram to view this post
VIEW IN TELEGRAM