while (true) и loop-break - anti-pattern?

Рассмотрим следующий код:

 public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

Предположим, что этот процесс включает конечное, но зависящее от ввода количество шагов; цикл разработан для завершения самостоятельно в результате алгоритма и не предназначен для запуска неограниченно (до отмены внешним событием). Поскольку тест, чтобы проверить, должен ли цикл заканчиваться, находится в середине логического набора шагов, сам цикл while в настоящий момент не проверяет ничего значимого; вместо этого проверка выполняется в «правильном» месте в концептуальном алгоритме.

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

Теперь код может быть структурирован следующим образом:

 public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

Однако это дублирует вызов метода в коде, нарушая DRY; if TransformInSomeWay позже были заменены каким-либо другим методом, оба вызова должны были быть найдены и изменены (и тот факт, что есть два, может быть менее очевидным в более сложной части кода).

Вы также можете записать его так:

 public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

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

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

Итак, что «лучше»? лучше ли отвечать на проверку состояния на цикл while, структурируя логику вокруг цикла? Или лучше структурировать логику «естественным» образом, как указано в требованиях или концептуальном описании алгоритма, хотя это может означать обход встроенных возможностей цикла?

31 голос | спросил KeithS 29 MarpmThu, 29 Mar 2012 22:42:29 +04002012-03-29T22:42:29+04:0010 2012, 22:42:29

9 ответов


14

Придерживайтесь своего первого решения. Петли могут стать очень привлекательными, и один или несколько чистых перерывов могут быть намного проще для вас, любого, кто смотрит на ваш код, оптимизатор и производительность программы, чем сложные if-statements и добавленные переменные. Мой обычный подход - создать цикл с чем-то вроде «while (true)» и получить лучшее кодирование, которое я могу. Часто я могу и затем заменяю (и) замену стандартным управлением контуром; В конце концов, большинство циклов довольно просты. Конечное условие должно быть проверено структурой цикла по причинам, о которых вы упоминаете, но not за счет добавления целых новых переменных, добавления if-операторов и повторения кода, все из которых даже больше подвержены ошибкам.

ответил RalphChapin 30 MarpmFri, 30 Mar 2012 20:04:01 +04002012-03-30T20:04:01+04:0008 2012, 20:04:01
12

Это только значимая проблема, если тело цикла нетривиально. Если тело настолько мало, то его анализ таким образом тривиален, а не проблема вообще.

ответил DeadMG 29 MarpmThu, 29 Mar 2012 23:58:17 +04002012-03-29T23:58:17+04:0011 2012, 23:58:17
7

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

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

, но решение break - это самый чистый рендеринг.

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

  

Труднее понять, как выйти из цикла,

Не ошибайтесь, обсуждение не состоится, если трудности не были в основном одинаковыми: условие одно и то же и присутствует в одном и том же месте.

Какой из

 while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

и

 while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

сделать лучше предполагаемую структуру? Я голосую за первое, вам не нужно проверять соответствие условий. (Но см. Мой отступ).

ответил AProgrammer 30 MaramFri, 30 Mar 2012 11:19:07 +04002012-03-30T11:19:07+04:0011 2012, 11:19:07
3

while (true) и break - обычная идиома. Это канонический способ реализовать середину выходных циклов на языках C-типа. Добавление переменной для сохранения условия выхода, а if () вокруг второй половины цикла является разумной альтернативой. Некоторые магазины могут официально стандартизироваться на одном или другом, но ни один из них не превосходит; они эквивалентны.

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

ответил mjfgates 30 MarpmFri, 30 Mar 2012 20:39:57 +04002012-03-30T20:39:57+04:0008 2012, 20:39:57
2

Ваши альтернативы не так плохи, как вы думаете. Хороший код должен:

  1. Четко укажите с хорошими именами /комментариями переменных, при каких условиях цикл должен быть прерван. (Состояние разрыва не должно быть скрыто)

  2. Четко укажите, что такое порядок выполнения операций. Здесь помещается дополнительная 3-я операция (DoSomethingElseTo(input)) внутри if, если это хорошая идея.

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

ответил Pat 29 MarpmThu, 29 Mar 2012 23:55:06 +04002012-03-29T23:55:06+04:0011 2012, 23:55:06
1

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

В вашем примере вы можете использовать while(true), потому что ваш код понятен и понятен. Нет смысла создавать неэффективные и трудные для чтения коды, просто потому, что некоторые люди считают, что всегда плохая практика использовать while(true).

У меня возникла аналогичная проблема:

 $i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

Использование do ... while(true) без необходимости избегает isset($array][$key] вызывается дважды, код короче, чище и легче читать. Нет абсолютно никакого риска, чтобы в конце была введена бесконечная ошибка цикла с else break;.

 $i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

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

ответил Dan Bray 10 J0000006Europe/Moscow 2018, 03:13:09
0

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

(обратите внимание, что это включает в себя частичное разворачивание цикла и перемещение тела цикла вниз, так что начало цикла совпадает с условным тестом)

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

ответил Hurkyl 25 Maypm14 2014, 21:04:32
0

Вы также можете пойти с этим:

 public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

Или менее смутно:

 bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}
ответил Eclipse 30 MarpmFri, 30 Mar 2012 20:55:35 +04002012-03-30T20:55:35+04:0008 2012, 20:55:35
0

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

 L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

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

 void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

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

ответил intrepidis 29 AMpSun, 29 Apr 2018 11:49:46 +030049Sunday 2018, 11:49:46

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132