Являются ли циклы do-while-false распространенными? - PullRequest
25 голосов
/ 11 сентября 2009

Некоторое время назад я переключил способ обработки ошибок стиля c.

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

int errorCode = 0;

errorCode = doSomething();
if (errorCode == 0)
{
   errorCode = doSomethingElse();
}

...

if (errorCode == 0)
{
   errorCode = doSomethingElseNew();
}

Но недавно я написал это так:

int errorCode = 0;

do
{       
   if (doSomething() != 0) break;
   if (doSomethingElse() != 0) break;
   ...
   if (doSomethingElseNew() != 0) break;
 } while(false);

Я видел много кода, где после ошибки ничего не выполняется, но он всегда был написан в первом стиле. Кто-нибудь еще использует этот стиль, а если нет, то почему?

Редактировать: просто для пояснения, обычно эта конструкция использует errno в противном случае я присваю значение int перед разрывом. Также в предложениях if (error == 0 ) обычно содержится больше кода, чем просто одного вызова функции. Впрочем, есть над чем подумать.

Ответы [ 20 ]

54 голосов
/ 11 сентября 2009

Если вы используете C ++, просто используйте исключения. Если вы используете C, первый стиль отлично работает. Но если вам действительно нужен второй стиль, просто используйте gotos - это именно тот тип ситуации, когда gotos действительно является самой ясной конструкцией.

    int errorCode = 0;

    if ((errorCode = doSomething()) != 0) goto errorHandler;
    if ((errorCode = doSomethingElse()) != 0) goto errorHandler;
      ...
    if ((errorCode = doSomethingElseNew()) != 0) goto errorHandler;

    return;
errorHandler:
    // handle error

Да, gotos может быть плохим, и исключения или явная обработка ошибок после каждого вызова могут быть лучше, но gotos гораздо лучше, чем использовать другую конструкцию, чтобы попытаться плохо имитировать их. Использование gotos также упрощает добавление другого обработчика ошибок для конкретной ошибки:

    int errorCode = 0;

    if ((errorCode = doSomething()) != 0) goto errorHandler;
    if ((errorCode = doSomethingElse()) != 0) goto errorHandler;
      ...
    if ((errorCode = doSomethingElseNew()) != 0) goto errorHandlerSomethingElseNew;

    return;
errorHandler:
    // handle error
    return;
errorHandlerSomethingElseNew:
    // handle error
    return;

Или, если обработка ошибок - это больше разновидность «раскручивания / очистки того, что вы сделали», вы можете структурировать ее следующим образом:

    int errorCode = 0;

    if ((errorCode = doSomething()) != 0) goto errorHandler;
    if ((errorCode = doSomethingElse()) != 0) goto errorHandler1;
      ...
    if ((errorCode = doSomethingElseNew()) != 0) goto errorHandler2;

errorHandler2:
    // clean up after doSomethingElseNew
errorHandler1:
    // clean up after doSomethingElse
errorHandler:
    // clean up after doSomething
    return errorCode;

Эта идиома дает вам преимущество, не повторяя код очистки (конечно, если вы используете C ++, RAII будет охватывать код очистки еще более чисто.

46 голосов
/ 11 сентября 2009

Второй фрагмент выглядит просто неправильно. Вы фактически заново изобрели goto.

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

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

16 голосов
/ 11 сентября 2009

Первый стиль - это рисунок опытного глазного яблока сразу.

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

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

11 голосов
/ 11 сентября 2009

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

Конечно, исключения превосходят оба ...

8 голосов
/ 11 сентября 2009

Сделать его коротким, компактным и удобным для чтения? Как насчет: if ((errorcode = doSomething()) == 0 && (errorcode = doSomethingElse()) == 0 && (errorcode = doSomethingElseNew()) == 0) maybe_something_here; return errorcode; // or whatever is next

4 голосов
/ 11 сентября 2009

Почему бы не заменить do / while и break на функцию и вернуть взамен?

Вы заново изобрели goto.

3 голосов
/ 11 сентября 2009

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

Первый может стать ДЕЙСТВИТЕЛЬНО раздражающим после того, как ваш код достигнет определенного размера, потому что у него много шаблонов.

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

fn() {
    while(true) {
        if(doIt())
            handleError();//error detected...
    }
}

bool doIt() {
    if(!doThing1Succeeds())
        return true;
    if(!doThing2Succeeds())
        return true;
    return false;
}

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

Это функционально идентично циклу while / bail без нетрадиционного синтаксиса (и также немного проще для понимания, потому что вы отделяете проблемы цикла / обработки ошибок от вопроса "что ваша программа делает в данном цикле" ».

3 голосов
/ 11 сентября 2009

А как насчет использования исключений?

try {
  DoSomeThing();
  DoSomethingElse();
  DoSomethingNew();
  .
  .
  .
}
catch(DoSomethingException e) {
  .
  .
}
catch(DoSomethingElseException e) {
  .
  .
}
catch(DoSomethingNewException e) {
  .
  .
}
catch(...) {
  .
  .
}
2 голосов
/ 11 сентября 2009

Это должно быть сделано через исключения, по крайней мере, если тег C ++ правильный. В этом нет ничего плохого, если вы используете только C, хотя я предлагаю вместо этого использовать логическое значение, поскольку вы не используете возвращенный код ошибки. Вам не нужно вводить! = 0 либо тогда ...

1 голос
/ 11 сентября 2009

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

Соответствующие экспонаты: файлы sqlstmt.ec, upload.ec, reload.ec из SQLCMD исходный код (нет, не самозванец Microsoft; мой). Расширение '.ec' означает, что файл содержит ESQL / C - встроенный SQL в C, который предварительно обработан для простого C; вам не нужно знать ESQL / C, чтобы увидеть структуры цикла. Все петли помечены:

    /* This is a one-cycle loop that simplifies error handling */
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...