Циклы действительно ли do-while-false распространены?

Я получил ответ на мой предыдущий вопрос. $ {methodName currMethodName editable = "false"} - это то, что я искал, поэтому код должен быть таким, как показано ниже.

Logger.log ("$ {classVar editable = false currClassName default = getClass ()}. $ {MethodName currMethodName editable =" false "} -> Ошибка:", level3);

25
задан Richard Chambers 14 June 2016 в 00:02
поделиться

19 ответов

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

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

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

46
ответ дан Glen 28 November 2019 в 17:33
поделиться

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

Затем код становится

    ...
    int errorCode = doItAll();
    ...


    int doItAll(void) {
      int errorCode;
      if(errorCode=doSomething()!=0)
        return errorCode;
      if(errorCode=doSomethingElse()!=0)
        return errorCode;
      if(errorCode=doSomethingElseNew()!=0)
        return errorCode;
      return 0;
    }

Объединение этого с очисткой также становится довольно простым, вы просто используете обработчик goto и error, как в ответе на затмения.

    ...
    int errorCode = doItAll();
    ...


    int doItAll(void) {
      int errorCode;
      void * aResource = NULL; // Somthing that needs cleanup after doSomethingElse has been called
      if(errorCode=doSomething()!=0) //Doesn't need cleanup
        return errorCode;
      if(errorCode=doSomethingElse(&aResource)!=0)
        goto cleanup;
      if(errorCode=doSomethingElseNew()!=0)
        goto cleanup;
      return 0;
    cleanup:
      releaseResource(aResource);
      return errorCode;
    }
0
ответ дан Michael Anderson 28 November 2019 в 17:33
поделиться

Честно говоря, более эффективные программисты на C / C ++, которых я знал, просто использовали бы gotos в таких условиях. Общий подход состоит в том, чтобы иметь единственную метку выхода со всей очисткой после нее. Есть только один путь возврата из функции. Когда логика очистки начинает усложняться / иметь условные выражения, тогда разбейте функцию на подфункции. Это довольно типично для системного кодирования в C / C ++ imo, где API, которые вы вызываете, возвращают коды ошибок, а не генерируют исключения.

В целом, gotos плохие. Поскольку использование, которое я описал, является настолько распространенным, я считаю, что это нормально.

0
ответ дан Frank Schwieterman 28 November 2019 в 17:33
поделиться

Для меня я бы предпочел:

if(!doSomething()) {
    doSomethingElse();
}
doSomethingNew();

Все остальное - это синтаксический шум, который скрывает три вызова функций. Внутри Else and New вы можете выдать ошибку, или, если старше, использовать longjmp, чтобы вернуться к более ранней обработке. Хороший, чистый и довольно очевидный.

0
ответ дан Paul W Homer 28 November 2019 в 17:33
поделиться

Я использую do { } while (false); время от времени, когда это кажется уместным. Я вижу, что это что-то вроде блока try / catch, в котором у меня есть код, который настроен как блок с серией решений с возможными исключениями, и мне нужно, чтобы в конце были различные пути через правила и логику для объединения блока.

Я почти уверен, что использую эту конструкцию только для программирования на С, и это не очень часто.

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

{
    int iCallStatus = 0;
    iCallStatus = doFunc1();
    if (iCallStatus == 0) iCallStatus = doFunc2();
    if (iCallStatus == 0) icallStatus = doFunc3();
}

Это коротко, и смысл прост и ясен даже без комментариев.

Время от времени я сталкивался с тем, что этот довольно простой последовательный поток процедурных шагов не относится к конкретному требованию. Что мне нужно, так это создать блок кода с различными решениями, обычно включающими циклы или итерации по некоторым сериям объектов данных, и я хочу рассматривать эту серию как разновидность транзакции, в которой транзакция будет зафиксирована, если не будет ошибки или отменена если при обработке транзакции обнаруживается какая-либо ошибка. Как часть этого блока данных, я могу создать набор временных переменных для области действия do { } while (false);. Когда я использую это, я всегда помещаю комментарий, указывающий, что это одна итерация, выполняемая в то время, что-то вроде:

do {  // single loop code block begins
    // block of statements for business logic with single ending point
} while (false);  // single loop code block ends

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

.

Причина, по которой я предпочитаю эту конструкцию вместо использования оператора goto, заключается в том, что использование скобок и отступов облегчает чтение исходного кода. С помощью моего редактора я легко могу найти верх и низ блока, а отступ позволяет визуализировать код как блок с единственной точкой входа и известной конечной точкой. В блоке может быть несколько точек выхода, но я знаю, где они все окажутся. Использование этого означает, что я могу создавать локализованные переменные, которые выйдут из области видимости, хотя использование скобок без do { } while (false); также делает это. Однако я использую do while, потому что мне нужен перерыв; способность также.

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

