Циклы «while (true)» настолько плохи? [закрыто]

218

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

do{
     //get some input.
     //if the input meets my conditions, break;
     //Otherwise ask again.
} while(true)

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

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

Что не так с do-while(true)?

JHarnach
источник
23
Спросите своего учителя, такие вещи довольно субъективны.
Крис Эберле
18
Я нашел эту статью полезной, чтобы понять, что вредно в goto . Сравнение с break, вероятно, имеет смысл, но на самом деле неправильно понято. Возможно, вы можете обучить своего профессора этому;) По моему опыту, профессора мало знают о ремесле программирования.
Магнус Хофф
100
На мой взгляд, единственная действительно неоспоримая плохая вещь - это то, что do {} while (true)эквивалентно тому, while(true) {}что последнее гораздо более общепринятая форма и намного яснее.
Во
11
Если кто-то не ценит простую выразительную силу break, он должен попробовать программирование на языке без него. Это не займет слишком много циклов, прежде чем вы захотите этого!
Стивен
16
Я не согласен с тегом домашней работы.
aaaa bbbb

Ответы:

220

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

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

В качестве примера того, где его использовать проще, breakчем флаг, рассмотрим:

while (true)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        break;
    }
    actOnInput(input);
}

Теперь давайте заставим его использовать флаг:

boolean running = true;
while (running)
{
    doStuffNeededAtStartOfLoop();
    int input = getSomeInput();
    if (testCondition(input))
    {
        running = false;
    }
    else
    {
        actOnInput(input);
    }
}

Я считаю последнее более сложным для чтения: у него есть дополнительный elseблок, тем actOnInputболее отступ, и если вы пытаетесь понять, что происходит при testConditionвозврате true, вам нужно внимательно просмотреть оставшуюся часть блока, чтобы убедиться, что это не то , что после того, как в elseблоке , который будет происходить ли runningустановлено значениеfalse или нет.

breakЗаявление сообщается намерение более четко, и позволяет остальной блок ужиться с тем, что он должен делать , не заботясь о прежних условиях.

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

Джон Скит
источник
3
Я согласен, что это не первый инструмент, к которому я обращаюсь, но, похоже, он так чисто позаботился о проблеме, и мне нравится чистый код.
JHarnach
3
@ X-Zero: да, иногда. Если вы можете превратить «перерыв» в «возвращение», это часто хорошо, хотя вы все равно можете получитьwhile (true)
Джон Скит
21
@trutheality: я предпочитаю, чтобы большая часть моего кода была как можно более незаметной. И состояние со сложным заданием мне кажется более сложным.
Джон Скит
3
@ Vince: Да, и это здорово, если вы можете легко выразить условие в начале цикла . Но иногда вы не можете - и это ситуация, о которой мы говорим. Обратите внимание на первые пару предложений моего ответа.
Джон Скит
6
@ Ismail: надеюсь, нет. Посты должны оцениваться исключительно по их содержанию, а не по автору. Черт, я бы даже понизил сообщение Эрика Липперта, если бы чувствовал, что оно неточное / бесполезное. Хотя этого еще не произошло :)
Джон Скит
100

AFAIK ничего, правда. У учителей просто аллергия goto, потому что они слышали где-то, что это действительно плохо. В противном случае вы просто напишите:

bool guard = true;
do
{
   getInput();
   if (something)
     guard = false;
} while (guard)

Что почти одно и то же.

Может быть, это чище (потому что вся информация о цикле содержится в верхней части блока):

for (bool endLoop = false; !endLoop;)
{

}
Эль Марсель
источник
4
Мне нравятся ваши предложения, так как условие завершения гораздо более заметно. Это означает, что при чтении кода вы быстрее поймете, что он пытается сделать. Я бы никогда не использовал бесконечный цикл, но часто использую ваши две версии.
Сани
4
Консенсус в том, что флаг лучше, чем ломать, во многом потому, что он выражает намерение? Я вижу, что это полезно. Все твердые ответы, но я отмечу это как принятое.
JHarnach
20
Оба они по-прежнему страдают от загрязнения остальной части цикла (по крайней мере, в некоторых ситуациях) продолжением перехода к концу тела цикла, даже если вы знаете, что разрываете . Я приведу пример в своем ответе ...
Джон Скит
8
Мне больше нравится ответ Джона Скита. Кроме того, while (true) {} НАМНОГО лучше, чем do {} while (true)
aaaa bbbb
1
Я ненавижу, что Дейкстра когда-либо писал эту статью GoTo. Хотя GoTo, безусловно, часто подвергался насилию в прошлом, это не значит, что вы никогда не должны его использовать. Кроме того, Exit For, Break, Exit While, Try / Catch - это всего лишь специализированные формы Goto. Gotos часто может сделать код более читабельным. И да, я знаю, что все может быть сделано без Гото. Это не значит, что должно.
Кибби
39

