Какие-нибудь хорошие идиомы для обработки ошибок в прямых программах на C?

Вот как я получаю userId, userName, authToken, authTokenSecret и profileImageURL

[[Twitter sharedInstance] logInWithCompletion:^(TWTRSession *session, NSError *error) {
        if (session != nil) {
            SPTWUser *user = [[SPTWUser alloc] init];
            user.userID = session.userID;
            user.userName = session.userName;
            user.authToken = session.authToken;
            user.authTokenSecret = session.authTokenSecret;

            TWTRAPIClient *client = [[TWTRAPIClient alloc] initWithUserID:user.userID];
            [client loadUserWithID:user.userID completion:^(TWTRUser * _Nullable user, NSError * _Nullable error) {
                NSLog(@"%@",user.profileImageURL);
            }];
        }
    }];

Ссылка TWTRAPIClient Class , Класс TWTRUser , ] TWTRSession Class

29
задан Will Hartung 7 May 2010 в 16:11
поделиться

11 ответов

Два типичных шаблона:

int major_func()
{
    int err = 0;

    if (err = minor_func1()) return err;
    if (err = minor_func2()) return err;
    if (err = minor_func3()) return err;

    return 0;
}

int other_idea()
{
    int err = minor_func1();
    if (!err)
        err = minor_func2();
    if (!err)
        err = minor_func3();
    return err;            
}

void main_func()
{
    int err = major_func();
    if (err)
    {
        show_err();
        return;
    }
    happy_happy_joy_joy();

    err = other_idea();
    if (err)
    {
        show_err();
        return;
    }
    happy_happy_joy_joy();
}
16
ответ дан 28 November 2019 в 00:57
поделиться

Вы должны проверить, что DirectX сделал с HRESULT - в основном это. Есть причина, по которой возникло исключение. В качестве альтернативы, если вы работаете на Win32, у них есть SEH, который запускается в программах C.

3
ответ дан 28 November 2019 в 00:57
поделиться

А теперь кое-что совершенно другое ...

Другой подход - использовать структуру, содержащую информацию об ошибке, например:

struct ErrorInfo
{
    int errorCode;
    char *errorMessage;
#if DEBUG
    char *functionName;
    int lineNumber;
#endif
}

Лучший способ использовать это - вернуть результаты вашего метода в виде кода возврата ( например, «FALSE для сбоя», или «указатель файла или NULL в случае сбоя», или «размер буфера или 0 в случае сбоя» и т. д.) и передать ErrorInfo в качестве параметра, который вызываемая функция заполнит, если что-то не получается.

Это дает подробные отчеты об ошибках: если метод не работает, вы можете ввести больше, чем простой код ошибки (например, сообщение об ошибке, строку кода и файл с ошибкой или что-то еще). Хорошая вещь в том, что это структура, заключается в том, что если вы придумаете что-нибудь, что-нибудь полезное позже, вы можете просто добавить это - например, в моей структуре выше я разрешил для отладочной сборки включать местоположение ошибки ( файл / строка), но вы можете добавить туда дамп всего стека вызовов в любое время без необходимости изменять какой-либо клиентский код.

Вы можете использовать глобальную функцию для заполнения ErrorInfo, чтобы можно было аккуратно управлять возвратом ошибки, и вы можете обновить структуру, чтобы легко предоставить дополнительную информацию:

if (error)
{
    Error(pErrorInfo, 123, "It failed");
    return(FALSE);
}

...и у вас могут быть варианты этой функции, которые возвращают FALSE, 0 или NULL, чтобы большинство возвращаемых ошибок можно было сформулировать в виде одной строки:

if (error)
    return(ErrorNull(pErrorInfo, 123, "It failed"));

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

Кроме того, вы можете пойти дальше, чтобы создать цепочку отчетов об ошибках (например, «InnerException»):

struct ErrorInfo
{
    int errorCode;
    char *errorMessage;
    ...
    ErrorInfo *pInnerError;    // Pointer to previous error that may have led to this one
}

Затем, если вы «поймаете» ошибку из функция, которую вы вызываете, вы можете создать новое описание ошибки более высокого уровня и вернуть цепочку этих ошибок. например, «Скорость мыши вернется к значению по умолчанию» (потому что) «Блок предпочтений 'MousePrefs' не может быть найден «(из-за)« Ошибка чтения XML »(из-за)« Файл не найден ».

