Элегантная проверка ошибок
Наш код (в простой реализации библиотеки) начинает выглядеть следующим образом:
err = callToUnderlyingLibrary1();
if (err!=0) {
printf ("blah %d\n", err);
...
}
err = callToUnderlyingLibrary2();
if (err!=0) {
printf ("blah %d\n", err);
...
}
err = callToUnderlyingLibrary3();
if (err!=0) {
printf ("blah %d\n", err);
...
}
Это громоздко и уродливо. Есть лучший способ сделать это? Возможно, используя препроцессор С? Я думал что-то вроде:
CHECK callToUnderlyingLibrary1();
CHECK callToUnderlyingLibrary2();
CHECK callToUnderlyingLibrary3();
где макрос CHECK вызывает функцию и выполняет рудиментарную проверку ошибок.
Существуют ли предпочтительные идиоматические способы обращения с этим?
Ответы
Ответ 1
Обычно в C используется goto
для обработки ошибок:
int foo()
{
if (Function1() == ERROR_CODE) goto error;
...
struct bar *x = acquire_structure;
...
if (Function2() == ERROR_CODE) goto error0;
...
release_structure(x);
return 0;
error0:
release_structure(x);
error:
return -1;
}
Это можно улучшить с помощью макросов и более умного потока команд (во избежание повторения кода очистки), но я надеюсь, что вы видите эту точку.
Ответ 2
Другой подход на основе макросов, который вы можете использовать для смягчения недостатков на C довольно легко:
#define CHECK(x) do { \
int retval = (x); \
if (retval != 0) { \
fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
return /* or throw or whatever */; \
} \
} while (0)
Затем, чтобы вызвать его, вы:
CHECK(doSomething1());
CHECK(doSomething2());
// etc.
Для бонусных очков вы можете легко расширить макрос CHECK, чтобы взять второй аргумент y, что нужно сделать при ошибке:
#define CHECK(x, y) do { \
int retval = (x); \
if (retval != 0) { \
fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
y; \
} \
} while (0)
// We're returning a different error code
CHECK(someFunction1(foo), return someErrorCode);
// We're actually calling it from C++ and can throw an exception
CHECK(someFunction2(foo), throw SomeException("someFunction2 failed")):
Ответ 3
Я думаю, вы должны посмотреть на исключения и обработку исключений. http://www.cplusplus.com/doc/tutorial/exceptions/
try{
callToUnderlyingLibrary1();
callToUnderlyingLibrary2();
callToUnderlyingLibrary3();
}catch(exception& e)
//Handle exception
}
ваши функции библиотеки могут генерировать исключения, если есть ошибка
Ответ 4
Вот предложение, вам может или не понравится:
- заставить ваши функции возвращать 0 при неудаче, что-то еще при успешном завершении
- Если что-то не работает в ваших функциях, установите для них глобальную (или статическую) переменную в код ошибки (например,
errno
)
- создайте функцию
die()
, которая печатает ошибку в зависимости от кода ошибки (или того, что вы хотите сделать)
- вызовите свои функции с помощью
do_something(foo, bar) || die("Argh...");
Ответ 5
Вы могли бы сделать то, что вы сказали, это какой-то рудиментарный макрос:
#define CHECK(x) (err = x()); \
if (err) { \
printf("blah %d on line %d of file %s\n", err, __LINE__, __FILE__); \
} \
else (void)0
И вы можете использовать его как
int err = 0;
CHECK(callToUnderlyingLibrary1); // don't forget the semicolon at the end
CHECK(callToUnderlyingLibrary2);
CHECK(callToUnderlyingLibrary3);
Ответ 6
Я предпочитаю вариант Александры К. goto-подход:
int foo()
{
int rv = 0;
struct bar *x = NULL;
struct bar *y = NULL;
rv = Function1();
if (rv != OK){
goto error;
}
//...
x = acquire_structure();
if (x==NULL){
rv = ERROR_MEMORY;
goto error;
}
//...
rv = Function2();
if (rv != OK){
goto error;
}
//...
y = acquire_structure();
if (y==NULL){
rv = ERROR_MEMORY;
goto error;
}
//...
rv = release_structure(x);
x = NULL;
if (rv != OK){
goto error;
}
rv = release_structure(y);
y = NULL;
if (rv != OK){
goto error;
}
return OK;
error:
if (x!=NULL){
release_structure(x);
}
return rv;
}
Когда вы используете несколько точек назначения, их легко смешивать. Или, возможно, вы переместите инициализацию переменной, но забудьте обновить gotos. И может быть очень сложно протестировать все способы, с которыми может выйти C-метод.
Я предпочитаю иметь единственное место назначения, которое выполняет всю очистку. Я считаю, что легче избежать ошибок.
Ответ 7
Нет 'goto', используйте только 1 'return' в функциях. Это элегантный код.
ИМХО, точка вопроса OP, и все ответы говорят о методах FANCY. Необычный код - это всего лишь глазная конфета.