У Дугласа Крокфорда было замечание о том, что ему бы хотелось, чтобы JavaScript содержал loopструктуру:

loop
{
  ...code...
}

И я не думаю, что Java будет еще хуже иметь loopструктуру.

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

Но они редко упоминают, что все циклические механизмы могут быть воспроизведены с помощью while(true)циклов.

while( a() )
{
  fn();
}

такой же как

loop
{
  if ( !a() ) break;
  fn();
}

и

do
{
  fn();
} while( a() );

такой же как:

loop
{
  fn();
  if ( !a() ) break;
}

и

for ( a(); b(); c() )
{
  fn();
}

такой же как:

a();
loop
{
  if ( !b() ) break;
  fn();
  c();
}

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

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

zzzzBov
источник
2
+1: есть дополнительные сложности с forциклами, когда continueоператоры задействованы, но они не являются основным расширением.
Донал Феллоуз
Я обычно использую цикл for, когда есть последовательность, которую я хочу выполнить, и цикл while, когда есть условие, которое я хочу выполнить. Это делает код более разборчивым IMO.
dascandy
4
Никто больше не пишет for (;;) {? (Произносится "навсегда"). Раньше это было очень популярно.
Дауд ибн Карим
16

Еще в 1967 году Эдгар Дейкстра написал статью в отраслевом журнале о том, почему goto следует исключать из языков высокого уровня для улучшения качества кода. Из этого вышла целая парадигма программирования, называемая «структурированное программирование», хотя, конечно, не все согласны с тем, что goto автоматически означает плохой код.

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

Очевидно, что это не единственная парадигма программирования, но часто она может быть легко применена к другим парадигмам, таким как объектно-ориентированное программирование (аля Java).

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

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

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

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

Джессика Браун
источник
2
Вот статья Эдсгера Дейкстры . Это хорошее чтение.
Рой Принс
14

Обычное Java-соглашение для чтения ввода:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String strLine;

while ((strLine = br.readLine()) != null) {
  // do something with the line
}

И обычное соглашение C ++ для чтения ввода:

#include <iostream>
#include <string>
std::string data;
while(std::readline(std::cin, data)) {
  // do something with the line
}

И в C это

#include <stdio.h>
char* buffer = NULL;
size_t buffer_size;
size_t size_read;
while( (size_read = getline(&buffer, &buffer_size, stdin)) != -1 ){
  // do something with the line
}
free(buffer);

или если вы уверены, что знаете, какова самая длинная строка текста в вашем файле, вы можете сделать

#include <stdio.h>
char buffer[BUF_SIZE];
while (fgets(buffer, BUF_SIZE, stdin)) {
  //do something with the line
}

Если вы проверяете, ввел ли ваш пользователь quitкоманду, легко расширить любую из этих трех структур цикла. Я сделаю это на Java для вас:

import java.io.*;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;

while ((line = br.readLine()) != null  && !line.equals("quit") ) {
  // do something with the line
}

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

Кен Блум
источник
Фактически, если вы используете while (true)цикл вместо одного из этих обычных входных циклов, вы можете забыть проверить конец файла.
Кен Блум
3
Вы выполняете это эффективно, вставляя первую часть цикла в whileусловное выражение . В конечном Java-состоянии оно уже становится довольно здоровенным, и, если вам нужно было проделать огромные манипуляции, чтобы решить, продолжать или нет, это может занять довольно много времени. Вы можете разделить его на отдельные функции, и это может быть лучше. Но если вы действительно хотите, чтобы это выполнялось в одной функции, и перед принятием решения о том, следует ли продолжить, нужно выполнить нетривиальную работу while(true).
пул
@poolie: Тогда, вероятно, лучше всего использовать команду read в качестве условия цикла (которое проверяет EOF) и проверять другие ваши условия внутри цикла как операторы break.
Кен Блум
1
Для чего это стоит, gets()это не обычное соглашение. Это очень способствует переполнению буфера. fgets(buffer, BUFFER_SIZE, file)это больше похоже на стандартную практику.
Дейв
@Dave: я сейчас отредактировал ответ для использования fgets.
Кен Блум
12

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

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

Тем не менее, вы все равно увидите такие вещи в большом количестве кода в реальном мире.

rfeak
источник
4
Если цикл начинается с while (true)довольно очевидно , что он будет внутри breakили returnвнутри, или что он будет работать вечно. Быть прямым очень важно, но while(true)не особенно плохо само по себе. Переменные, которые имеют сложные инварианты в итерациях цикла, могут быть примером чего-то, что вызывает гораздо большую тревогу.
пул
4
Попробуйте пройти три уровня в глубине поиска, а затем как-нибудь вырваться. Код будет намного хуже, если вы просто не вернетесь с этого момента.
dascandy
11

Это твой пистолет, твоя пуля и твоя нога ...

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

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

Зачем? Я должен был выяснить, почему приложение 700K LOC постепенно начинает сжигать 100% процессорного времени, пока каждый процессор не будет насыщен. Это был удивительный (истинный) цикл. Это было большим и противным, но это сводилось к:

x = read_value_from_database()
while (true) 
 if (x == 1)
  ...
  break;
 else if (x ==2)
  ...
  break;
and lots more else if conditions
}

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

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

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