т.е.

FILE *OpenFile(char *filename, ErrorInfo *pErrorInfo)
{
    FILE *fp = fopen(filename, "rb");
    if (fp == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't open file"));

    return(fp);
}

XmlElement *ReadPreferenceXml(ErrorInfo *pErrorInfo)
{
    if (OpenFile("prefs.xml", pErrorInfo) == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't read pref"));
    ...
}

char *ReadPreference(char *prefName, ErrorInfo *pErrorInfo)
{
    XmlElement *pXml = ReadPreferenceXml(pErrorInfo);
    if (pXml == NULL)
        return(ChainedErrorNull(pErrorInfo, "Couldn't read pref"));
    ...
}
2
ответ дан 28 November 2019 в 00:57
поделиться

Что вы делаете в операторах else? Если ничего, попробуйте следующее:

int err = func1(...);
if (err) {
    return err;
}

err = func2(...);
if (err) {
    return err;
}

err = func3(...);

return err;

Таким образом, вы замыкаете всю функцию, даже не беспокоясь о следующих вызовах функций.

EDIT

Возвращаясь назад и читая снова, я понимаю, что не имеет значения, что вы делаете в ваших else утверждениях. Такой код легко может идти сразу после блоков if.

8
ответ дан 28 November 2019 в 00:57
поделиться

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

return func1() && func2() && func3()
6
ответ дан 28 November 2019 в 00:57
поделиться

Один из подходов OpenGL - не возвращать ошибки из функций вообще, а представлять состояние ошибки, которое можно проверить после вызова функции. . В этом подходе есть одна приятная особенность: когда у вас есть функция, которая действительно хочет вернуть что-то, кроме кода ошибки, вы можете обрабатывать ошибки таким же образом. Еще одна вещь, которая хороша в этом, заключается в том, что если пользователь хочет вызвать несколько функций и добиться успеха только в том случае, если все они были успешными, вы можете проверить наличие ошибок после x количества вызовов.

/* call a number of functions which may error.. */
glMatrixMode(GL_MODELVIEW);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);

/* ...check for errors */
if ((error = glGetError()) != GL_NO_ERROR) {
    if (error == GL_INVALID_VALUE)
        printf("error: invalid value creating view");
    else if (error == GL_INVALID_OPERATION)
        printf("error: invalid operation creating view");
    else if (error == GL_OUT_OF_MEMORY)
        printf("error: out of memory creating view");
}
4
ответ дан 28 November 2019 в 00:57
поделиться

Другие предлагали хорошие идеи. Вот идиомы, которые я видел

int err;
...
err = foo(...);
if (err)
    return err;
...

. Вы можете добавить макрос к чему-то вроде

#define dERR int err=0
#define CALL err = 
#define CHECK do { if (err) return err } while(0)
...
void my_func(void) {
   dERR;
   ...
   CALL foo(...);
   CHECK;

или, если вы чувствуете себя действительно мотивированным, поиграйте с CALL и CHECK, чтобы их можно было использовать как

CALL foo(...) CHECK;

или

CALL( foo(...) );

] -

Часто функции, которые должны выполнять очистку при выходе (например, свободная память), записываются следующим образом:

int do_something_complicated(...) {
    ...

    err = first_thing();
    if (err)
       goto err_out;

    buffer = malloc(...);
    if (buffer == NULL)
        goto err_out

    err = another_complicated(...);
    if (err)
        goto err_out_free;

    ...

   err_out_free:
    free(buffer);
   err_out:
    return err; /* err might be zero */
}

Вы можете использовать этот шаблон или попытаться упростить его с помощью макросов.

-

Наконец, если вы чувствуете / действительно / мотивированы, вы можете использовать setjmp / longjmp.

int main(int argc, char *argv[]) {
    jmp_buf on_error;
    int err;
    if (err = setjmp(on_error)) {
        /* error occurred, error code in err */
        return 1;
    } else {
        actual_code(..., on_error);
        return 0;
    }
}
void actual_code(..., jmp_buf on_error) {
    ...
    if (err)
        longjmp(on_error, err);
}

По сути, объявление новой функции jmp_buf и setjmp как установка блока try. Случай, когда setjmp возвращает ненулевое значение, - это ваш улов, а вызов longjmp - это ваш бросок. Я написал это, передавая jmp_buf на случай, если вам нужны вложенные обработчики (например, если вам нужно освободить материал перед сигналом ошибки); если вам это не нужно, не стесняйтесь объявлять err и jmp_buf как глобальные переменные.

В качестве альтернативы вы можете использовать макросы для простой передачи аргумента. Я бы предложил способ реализации Perl:

#define pERR jmp_buf _err_handler
#define aERR _err_handler
#define HANDLE_ERRORS do { jmp_buf _err_handler; int err = setjmp(_err_handler);
#define END_HANDLE while(0)
#define TRY if (! err)
#define CATCH else
#define THROW(e) longjmp(_err_handler, e)

void always_fails(pERR, int other_arg) {
    THROW(42);
}
void does_some_stuff(pERR) {
    normal_call(aERR);
    HANDLE_ERRORS
      TRY {
        always_fails(aERR, 23);
      } CATCH {
        /* err is 42 */
      }
    END_HANDLE;
}
int main(int argc, char *argv[]) {
    HANDLE_ERRORS
      TRY {
        does_some_stuff(aERR);
        return 0;
      } CATCH {
        return err;
      }
    DONE_ERRORS;
}

-

Уф. Я задолбался. (Сумасшедшие непроверенные примеры. Некоторые детали могут быть неточными.)

4
ответ дан 28 November 2019 в 00:57
поделиться

Вы можете стать действительно глупым и сделать продолжения:

void step_1(int a, int b, int c, void (*step_2)(int), void (*err)(void *) ) {
     if (!c) {
         err("c was 0");
     } else {
         int r = a + b/c;
         step_2(r);
     }
}

Возможно, это не совсем то, что вы хотите сделать, но это то, как используются многие функциональные языки программирования, и даже чаще, как они моделируют свой код для оптимизации.

2
ответ дан 28 November 2019 в 00:57
поделиться

Если у вас есть ресурсы, которые необходимо выпущен в конце, то иногда может пригодиться старый верный goto !

int
major_func(size_t len)
{
    int err;
    char *buf;

    buf = malloc(len);

    if (err = minor_func1(buf))
        goto major_func_end;
    if (err = minor_func2(buf))
        goto major_func_end;
    if (err = minor_func3(buf))
        goto major_func_end;

major_func_end:
    free(buf);
    return err;
}
32
ответ дан 28 November 2019 в 00:57
поделиться

Что-то, что я недавно видел, это идом:

int err;
do 
{
  err = func1 (...);
  if (!err) break;

  err = func2 (...);
  if (!err) break;

  err = func3 (...);
  if (!err) break;

  /* add more calls here */

} while (0);

if (err)
{
  /* handle the error here */
  return E_ERROR; /* or something else */
}
 else 
{
  return E_SUCCESS;
}

Pro аргументы:

Он избегает goto (злоупотребляет while (0) / break комбинация для этого). Зачем тебе это нужно? Он снижает цикломатическую сложность и по-прежнему проходит большинство проверок статического анализатора кода (кто-нибудь MISRA?). Для проектов, которые тестируются на цикломатическую сложность, это послание бога, потому что оно объединяет все элементы инициализации.

Contra arguments:

Значение конструкции цикла do / while неочевидно, потому что конструкция цикла используется в качестве дешевой замены goto, и это можно увидеть только в конце цикла. Я уверен, что впервые эта конструкция вызовет множество "WTF" -моментов.

По крайней мере, комментарий необходим, чтобы объяснить, почему код написан так, как он требуется.

1
ответ дан 28 November 2019 в 00:57
поделиться

Вот довольно информативная статья и тестовый файл из серии статей IBM Unix:

Ошибки: errno в программах UNIX

Работа со стандартным механизмом ошибок

https://www.ibm.com/developerworks/aix/library/au-errnovariable/

Еще одним хорошим примером того, как реализовать коды выхода, является исходный код curl ( человек 1 локон).

1
ответ дан 28 November 2019 в 00:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: