Элегантная проверка ошибок - PullRequest
25 голосов
/ 03 августа 2011

Наш код (в простой реализации библиотеки) начинает выглядеть так:

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);
...
}

Это громоздко и безобразно. Есть лучший способ сделать это ? Возможно, используя препроцессор C? Я думал что-то вроде:

CHECK callToUnderlyingLibrary1();
CHECK callToUnderlyingLibrary2();
CHECK callToUnderlyingLibrary3();

где макрос CHECK вызывает функцию и выполняет элементарную проверку ошибок.

Есть ли предпочтительные идиоматические способы справиться с этим?

Ответы [ 7 ]

16 голосов
/ 04 августа 2011

Обычно в 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;
}

Это можно улучшить с помощью макросов и более умного потока команд (чтобы избежать повторения кода очистки), но я надеюсь, что вы понимаете суть.

13 голосов
/ 04 августа 2011

Другой подход на основе макросов, который вы можете использовать, чтобы довольно легко устранить недостатки в 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")):
6 голосов
/ 03 августа 2011

Я думаю, вы должны посмотреть на исключения и обработку исключений.http://www.cplusplus.com/doc/tutorial/exceptions/

try{    
    callToUnderlyingLibrary1();
    callToUnderlyingLibrary2();
    callToUnderlyingLibrary3();
}catch(exception& e)
    //Handle exception
}

функции вашей библиотеки могут выдавать исключения в случае ошибки

3 голосов
/ 04 августа 2011

Вот предложение, вам может или не может понравиться:

  • заставить ваши функции возвращать 0 в случае сбоя, что-то еще в случае успеха
  • если что-то не получится в ваших функциях,попросите их установить глобальную (или статическую) переменную для кода ошибки (например, errno)
  • , создать функцию die(), которая печатает ошибку в зависимости от кода ошибки (или от того, что вы хотите, чтобы это делалось)
  • вызовите ваши функции с помощью do_something(foo, bar) || die("Argh...");
1 голос
/ 04 августа 2011

Я предпочитаю вариант гото-подхода Александры С.:

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-метод может потерпеть неудачу.

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

1 голос
/ 04 августа 2011

Вы можете сделать то, что вы сказали, это какой-то элементарный макрос:

#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);
0 голосов
/ 04 августа 2011

Нет 'goto', используйте только 1 'return' в функциях.Это элегантный код.

ИМХО, вопрос OP и все ответы говорят о методах FANCY.Необычный код - это просто приятный глаз.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...