Подтвердить что ты не робот

Элегантная проверка ошибок

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

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 вызывает функцию и выполняет рудиментарную проверку ошибок.

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

4b9b3361

Ответ 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. Необычный код - это всего лишь глазная конфета.