(Но я помню, что профессора понизили рейтинг, если бы мы не комментировали каждую строку, даже для i ++;)

JQA
источник
3
+1 за комментарий о защитном программировании.
JHarnach
3
В вашем примере код глуп не из-за выбора while (true), а из-за идеи создания цикла вокруг кода.
Florian F
Действительно, в чем был смысл этого цикла? :)
Джуззлин
6

Это плохо в том смысле, что конструкции структурированного программирования предпочтительнее (несколько неструктурированные) операторы break и continue. Для сравнения, они предпочитают «идти» по этому принципу.

Я всегда рекомендовал бы сделать ваш код максимально структурированным ... хотя, как отмечает Джон Скит, не делайте его более структурированным!

Patrick87
источник
5

Согласно моему опыту, в большинстве случаев петли имеют «основное» условие для продолжения. Это условие, которое должно быть записано в сам оператор while (). Все остальные условия, которые могут нарушить цикл, являются вторичными, не столь важными и т. Д. Они могут быть записаны как дополнительные if() {break}операторы.

while(true) часто сбивает с толку и менее читабелен.

Я думаю, что эти правила не охватывают 100% случаев, но, вероятно, только 98% из них.

AlexR
источник
Хорошо сказано. Это похоже на использование цикла for с: while (true) {i ++; ...}. Вы хороните условие цикла внутри цикла, а не в «подписи».
LegendLength
3

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

Что касается вашего вопроса: нет никаких проблем с

while(true) {
   do_stuff();
   if(exit_time) {
      break;
   }
}

... если вы знаете, что делаете, и убедитесь, что exit_timeв какой-то моментtrue .

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

Shadur
источник
Я думаю, что установка exit_time = false; while(!exit_time) { execute_stuff(); }и do { execute_stuff(); } while(! exit_time );оба гораздо понятнее, чем наличие if( condition ) { break; }в конце цикла с while(true). Разрывы - это короткие замыкания в циклах - прекрасно, если они используются в качестве короткого замыкания в середине цикла, но вы должны просто оценить условие в операторе while и иметь разрыв в конце цикла.
Доктор Джимбоб
Что касается этого комикса: пока do-while не может делать все то, что может делать, do-while иногда лучше делать то, что do-while может делать, даже если это можно сделать.
Theraot
3

Вы можете просто использовать логический флаг, чтобы указать, когда заканчивать цикл while. Breakи go toбыли причины для того, чтобы программное обеспечение было трудно поддерживать - кризис программного обеспечения (tm) - и его следует избегать, и легко может быть.

Вопрос в том, прагматичны вы или нет. Прагматичные кодеры могут просто использовать break в этой простой ситуации.

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

