Являются ли while (true) "петлями так плохо?

Я программировал на 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)?

Ответ 1

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

В ситуациях, когда это первое, что я пишу, я почти всегда стараюсь реорганизовать его на что-то более ясное. Иногда это не может помочь (или альтернатива должна иметь переменную 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.

Ответ 2

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

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

Это почти то же самое.

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

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

}

Ответ 3

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

loop
{
  ...code...
}

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

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

Но они редко упоминают, что механизмы all могут быть реплицированы с помощью циклов 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.

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

Ответ 4

Еще в 1967 году Дейкстра написал статью в журнале торговли о том, почему goto следует исключить из языков высокого уровня для улучшения качества кода. Из этого вышла целая парадигма программирования под названием "структурированное программирование", хотя, конечно, не все согласны с тем, что goto автоматически означает "плохой код". Суть структурированного программирования состоит в том, что структура кода должна определять его поток, а не иметь gotos или breaks или продолжать определять поток, где это возможно. Аналогично, в этой парадигме также не поощряется наличие нескольких точек входа и выхода в цикл или функцию. Очевидно, что это не единственная парадигма программирования, но часто ее можно легко применить к другим парадигмам, таким как объектно-ориентированное программирование (ala Java). Ваши преподаватели, вероятно, были преподаны и пытаются научить ваш класс, что нам лучше избегать "кода спагетти", убедившись, что наш код структурирован и соблюдает подразумеваемые правила структурированного программирования. Хотя нет ничего изначально "неправильного" с реализацией, использующей break, некоторые считают, что значительно легче читать код, в котором условие для цикла явно указано в условии while(), и устраняет некоторые возможности быть слишком сложным. Есть определенно проблемы с использованием условия while (true), которые часто появляются в коде начинающими программистами, такими как риск случайного создания бесконечного цикла или создания кода, который трудно читать или излишне запутывать.

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

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

Ответ 5

Условное обозначение Java для чтения:

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

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

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

#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) для этого - ваш язык программирования уже предоставил вам соответствующую идиому для использования команды ввода в качестве условия цикла.

Ответ 6

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

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

Сказав это, вы все равно увидите такие вещи в LOT-коде в реальном мире.

Ответ 7

Это ваше оружие, ваша пуля и ваша нога...

Это плохо, потому что вы просите о неприятностях. Это не вы или другие плакаты на этой странице, у которых есть примеры коротких/простых циклов 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). Он вернется, чтобы преследовать вас.

(Но я действительно помню, как профессора оценивали, если мы не прокомментировали каждую строку, даже для я ++;)

Ответ 8

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

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

Ответ 9

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

while(true) часто путается и менее читабельна.

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

Ответ 10

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

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

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

Ответ 11

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

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

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

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

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

Ответ 12

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

Ответ 13

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

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

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

Это особенно верно, если цикл while 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);

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

Ответ 14

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

Ответ 15

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

В моем опыте 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);
      }
  }

Ответ 16

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

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 */
}

Ответ 17

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

2) Ваш учитель ошибается.

NSFs!!:

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

Ответ 18

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

Ответ 19

Для меня проблема в читаемости.

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

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

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

do {
  // Imagine a nice chunk of code here
} while(price < priceAllowedForDiscount);

Ответ 20

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

Ответ 21

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