1114 Единственное, о чем я могу подумать, когда я использовал это, было с чем-то немного грубым, и это помогло прояснить ситуацию и упростить прерывание обработки. Так что в основном я использовал это похоже на создание исключения с помощью try / catch.

1
ответ дан Richard Chambers 28 November 2019 в 17:33
поделиться

Как насчет этой версии?

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

bool statusOkay = true;

if (statusOkay)
    statusOkay = (doSomething() == 0);

if (statusOkay)
    statusOkay = (doSomethingElse() == 0);

if (statusOkay)
    statusOkay = (doSomethingElseNew() == 0);

Но если вы действительно заинтересованы в краткость вашего второго метода, тогда вы могли бы рассмотреть этот подход:

bool statusOkay = true;

statusOkay = statusOkay && (doSomething() == 0);
statusOkay = statusOkay && (doSomethingElse() == 0);
statusOkay = statusOkay && (doSomethingElseNew() == 0);

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

1
ответ дан GrahamS 28 November 2019 в 17:33
поделиться

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

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

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

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

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

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

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

3
ответ дан Bill K 28 November 2019 в 17:33
поделиться

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

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

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

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

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

0
ответ дан 28 November 2019 в 17:33
поделиться

There seems to be a deeper problem here than your control constructs. Why do you have such complex error control? I.e. you seem to have multiple ways of handling different errors.

Typically, when I get an error, I simply break off the operation, present an error message to the user, and return control to the event loop, if an interactive application. For batch, log the error, and either continue processing on the next data item or abort the processing.

This kind of control flow is easily handled with exceptions.

If you have to handle error numbers, then you can effectively simulate exceptions by continuing normal error processing in case of an error, or returning the first error. Your continued processing after an error occurs seems to be very fragile, or your error conditions are really control conditions not error conditions.

0
ответ дан 28 November 2019 в 17:33
поделиться

Сделать его коротким, компактным и легко читаемым?

Как насчет:

if ((errorcode = doSomething()) == 0
&&  (errorcode = doSomethingElse()) == 0
&&  (errorcode = doSomethingElseNew()) == 0)
    maybe_something_here;
return errorcode; // or whatever is next
8
ответ дан 28 November 2019 в 17:33
поделиться

Классическая идиома C:

if( (error_val = doSomething()) == 0)
{ 
   //Manage error condition
}

Обратите внимание, что C возвращает присвоенное значение из присваивания, что позволяет выполнить тест. Часто люди пишут:

if( ! ( error_val = doSomething()))

, но я оставил == 0 для ясности.

Что касается ваших идиом ...

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

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

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

Конечно, исключения лучше обоих ...

11
ответ дан 28 November 2019 в 17:33
поделиться

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

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

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

16
ответ дан 28 November 2019 в 17:33
поделиться

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

Соответствующие экспонаты: файлы 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 */
1
ответ дан 28 November 2019 в 17:33
поделиться

Если вы используете 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;

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

54
ответ дан 28 November 2019 в 17:33
поделиться

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

try {
  DoSomeThing();
  DoSomethingElse();
  DoSomethingNew();
  .
  .
  .
}
catch(DoSomethingException e) {
  .
  .
}
catch(DoSomethingElseException e) {
  .
  .
}
catch(DoSomethingNewException e) {
  .
  .
}
catch(...) {
  .
  .
}
3
ответ дан 28 November 2019 в 17:33
поделиться

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

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

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

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


pointerA * pa = NULL;
pointerB * pb = NULL;
pointerB * pc = NULL;
BOOL bRet = FALSE;
pa = new pointerA();
do {
  if (!dosomethingWithPA( pa ))
    break;
   pb = new poninterB();
  if(!dosomethingWithPB( pb ))
    break;
  pc = new pointerC();
  if(!dosemethingWithPC( pc ))
    break;
  bRet = TRUE;
} while (FALSE);

//
// cleanup
//
if (NULL != pa)
 delete pa;
if (NULL != pb)
 delete pb;
if (NULL != pc)
 delete pc;

return bRet;

в отличие от


pointerA * pa = NULL;
pointerB * pb = NULL;
pointerB * pc = NULL;

pa = new pointerA(); if (!dosomethingWithPA( pa )) { delete pa; return FALSE; }

pb = new poninterB(); if(!dosomethingWithPB( pb )) { delete pa; delete pb; return FALSE; } pc = new pointerC(); if(!dosemethingWithPAPBPC( pa,pb,pc )) { delete pa; delete pb; delete pc; return FALSE; }

delete pa; delete pb; delete pc; return TRUE;

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

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