Даниэль Лешковский
источник
8
Потому что , по моему опыту, сохранение логического флага, вероятно, не яснее, а может быть и менее ясным?
Джон Скит
3
@Jon Skeet Что ж, вопрос в том, чтобы выбрать хороший навык или научить себя плохому, используя брейк. А бул, называемый "бегом", тебе не понятен? Это очевидно, легко отлаживать, и, как я уже говорил, хороший навык - это хорошая вещь. В чем проблема?
Даниэль Лешковски,
4
Bool, называемый running, который требует от меня наличия if (running)внутри цикла, с отступом всего остального кода, когда все, что я хочу - это выйти из цикла, для меня определенно менее понятно, чем простое выражение break, в котором точно указано , что я хочу сделать. Вы, кажется, рассматриваете использование разрыва как вредную привычку аксиоматически - я не думаю об этом как таковой.
Джон Скит
2
Почему у вас есть (работает) внутри цикла? Как и в случае с break в конце цикла, вы проверяете, хотите ли вы выйти из режима ожидания, и переворачиваете флаг вместо использования break и используете флаг в то время как (выполняется) вместо while (true). Я не понимаю твою точку зрения, серьезно. Я бы согласился, что прагматичный кодер может использовать break в определенных ситуациях, но я действительно не получаю комментарии, которые вы сделали по моему предложению
Даниэль Лешковски,
3
Вы предполагаете, что вам не нужно ничего делать в цикле после того, как вы определили, выходить или нет. Например, в ситуации ОП ему нужно попросить больше информации, если он не уходит.
Джон Скит
3

Может быть, мне не повезло. Или, может быть, мне просто не хватает опыта. Но каждый раз , когда я вспоминаю дело с while(true)имеяbreak внутри, можно было улучшить код, применяя метод извлечения к блоку while , который сохранял, while(true)но (по совпадению?) Преобразовывал все breaks в returns.

По моему опыту while(true)без перерывов (то есть с возвратами или бросками) довольно удобно и легко понять.


  void handleInput() {
      while (true) {
          final Input input = getSomeInput();
          if (input == null) {
              throw new BadInputException("can't handle null input");
          }
          if (input.isPoisonPill()) {
              return;
          }
          doSomething(input);
      }
  }
комар
источник
3

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

Грифф Соб Беккер
источник
2

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

Для вашего примера гораздо понятнее сделать что-то вроде:

do {
   input = get_input();
   valid = check_input_validity(input);    
} while(! valid)

Это особенно верно, если цикл do while становится длинным - вы точно знаете, где находится проверка, чтобы увидеть, происходит ли дополнительная итерация. Все переменные / функции имеют соответствующие имена на уровне абстракции. Это while(true)утверждение говорит вам, что обработка не в том месте, о котором вы думали.

Может быть, вы хотите другой вывод во второй раз через цикл. Что-то вроде

input = get_input();
while(input_is_not_valid(input)) {
    disp_msg_invalid_input();
    input = get_input();
}

кажется мне более читабельным

do {
    input = get_input();
    if (input_is_valid(input)) {
        break;
    }
    disp_msg_invalid_input();
} while(true);

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

доктор джимбоб
источник
1

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

DWORD dwError = ERROR_SUCCESS;

do
{
    if ( (dwError = SomeFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }

    if ( (dwError = SomeOtherFunction()) != ERROR_SUCCESS )
    {
         /* handle error */
         continue;
    }
}
while ( 0 );

if ( dwError != ERROR_SUCCESS )
{
    /* resource cleanup */
}
Джим Фелл
источник
1

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

Пити Б
источник
1

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

Павел
источник
1

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

Оператор while с истинным условием ничего не говорит о цикле. Это усложняет задачу понимания этого.

Что будет легче понять из этих двух фрагментов?

do {
  // Imagine a nice chunk of code here
} while(true);

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);
Винс Пануччо
источник
0

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

Бимал Шарма
источник
0

1) Нет ничего плохого в do -while(true)

2) Ваш учитель не прав.

NSFs !!:

3) Большинство учителей являются учителями, а не программистами.

Pacerier
источник
@ Коннелл, подожди, пока твой учитель не услышит это!
Pacerier
1
Я самоучка на фронте программирования. Все, о чем я могу судить, - это мой учитель информатики в средней школе, который научил нас создавать веб-сайты с помощью Dreamweaver таким образом, чтобы ВСЁ было абсолютно позиционированным див ...
Коннелл
@ Подпишись на мой пост, чтобы показать твое согласие
Pacerier
2
«Те, кто не умеют, учат». - Это довольно большое обобщение, не правда ли? Должны ли врачи, инженеры, пилоты и т.д. быть самоучками, поскольку их инструкторы, по вашему мнению, некомпетентны?
filip-fku
@ filip-fku вау вау ~ круто это круто!
Pacerier
0

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

Дмитрий
источник