В то время как (правда) с нарушением практики программирования? - PullRequest
49 голосов
/ 24 декабря 2008

Я часто использую этот шаблон кода:

while(true) {

    //do something

    if(<some condition>) {
        break;
    }

}   

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

while(!<some condition>) {

    //do something

}   

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

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

Может ли кто-нибудь обогатить этот аргумент, добавив доказательства для одной или другой стороны?

Ответы [ 23 ]

43 голосов
/ 24 декабря 2008

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

Я думаю, то, что вы ищете, это цикл "сделай пока". Я на 100% согласен с тем, что while (true) не очень хорошая идея, потому что это затрудняет поддержание этого кода, а способ выхода из цикла очень goto esque, что считается плохой практикой.

Попробуйте:

do {
  //do something
} while (!something);

Проверьте точную синтаксическую документацию на вашем языке. Но посмотрите на этот код, он в основном делает то, что находится в do, затем проверяет часть while, чтобы увидеть, следует ли это делать снова.

29 голосов
/ 24 декабря 2008

Цитирую, что известный разработчик минувших дней, Вордсворт:

...
По правде говоря, тюрьма, в которую мы обречены
Мы сами, тюрьмы нет; и, следовательно, для меня,
В разных настроениях времяпрепровождение будет
На скудном участке земли Сонета;
Приятно, если некоторые души (для таких их потребностей должны быть)
Кто почувствовал вес слишком большой свободы,
Должен найти краткое утешение там, как я нашел.

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

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

while (true) {
    action0;
    if (test0) break;
    action1;
}

состоит в том, что можно легко action0 и action1 становиться все большими и большими кусками кода или добавлять «еще одну» последовательность тест-разрыв-действие, пока не станет трудно указывать на конкретную строку и ответьте на вопрос: «Какие условия, как я знаю, выполняются в этот момент?» Поэтому, не устанавливая правил для других программистов, я стараюсь по возможности избегать идиомы while (true) {...} в своем собственном коде.

19 голосов
/ 24 декабря 2008

Когда вы можете написать свой код в виде

while (condition) { ... }

или

while (!condition) { ... }

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

Но многие циклы не подходят для этой модели, и бесконечный цикл с явным выходом (ами) в середине - достойная модель . (Циклы с continue обычно сложнее понять, чем циклы с break.) Если вы хотите, чтобы какое-либо свидетельство или авторитет цитировались, посмотрите не дальше, чем знаменитая статья Дона Кнута о Структурированное программирование с утверждениями Гото ; вы найдете все примеры, аргументы и объяснения, которые вы можете захотеть.

Незначительный смысл идиомы: написание while (true) { ... } делает вас старым программистом на Паскале или, возможно, в наши дни программистом на Java. Если вы пишете на C или C ++, предпочтительная идиома -

for (;;) { ... }

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

11 голосов
/ 24 декабря 2008

Я предпочитаю

while(!<some condition>) {
    //do something
}

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

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

В случае множества break баллов я бы заменил их на

while( !<some condition> ||
       !<some other condition> ||
       !<something completely different> ) {
    //do something
}

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

6 голосов
/ 24 декабря 2008

while (true) может иметь смысл, если у вас много утверждений, и вы хотите остановиться, если произойдет сбой

 while (true) {
     if (!function1() ) return;   
     if (!function2() ) return;   
     if (!function3() ) return;   
     if (!function4() ) return;  
   }

лучше

 while (!fail) {
     if (!fail) {
       fail = function1()
     }           
     if (!fail) {
       fail = function2()
     }  
     ........

   }
6 голосов
/ 25 декабря 2008

Хавьер сделал интересный комментарий на мой предыдущий ответ (тот, что цитирует Wordsworth):

Я думаю, что while (true) {} является более «чистой» конструкцией, чем while (условие) {}.

и я не смог ответить адекватно в 300 символов (извините!)

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

Итак, с целью уменьшения сложности позвольте мне ответить Хавьеру с точки зрения полноты и силы, а не чистоты.

Я думаю об этом фрагменте кода:

while (c1) {
    // p1
    a1;
    // p2
    ...
    // pz
    az;
}

как выражение двух вещей одновременно:

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

Разница в перспективе; первое из них связано с внешним динамическим поведением всего цикла в целом, а второе полезно для понимания внутренней статической гарантии, на которую я могу рассчитывать, в частности, думая о a1. Конечно, чистый эффект a1 может сделать недействительным c1, что потребует от меня больше думать о том, на что я могу рассчитывать в пункте 2 и т. Д.

Давайте приведем конкретный (крошечный) пример, чтобы подумать об условии и первом действии:

while (index < length(someString)) {
    // p1
    char c = someString.charAt(index++);
    // p2
    ...
}

«Внешняя» проблема заключается в том, что цикл явно делает что-то внутри someString, что может быть сделано только до тех пор, пока index находится в someString. Это создает ожидание того, что мы будем изменять либо index, либо someString внутри тела (в месте и способе, неизвестном до тех пор, пока я не исследую тело), ​​чтобы в конечном итоге произошло прекращение. Это дает мне и контекст, и ожидание для размышления о теле.

«Внутренняя» проблема заключается в том, что мы гарантируем, что действие, следующее за пунктом 1, будет законным, поэтому, читая код в пункте 2, я могу думать о том, что делается со значением char, которое я знаю было юридически получено. (Мы даже не можем оценить условие, если someString является нулевым ссылочным номером, но я также предполагаю, что мы защитились от этого в контексте этого примера!)

Напротив, петля вида:

while (true) {
    // p1
    a1;
    // p2
    ...
}

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

На внутреннем уровне у меня абсолютно нет явных гарантий относительно любых обстоятельств, которые могут иметь место в пункте 1. Условие true, которое, конечно, везде верно, является самым слабым из возможных утверждений о том, что мы можем знать в любой точке программы. Понимание предпосылок действия - очень ценная информация, когда вы пытаетесь думать о том, что действие выполняет!

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

3 голосов
/ 24 декабря 2008

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

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

2 голосов
/ 24 декабря 2008

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

Так что, хотя (true) ... не следует классифицировать как хорошие или плохие, пусть ситуация решает, что использовать

1 голос
/ 24 декабря 2008

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

  • Это проще, поскольку вам не нужен еще один тест в коде.
  • Его легче читать, поскольку вам не нужно охотиться за перерывом внутри цикла.
  • Вы заново изобретаете колесо. Все дело в том, чтобы сделать что-то, пока проверка верна. Зачем подрывать это, помещая условие разрыва в другое место?

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

1 голос
/ 24 декабря 2008

Идеальный ответ консультанта: это зависит. В большинстве случаев правильнее всего использовать цикл while

while (condition is true ) {
    // do something
}

или «повторить до», что делается на C-подобном языке с

do {
    // do something
} while ( condition is true);

Если любой из этих случаев работает, используйте их.

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

ТОГДА И ТОЛЬКО ТОГДА используйте некоторое время (1):

while(1) {
   accept connection
   fork child process
}

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

while(1) { // or for(;;)
   // do some stuff
   if (condition met) break;
   // otherwise do more stuff.
}
...