Обработка ошибок короткого замыкания в C

Мне было интересно, есть ли лучший способ справиться с ситуацией в C, когда вы хотите выйти из функции, как только вы столкнетесь с ошибкой в ​​серии выражений. (в данном случае это функция, которая при ошибке возвращает NULL)

например в каком-то коде C, где они пытались сократить обработку ошибок, объединив серию операторов с AND (&&).

return doSomething() && 
     doSomething2() && 
     doSomething3() && ... ;

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

if (!(value = doSomething()))
    return NULL;
if (!(value = doSomething2()))
    return NULL;
etc
return value;

Но как насчет оценки ошибок короткого замыкания, которые я видел в сценариях perl и bash ..

int die(int retvalue) {
    exit(retvalue);
}
.....
(value = doSomething()) || die(1);
(value = doSomething2()) || die(2);
(value = doSomething3()) || die(3);
return value;

Основная проблема заключается в том, что RHS должен быть выражением, поэтому вы не можете перейти или вернуться из функции. Кто-нибудь сочтет это ценным, или оно слишком ограничено?

edit: Думаю, мне следовало включить новые строки в первый пример. Проблема в том, что вам нужно быть осторожным, если вы решите добавить еще одно выражение посередине.


person Nick Sonneveld    schedule 13.04.2009    source источник


Ответы (6)


Я видел использование GOTO в C именно для этой цели.

Поскольку в C нет конструкции «finally», требовался метод для освобождения всей вашей памяти, даже если вы выходили из функции раньше.

По сути, это выглядит следующим образом (может ли кто-нибудь исправить мой синтаксис C, если я ошибаюсь, я немного заржавел)

int foo()
{
    /* Do Stuff */
    if(!DoSomething())
        GOTO Failure;

    if(!DoSomething2())
        GOTO Failure;

    if(!DoSomething3())
        GOTO Failure;

    /* return success */
    return true; 

    Failure:
    /* release allocated resources */
    /* print to log if necessary */
    return false;
}

Важное примечание. Не используйте GOTO для выполнения. Их следует использовать только при ошибке и только для перехода к концу текущей функции. Если вы используете их для чего-то еще, вы создаете спагетти-код, который может разрушить ткань реальности. Просто не делай этого.

ИЗМЕНИТЬ

Как отмечалось на одном из плакатов, использование Exit (x) приведет к уничтожению всей вашей программы, что оставляет это решение зарезервированным для фатальных ошибок. Однако ваше исходное предложенное решение (DS () && DS2 () && DS3 ()) все в одной строке создает проблему для обработки ошибок.

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

int result1 = 0;
int result2 = 0;
int result3 = 0;

result1 = DoSomething();

if(result1)
    result2 = DoSomething2();

if(result2)
    result3 = DoSomething3();

return result1 && result2 && result3;

Потому что этот метод не исключает обработки ошибок.

person DevinB    schedule 13.04.2009
comment
Не знаю почему. Ваш ответ был вполне разумным. - person Nick Sonneveld; 13.04.2009
comment
Просто небольшое примечание, @devinb, ваше редактирование делает все три вещи, которые не совпадают с сокращенной версией в вопросе (или вашим оригиналом). Прекрасно, если нет побочных эффектов (или не влияет на производительность), но это не может быть гарантировано. - person paxdiablo; 13.04.2009

Если вас беспокоит только одна строка, почему бы вам просто не использовать:

return doSomething()
    && doSomething2()
    && doSomething3()
    && ... ;

Второй случай я пишу так:

if (!(value = doSomething()))  return NULL;
if (!(value = doSomething2())) return NULL;
: : : : :
return value;

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

