Язык C не имеет встроенного механизма работы с ошибками. Но работать с ошибками как-то нужно, значит это придётся делать вручную.


Конечная цель — писать такие программы, где ошибка считается нормальным поведением. После ошибки можно прибрать за собой, написать в лог, в телеграм-канал, и самостоятельно заершить работу.


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


Есть, например, функция, которая работает с файлами.
Попробуем в случае ошибки завершить работу программы:


void
kill_running_instance
(char *pid_file)
{
FILE *f = fopen(pid_file, "r"
if (f == NULL) {
fprintf(stderr, "can not open file for reading: %s: %s\n", pid_file, strerror(errno));
exit(1);
}
....
}


Проблема в том, что так мы не сможем вызвать код очистки. Попробуем исправить функцию:


void
kill_running_instance
(bool *ok, char *pid_file)
{
// reset error
*ok = true;

FILE *f = fopen(pid_file, "r"

// indicate error
if (f == NULL) {
bool *ok = false;
return;
}

....
}


Так мы сможем извне узнать — функция нормально выполнила свою работу или что-то пошло неправильно.


Здесь появляется главная проблема. Как узнать что конкретно пошло неправильно? Хотя бы для того чтобы записать ошибку в лог и в консоль:


bool *ok;
if (kill_running_instance(&ok, pid_file), !ok) {
// how can we find out what went wrong?
}


Это произошла ошибка открытия файла или ошибка чтения? Узнать это невозможно.


Небольшая заметка: посмотреть в errno не получится. Эта переменная используется для уточнения ошибки, а не полного описания ошибки. Например, strerror(errno) скажет Permission denied, а как извне узнать к чему это ошибка относится — всё ещё неизвестно.


Чтобы решить эту проблему, я придумал возвращать не bool *ok, а сообщение об ошибке и тип ошибки. Выглядит вот так:


void
kill_running_instance
(struct error *e, char *pid_file)
{
FILE *f = fopen(pid_file, "r"
if (f == NULL) {
ERROR_PRINTF(e, SYSTEM, "can not open file for reading: %s: %s", pid_file, strerror(errno));
return;
}
....
}

....

struct error e;
INIT_ERROR(&e);
if (kill_running_instance(&e, pid_file), e.is_set) {
print_error(&e);
deinit_error(&e);
goto cleanup;
}


Ошибка будет выведена в консоль. Это значит, что контекст ошибки нам известен извне. Это значит, что с этой ошибкой мы можем работать как захотим — писать в лог, отправлять по почте, etc. Таким образом, конечная цель выполнена.


Кстати, благодаря макросам известны имя файла и номер линии где был вызван ERROR_PRINTF.


error определён так:


enum error_type {

/*
Standard library function fails
*/
SYSTEM,
};

struct error {
enum error_type type;
char *msg;
bool is_set;
};


INIT_ERROR присваивает структуре дефолтные значения.


Функция, раскрываемая макросом ERROR_PRINTF, присваивает тип ошибки и сообщение об ошибке (сообщение об ошибке копируется, всё норм).


Теперь, собственно, вопросы.




  1. Много ли Open Source проектов, где работа с ошибками организована похожим образом? Буду благодарен, если покажете.




  2. Если в Open Source проектах с ошибками работают по-другому, значит, логично, они считают свой способ лучше моего. Можете показать как по-другому работают с ошибками и объяснить почему эти способы лучше моего?




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




Заранее спасибо.









 






URL записи