Подтвердить что ты не робот

Есть (правда) с нарушением плохой практики программирования?

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

while(true) {

    //do something

    if(<some condition>) {
        break;
    }

}   

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

while(!<some condition>) {

    //do something

}   

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

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

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

4b9b3361

Ответ 1

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

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

Try:

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

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

Ответ 2

Чтобы процитировать, что отметил разработчик прошедших дней, Wordsworth:

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

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

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

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

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

Ответ 3

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

while (condition) { ... }

или

while (!condition) { ... }

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

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

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

for (;;) { ... }

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

Ответ 4

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

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

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

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

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

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

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

Ответ 5

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

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

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

Ответ 7

Хавьер сделал интересный комментарий к моему более раннему ответу (тот, который цитирует Вордсворта):

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

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

В моем учении и наставничестве я неформально определил "сложность" как "Какую часть остального кода мне нужно иметь в голове, чтобы понять эту единственную строку или выражение?" Чем больше я должен иметь в виду, тем сложнее код. Чем больше код говорит мне явно, тем сложнее.

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

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

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

одновременно выражая две вещи:

  • тело (целое) будет повторяться до тех пор, пока c1 останется верным, а
  • в точке 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 является null ref, но я также предполагаю, что мы защитили его в контексте этого примера!)

Напротив, петля формы:

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

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

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

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

Ответ 8

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

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

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

Ответ 9

Если существует одно (и только одно) условие исключительного нарушения, то включение этого условия непосредственно в конструкцию потока управления (время) является предпочтительным. Увидев, пока (правда) {...} заставляет меня, как читателя кода, думать, что нет простого способа перечислить условия разрыва и заставляет меня думать: "Посмотрите внимательно на это и тщательно подумайте о условиях разрыва (что задано раньше их в текущем цикле и то, что могло быть установлено в предыдущем цикле)"

Короче говоря, я с вашим коллегой в простейшем случае, но хотя (правда) {...} не редкость.

Ответ 10

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

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

или "repeat until", который выполняется на языке 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.
}

Ответ 11

Там по существу идентичный вопрос уже в SO на Есть ли WHILE TRUE... BREAK... END WHILE хороший дизайн?. @Glomek ответил (в недооцененной должности):

Иногда это очень хороший дизайн. См. Структурированное программирование с помощью Goto Statement Дональда Кнута для некоторых примеров. Я часто использую эту основную идею для циклов, которые работают "n и полтора раза", особенно для циклов чтения/обработки. Однако, как правило, я стараюсь иметь только один оператор break. Это облегчает рассуждение о состоянии программы после завершения цикла.

Несколько позже я ответил связанным, а также очень недооцененным комментарием (отчасти потому, что я не замечал Гломека в первый раз, я думаю):

Одна увлекательная статья - "Квалифицированное программирование с Кнут" с 1974 года (доступно в его книге "Грамотное программирование" и, возможно, в другом месте). Он обсуждает, среди прочего, контролируемые способы выхода из циклов и (не используя термин) оператор цикла с половиной.

Ada также предоставляет конструкции цикла, включая

loopname:
    loop
        ...
        exit loopname when ...condition...;
        ...
    end loop loopname;

Оригинальный код вопроса аналогичен этому в намерении.

Единственное различие между ссылочным пунктом SO и этим - "окончательный разрыв"; это цикл с одним выстрелом, который использует break для выхода из цикла раньше. Были вопросы о том, является ли это хорошим стилем - у меня нет перекрестной ссылки.

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

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

Ответ 15

Проблема заключается в том, что не каждый алгоритм придерживается модели while (cond) {action}.

Общая модель цикла выглядит следующим образом:

loop_prepare
 loop:
  action_A
  if(cond) exit_loop
  action_B
  goto loop
after_loop_code

Если нет action_A, вы можете заменить его на:

loop_prepare
  while(cond)
  action_B
after_loop_code

Когда нет action_B, вы можете заменить его на:

loop_prepare
  do action_A
  while(cond)
after_loop_code

В общем случае action_A будет выполняться n раз, а action_B будет выполняться (n-1) раз.

Пример реальной жизни: напечатать все элементы таблицы, разделенные запятыми. Нам нужны все n элементов с (n-1) запятыми.

Вы всегда можете сделать некоторые трюки, чтобы придерживаться модели while-loop, но это всегда будет повторять код или проверять дважды то же условие (для каждого цикла) или добавлять новую переменную. Таким образом, вы всегда будете менее эффективны и менее читабельны, чем модель цикла while-true-break.

Пример (плохой) "трюк": добавьте переменную и условие

loop_prepare
b=true // one more local variable : more complex code
 while(b): // one more condition on every loop : less efficient
  action_A
  if(cond) b=false // the real condition is here
  else action_B
after_loop_code

Пример (плохой) "трюк": повторите код. Повторный код нельзя забывать при изменении одного из двух разделов.

loop_prepare
action_A
 while(cond):
  action_B
  action_A
after_loop_code

Примечание: в последнем примере программист может запутать (охотно или нет) код, смешав "loop_prepare" с первым "action_A" и action_B со вторым действием_A. Поэтому у него может быть ощущение, что он этого не делает.

Ответ 16

Он, вероятно, прав.

Функционально два могут быть одинаковыми.

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

Ответ 17

Несколько преимуществ использования последней конструкции, которая приходит мне на ум:

  • легче понять, что делает цикл, не ища разрывов в коде цикла.

  • если вы не используете другие фрагменты кода цикла, в вашем цикле есть только одна точка выхода и условие while().

  • обычно заканчивается тем, что он меньше кода, что добавляет читаемости.

Ответ 18

Я предпочитаю подход while (!), потому что он более четко и сразу передает намерение цикла.

Ответ 19

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

His reasoning was that you could "forget the break" too easily and have an endless loop.

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

Ответ 20

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

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

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

Ответ 21

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

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

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

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

while(true){
    // do something
if(<some condition>) break;
    // continue do something
}

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

Ответ 22

используя такие циклы, как

while (1) {do stuff}

необходимо в некоторых ситуациях. Если вы программируете встроенные системы (подумайте о микроконтроллерах, таких как ПОС, MSP430 и программирование DSP), то почти весь ваш код будет в течение некоторого времени (1). При кодировании для DSP иногда вам просто нужно некоторое время (1) {}, а остальная часть кода - это процедура обслуживания прерываний (ISR).

Ответ 23

Мне нравится for петли лучше:

for (;;)
{
    ...
}

или даже

for (init();;)
{
    ...
}

Но если функция init() выглядит как тело цикла, вам лучше с

do
{
    ...
} while(condition);