person paxdiablo    schedule 13.04.2009
comment
Это улучшает читаемость, но не помогает тому факту, что «одна строка» перегружена. - person DevinB; 13.04.2009
comment
@devinb, я не знаю, почему это на самом деле проблема. Есть две вещи, которые вам нужно делать с исходным кодом: (а) читать и понимать его, (б) компилировать. Компилятор не имеет проблем ни с одной из версий, поэтому я решил, что это раздражает, потому что ее трудно читать. Переформатирование исправляет это. - person paxdiablo; 13.04.2009
comment
Пока решение компилируется с одинаковыми функциональными возможностями и производительностью, они все одинаковы. Некоторые, такие как решения типа goto, могут считаться менее читаемыми (не мной, поскольку я думаю, как компьютер :-). - person paxdiablo; 13.04.2009
comment
Я согласен с тем, что решение goto не идеально. Однако есть преимущество в том, чтобы отделить вызовы функций от операторов возврата, и это обработка ошибок. Будь то GOTO (не для всех), assert, exit, try_catch (это можно сделать на C) или что-то еще. - person DevinB; 13.04.2009
comment
Когда вы объединяете все эти вызовы функций (каждый с возможными побочными эффектами) в один оператор, вы ограничиваете возможности и гибкость ваших обработчиков для правильной оценки того, что пошло не так. Почему это так важно? Потому что 99,9% времени написания кода программа НЕ РАБОТАЕТ. - person DevinB; 13.04.2009
comment
Одна из проблем, возникающих при проверке нулей. В более поздней части кода doA () && (X! = Null || doB (x)) && .. я подумал, что это что-то вроде крысиного гнезда. - person Nick Sonneveld; 13.04.2009
comment
Ошибки будут всегда, так зачем усложнять их поиск? - person DevinB; 13.04.2009
comment
Мне гораздо больше нравится ваше второе решение :) - person DevinB; 13.04.2009
comment
NickS: очевидно, что если возвращаемые значения нужно проверить с чем-то другим, или есть другие проверки, вы бы не сделали этого в одном операторе. В этом случае я бы просто использовал свой второй пример, как показано (с вкраплениями других проверок по мере необходимости). Все дело в читабельности кода. - person paxdiablo; 13.04.2009

Как насчет этого?

int result;

result = doSomething();

result = result && doSomething2();

result = result && doSomething3();

return result;

Он использует короткое замыкание аналогично вашему первому примеру, но позволяет разбить его на несколько строк и добавить комментарии и т. Д.

person GrahamS    schedule 13.04.2009
comment
сделать этот результат && = doSomething2 () - person ; 13.04.2009
comment
Я бы стал, но в C. нет оператора && =. - person GrahamS; 13.04.2009

Я рекомендую против предложенной вами техники. Сначала exit в C завершает процесс; он не просто возвращается. Таким образом, это ограничено лишь несколькими случаями фатальных ошибок.

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

person kgiannakakis    schedule 13.04.2009

По моему опыту, идиоматический способ записать это на C состоит в том, чтобы запустить функцию с ряда операторов if, которые проверяют предварительные условия остальной части функции. Если предварительное условие не выполняется, выполняется немедленный возврат с кодом ошибки. Таким образом, когда вы перейдете к основной части тела функции, вы узнаете, что все в порядке, и сможете сохранить код как можно более простым.

Другими словами, у вас второй подход.

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

int copy_string(char *source, char *target, size_t max)
{
    if (source == NULL)
        return -1;
    if (target == NULL)
        return -1;
    source_len = strlen(source);
    if (source_len + 1 > max)   // +1 for NUL
        return -1;
    memcpy(target, source, source_len + 1);
    return 0;
}

(Мне нравится соглашение о системных вызовах Unix, согласно которому в случае ошибок возвращается -1, но это спорный вопрос стиля и не имеет отношения к этому вопросу.)

person Community    schedule 13.04.2009

Я видел это раньше:

int Function (void)
{
    int Result = DoSomething();

    if (Result) Result = DoSomething2();
    if (Result) Result = DoSomething3();
    if (Result) Result = DoSeomthing4();

    return Result;
}

Смотрится достаточно аккуратно, легко выравнивается. Он не выйдет сразу, но больше ничего не выполнит. Это, конечно, предполагает, что в случае успеха функции возвращают ненулевое значение. В противном случае вы можете просто использовать if (!Result), если функции возвращают ненулевое значение при ошибке.

person dreamlax    schedule 13.04.2009