Os loops "while (true)" são tão ruins? [fechadas]

218

Estou programando em Java há vários anos, mas acabei de voltar à escola para obter um diploma formal. Fiquei bastante surpreso ao saber que, na minha última tarefa, perdi pontos por usar um loop como o abaixo.

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

Agora, para o meu teste, estou apenas procurando por alguma entrada do console, mas me disseram que esse tipo de loop é desencorajado porque o uso breaké semelhante goto, simplesmente não fazemos isso.

Entendo completamente as armadilhas de gotoseu primo Java break:labele tenho o bom senso de não usá-las. Sei também que um programa mais completo forneceria outros meios de fuga, por exemplo, para encerrar o programa, mas não foi por isso que meu professor citou, então ...

O que há de errado do-while(true)?

JHarnach
fonte
23
Pergunte ao seu professor, esse tipo de coisa é bastante subjetiva.
21811 Chris Eberle
18
Eu achei este artigo útil para entender o que é prejudicial no goto . A comparação com breakprovavelmente é bem-intencionada, mas realmente mal compreendida. Talvez você possa educar seu professor sobre isso;) Na minha experiência, os professores não sabem muito sobre o ofício da programação.
Magnus Hoff
100
A única coisa realmente incontestável sobre isso está em minha mente, o fato de que do {} while (true)é equivalente while(true) {}e o último é, de longe, a forma mais convencional e muito mais clara.
Voo 27/07
11
Se alguém não aprecia o poder expressivo simples de break, deve tentar programar em uma linguagem sem ela. Não são necessários muitos loops antes que você deseje!
27711 Steven
16
Eu discordo da etiqueta da lição de casa.
Aaaa bbbb 27/07/11

Respostas:

220

Eu não diria que é ruim - mas igualmente eu normalmente procuraria pelo menos uma alternativa.

Nas situações em que é a primeira coisa que escrevo, quase sempre tento refatorá-lo para algo mais claro. Às vezes, isso não pode ser ajudado (ou a alternativa é ter uma boolvariável que não faz nada significativo, exceto indicar o fim do loop, menos claramente que uma breakdeclaração), mas vale a pena tentar pelo menos.

Como um exemplo de onde é mais claro o uso breakde uma bandeira, considere:

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

Agora vamos forçá-lo a usar uma bandeira:

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

Eu vejo o último como mais complicado de ler: ele tem um elsebloco extra , o actOnInputé mais recuado e, se você está tentando descobrir o que acontece quando testConditionretorna true, precisa examinar cuidadosamente o resto do bloco para verificar se existe não é algo após o elsebloco que ocorrerá, se runningfoi definido falseou não.

A breakdeclaração comunica a intenção de forma mais clara e permite que o restante do bloco continue com o que precisa fazer sem se preocupar com as condições anteriores.

Observe que esse é exatamente o mesmo tipo de argumento que as pessoas têm sobre várias instruções de retorno em um método. Por exemplo, se eu conseguir calcular o resultado de um método dentro das primeiras linhas (por exemplo, porque alguma entrada é nula, vazia ou nula), acho mais claro retornar essa resposta diretamente do que ter uma variável para armazenar o resultado , em seguida, um bloco inteiro de outro código e, finalmente, uma returndeclaração.

Jon Skeet
fonte
3
Concordo que não é a primeira ferramenta que busco, mas parecia resolver o problema de maneira tão limpa e eu gosto de código limpo.
21711 JHarnach
3
@ X-Zero: Sim, às vezes. Se você pode transformar a "quebrar" em um "retorno" que muitas vezes Deus ... embora você ainda pode acabar com umwhile (true)
Jon Skeet
21
Verdade @ verdade: eu prefiro que a maior parte do meu código seja o mais desatento possível. E uma condição com uma tarefa composta me parece mais complicada.
21811 Jon Skeet
3
@Vince: Sim, e isso é ótimo se você pode expressar a condição facilmente no início do loop . Mas às vezes você não pode - e é sobre essa situação que estamos falando. Observe as duas primeiras frases da minha resposta.
Jon Jonke
6
@ Ismail: Espero que não. As postagens devem ser julgadas apenas pelo seu conteúdo, não pelo autor. Caramba, eu até rebaixaria uma postagem de Eric Lippert se considerasse imprecisa / inútil. Isso ainda não aconteceu :)
Jon Skeet
100

AFAIK nada, realmente. Os professores são alérgicos goto, porque ouviram em algum lugar que é muito ruim. Caso contrário, você apenas escreveria:

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

O que é quase a mesma coisa.

Talvez isso seja mais limpo (porque todas as informações de loop estão contidas na parte superior do bloco):

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

}
El Marcel
fonte
4
Gosto das suas sugestões, pois a condição de encerramento é muito mais visível. Isso significa que, ao ler o código, você entenderá o que está tentando fazer mais cedo. Eu nunca usaria um loop infinito, mas usaria suas duas versões com freqüência.
Trenó
4
O consenso é que uma bandeira é melhor do que quebrar em grande parte porque expressa intenção? Eu posso ver que isso é benéfico. Todas as respostas sólidas, mas vou marcar isso como aceito.
JHarnach
20
Ambos ainda sofrem com o restante do loop ser poluído (pelo menos em algumas situações) por ter que continuar indo até o final do corpo do loop, mesmo que você saiba que está quebrando . Vou colocar um exemplo na minha resposta ...
Jon Skeet
8
Eu gosto mais da resposta de Jon Skeet. Além disso, while (true) {} é muito melhor que do {} while (true)
aaaa bbbb
1
Eu odeio que Dijkstra já tenha escrito esse artigo no GoTo. Embora o GoTo certamente possa ter sido abusado no passado, isso não significa que você nunca deve usá-lo. Além disso, Exit For, Break, Exit While, Try / Catch são apenas formas especializadas de Goto. Gotos geralmente podem tornar o código mais legível. E sim, eu sei que tudo pode ser feito sem o Goto. Isso não significa que deveria.
Kibbee
39

Douglas Crockford fez uma observação sobre como ele desejava que o JavaScript contivesse uma loopestrutura:

loop
{
  ...code...
}

E eu não acho que Java seria pior por ter uma loopestrutura também.

Não há nada de intrinsecamente errado com while(true)loops, mas não é uma tendência para os professores a desencorajá-los. Do ponto de vista do ensino, é muito fácil fazer com que os alunos criem loops sem fim e não entendam por que o loop nunca é escapado.

Mas o que eles raramente mencionam é que todos os mecanismos de loop podem ser replicados com while(true)loops.

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

é o mesmo que

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

e

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

é o mesmo que:

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

e

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

é o mesmo que:

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

Contanto que você possa configurar seus loops de uma maneira que funcione, a construção que você escolher usar não é importante. Se acontecer de caber em um forloop, use um forloop.

Uma última parte: mantenha seus loops simples. Se houver muita funcionalidade em todas as iterações, coloque-a em uma função. Você sempre pode otimizá-lo depois de fazê-lo funcionar.

zzzzBov
fonte
2
+1: existem complexidades adicionais com forloops quando continuehá declarações envolvidas, mas elas não são uma grande extensão.
Donal Fellows
Eu normalmente uso um loop for quando há uma sequência que eu quero percorrer, e um loop while quando há uma condição que eu quero atender. Isso mantém o código mais legível.
Dascandy
4
Ninguém mais escreve for (;;) {? (Pronunciado "para sempre"). Isso costumava ser muito popular.
Dawood ibn Kareem
16

Em 1967, Edgar Dijkstra escreveu um artigo em uma revista especializada sobre por que o goto deve ser eliminado das linguagens de alto nível para melhorar a qualidade do código. Um paradigma inteiro de programação chamado "programação estruturada" surgiu disso, embora certamente nem todos concordam que ir automaticamente significa código incorreto.

O ponto crucial da programação estruturada é essencialmente que a estrutura do código deve determinar seu fluxo em vez de ter gotos ou quebras ou continuar a determinar o fluxo, sempre que possível. Da mesma forma, ter vários pontos de entrada e saída em um loop ou função também é desencorajado nesse paradigma.

Obviamente, esse não é o único paradigma de programação, mas geralmente pode ser facilmente aplicado a outros paradigmas, como a programação orientada a objetos (ala Java).

Seus professores provavelmente foram ensinados e estão tentando ensinar à sua turma que é melhor evitar o "código de espaguete", garantindo que nosso código seja estruturado e seguindo as regras implícitas da programação estruturada.

Embora não haja nada inerentemente "errado" em uma implementação que use break, alguns consideram significativamente mais fácil ler código onde a condição para o loop é explicitamente especificada na condição while () e elimina algumas possibilidades de ser excessivamente complicada. Definitivamente, existem armadilhas no uso de uma condição while (true) que parece surgir com frequência no código por programadores iniciantes, como o risco de criar acidentalmente um loop infinito ou tornar o código difícil de ler ou desnecessariamente confuso.

Ironicamente, o tratamento de exceções é uma área em que certamente ocorrerá um desvio da programação estruturada e será esperado à medida que você avança na programação em Java.

Também é possível que seu instrutor tenha esperado que você demonstrasse sua capacidade de usar uma estrutura ou sintaxe específica de loop ensinada nesse capítulo ou lição do seu texto e, embora o código que você escreveu seja funcionalmente equivalente, você pode não estar demonstrando o habilidade específica que você deveria aprender nessa lição.

Jessica Brown
fonte
2
Aqui está o artigo de Edsger Dijkstra . É uma boa leitura.
Roy Prins
14

A convenção Java usual para leitura de entrada é:

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

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

E a convenção C ++ usual para leitura de entrada é:

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

E em 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);

ou se estiver convencido de que sabe quanto tempo dura a linha de texto mais longa no seu arquivo, você pode

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

Se você estiver testando para ver se o usuário digitou um quitcomando, é fácil estender qualquer uma dessas três estruturas de loop. Vou fazer isso em Java para você:

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
}

Portanto, embora certamente haja casos em que breakou gotojustificado, se você estiver lendo um arquivo ou o console linha por linha, não será necessário um while (true)loop para realizá-lo - sua linguagem de programação já lhe forneceu com um idioma apropriado para usar o comando de entrada como condição de loop.

Ken Bloom
fonte
De fato, se você estiver usando um while (true)loop em vez de um desses loops de entrada convencionais, pode estar esquecendo de verificar o final do arquivo.
28511 Ken Bloom
3
Você está realizando isso efetivamente, colocando a primeira parte do loop na whilecondicional. Na condição final do Java, ele já está ficando bastante pesado e, se você tiver que fazer uma manipulação extensiva para decidir se continua ou não, pode demorar um pouco. Você pode dividi-lo em uma função separada, e isso pode ser melhor. Mas se você o desejar em uma função e houver um trabalho não trivial antes de decidir continuar, while(true)pode ser melhor.
poolie
@poolie: Então provavelmente é melhor usar o comando read como a condição do loop (que verifica EOF) e verificar suas outras condições dentro do loop como instruções de interrupção.
Ken Bloom
1
Por que vale a pena, gets()não é uma convenção comum. É altamente propício ao buffer overflow. fgets(buffer, BUFFER_SIZE, file)é muito mais parecido com a prática padrão.
Dave
@ Dave: Agora eu editei a resposta para usar fgets.
Ken Bloom
12

Não é uma coisa tão terrível, mas você precisa levar em consideração outros desenvolvedores ao codificar. Mesmo na escola.

Seus colegas desenvolvedores devem poder ver a cláusula de saída do seu loop, na declaração do loop. Você não fez isso. Você ocultou a cláusula de saída no meio do loop, dando mais trabalho a alguém que aparece e tenta entender seu código. Esta é a mesma razão pela qual coisas como "pausa" são evitadas.

Dito isto, você ainda verá coisas assim em MUITO código no mundo real.

rfeak
fonte
4
Se o loop começar while (true), é bastante óbvio que haverá um breakou returndentro dele, ou que ele funcionará para sempre. Ser direto é importante, mas while(true)não é especialmente ruim, por si só. Variáveis ​​que possuem invariantes complexos em iterações de loop seriam um exemplo de algo que causa muito mais angústia.
poolie
4
Tente avançar três níveis em uma pesquisa e, de alguma forma, sair. O código será muito pior se você não retornar a partir desse ponto.
Dascandy
11

É sua arma, sua bala e seu pé ...

É ruim porque você está pedindo problemas. Não será você ou qualquer um dos outros pôsteres nesta página que tenha exemplos de loops curtos / simples enquanto.

O problema começará em um momento muito aleatório no futuro. Pode ser causado por outro programador. Pode ser a pessoa que está instalando o software. Pode ser o usuário final.

Por quê? Eu tive que descobrir por que um aplicativo LOC de 700K começaria a queimar gradualmente 100% do tempo da CPU até que toda CPU estivesse saturada. Foi um incrível loop enquanto (verdadeiro). Era grande e desagradável, mas resumia-se a:

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

Não havia ramo final. Se o valor não corresponder a uma condição if, o loop continuará em execução até o final do tempo.

Obviamente, o programador culpou os usuários finais por não escolherem um valor que o programador esperava. (Eliminei todas as instâncias de while (true) no código.)

IMHO, não é uma boa programação defensiva usar construções como while (true). Voltará para assombrá-lo.

(Mas lembro-me de professores com notas baixas, se não comentássemos todas as linhas, mesmo para i ++;)

jqa
fonte
3
+1 no comentário sobre programação defensiva.
JHarnach
3
No seu exemplo, o código é estúpido não por causa da escolha do momento (true), mas por causa da idéia de colocar um loop em torno do código.
Florian F
De fato, qual era o objetivo desse loop? :)
juzzlin
6

É ruim no sentido de que as construções de programação estruturada são preferidas às declarações (um tanto não estruturadas) de interrupção e continuação. Eles são, por comparação, preferidos a "ir" de acordo com esse princípio.

Eu sempre recomendo tornar seu código o mais estruturado possível ... embora, como Jon Skeet ressalte, não o torne mais estruturado do que isso!

Patrick87
fonte
5

De acordo com minha experiência, na maioria dos casos, os loops têm a condição "principal" de continuar. Essa é a condição que deve ser gravada no próprio operador while (). Todas as outras condições que podem interromper o loop são secundárias, não tão importantes etc. Elas podem ser escritas como if() {break}instruções adicionais .

while(true) geralmente é confuso e é menos legível.

Penso que estas regras não cobrem 100% dos casos, mas provavelmente apenas 98% deles.

AlexR
fonte
Bem dito. É como usar um loop for a com: while (true) {i ++; ...}. Você está enterrando a condição do loop dentro do loop, e não na 'assinatura'.
usar o seguinte comando
3

Embora não seja necessariamente uma resposta sobre o porquê de não usar while (true), sempre achei essa declaração cômica e do autor que o acompanha uma explicação sucinta sobre o porquê de fazer enquanto em vez de fazer enquanto.

Com relação à sua pergunta: Não há nenhum problema inerente ao

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

... se você souber o que está fazendo e se certificar de que exit_timeem algum momento será avaliado true.

Os professores o desencorajam a usar, while(true)porque até e a menos que você saiba exatamente o que está fazendo, é uma maneira fácil de cometer um erro crítico.

Shadur
fonte
Eu acho que configuração exit_time = false; while(!exit_time) { execute_stuff(); }e do { execute_stuff(); } while(! exit_time );ambos são muito mais claros do que ter um if( condition ) { break; }no final de um loop com um while(true). Quebras são curtos-circuitos para loops - perfeitamente bem quando usadas como um curto-circuito no meio de um loop, mas você deve apenas avaliar uma condição na instrução while versus fazer uma pausa no final de um loop.
dr jimbob
No que diz respeito a essa história em quadrinhos: enquanto fazer-enquanto não pode fazer tudo o que pode fazer, fazer-às vezes é melhor fazer o que fazer-enquanto pode fazer, enquanto isso também pode fazer.
Theraot
3

Você pode apenas usar um sinalizador booleano para indicar quando terminar o loop while. Breake go tohavia razões para que o software fosse difícil de manter - a crise do software (tm) - e deveria ser evitado e facilmente pode ser.

É uma pergunta se você é pragmático ou não. Codificadores pragmáticos podem apenas usar quebra nessa situação simples.

Mas é bom obter o hábito de não usá-los; caso contrário, você pode usá-los em situações inadequadas, como em loops aninhados complicados em que a legibilidade e a manutenção do seu código se tornam mais difíceis de usar break.

Daniel Leschkowski
fonte
8
Porque manter uma bandeira booleana provavelmente não é mais claro e pode ser menos claro na minha experiência?
31911 Jon Skeet
3
@ Jon Skeet Bem, é uma questão de ir com um bom hábito ou treinar-se com um mau, usando o intervalo. Um bool chamado "running" não está claro para você? É óbvio, fácil de depurar, e como eu mencionei antes, um bom habbit é uma coisa boa a se manter. Onde está o problema?
Daniel Leschkowski
4
Um bool chamado running que, em seguida, exige que eu tenha um if (running)dentro do loop, recuando todo o resto do código quando tudo o que eu quero é sair do loop é definitivamente menos claro para mim do que uma declaração de interrupção simples que afirma exatamente o que eu quero fazer. Você parece estar pensando em usar o intervalo como um mau hábito axiomaticamente - não penso nisso como tal.
27911 Jon Skeet
2
Por que você teria um if (em execução) dentro do loop? Assim como ocorre com break no final do loop, você verifica se deseja sair e virar a bandeira em vez de usar break e usar a bandeira enquanto (executando) em vez de while (true). Eu não entendo o seu ponto, sério. Concordo que um programador pragmático pode usar break em determinadas situações, mas eu realmente não entendo os comentários que você feitas sob minha sugestão
Daniel Leschkowski
3
Você está assumindo que não precisa fazer nada dentro do loop depois de determinar se deve ou não sair. Por exemplo, na situação do OP, ele precisa pedir mais informações se não está desistindo.
21411 Jon Skeet
3

Talvez eu seja azarado. Ou talvez apenas me falte uma experiência. Mas cada vez que eu lembro de lidar com while(true)ter breakdentro, foi possível melhorar o código da aplicação Extract Method para enquanto bloco , que manteve a while(true)mas (por coincidência?) Transformou todas as breaks emreturn s.

Na minha experiência, while(true)sem pausas (isto é, com retornos ou arremessos) são bastante confortáveis ​​e fáceis de entender.


  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);
      }
  }
mosquito
fonte
3

Eu acho que sim, é muito ruim ... ou pelo menos para muitos desenvolvedores. É sintomático de desenvolvedores que não pensam em suas condições de loop. Consequentemente, há propensão a erros.

Griff Sob Becker
fonte
2

Não há nenhum grande problema com while(true)com breakdeclarações, no entanto alguns podem pensar sua reduz ligeiramente a legibilidade do código. Tente dar nomes significativos às variáveis, avalie as expressões no local apropriado.

Para o seu exemplo, parece muito mais claro fazer algo como:

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

Isso é especialmente verdadeiro se o loop do while for longo - você sabe exatamente onde está a verificação para verificar se está ocorrendo uma iteração extra. Todas as variáveis ​​/ funções têm nomes apropriados no nível de abstração. owhile(true) afirmação é dizer que o processamento não está no lugar que você pensou.

Talvez você queira uma saída diferente na segunda vez no loop. Algo como

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

parece mais legível para mim, então

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

Novamente, com um exemplo trivial, ambos são bastante legíveis; mas se o loop ficar muito grande ou profundamente aninhado (o que significa que você provavelmente já deve ter refatorado), o primeiro estilo pode ser um pouco mais claro.

dr jimbob
fonte
1

Uso algo semelhante, mas com lógica oposta, em muitas das minhas funções.

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 */
}
Jim Fell
fonte
1

É mais uma questão estética, muito mais fácil de ler código, onde você sabe explicitamente por que o loop irá parar na declaração do loop.

Petey B
fonte
1

Eu diria que, geralmente, a razão pela qual não é considerada uma boa ideia é que você não está usando o construto em seu potencial máximo. Além disso, costumo pensar que muitos instrutores de programação não gostam quando seus alunos chegam com "bagagem". Com isso, quero dizer que eles gostam de ser a principal influência no estilo de programação de seus alunos. Então, talvez seja apenas uma irritação do instrutor.

Paulo
fonte
1

Para mim, o problema é legibilidade.

Uma declaração while com uma condição verdadeira não diz nada sobre o loop. Isso torna o trabalho de entendê-lo muito mais difícil.

O que seria mais fácil de entender desses dois trechos?

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

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

Eu acho que usar o intervalo para o seu professor é como quebrar um galho de árvore para obter o fruto, use alguns outros truques (incline o galho) para que você obtenha o fruto e o galho ainda esteja vivo. :)

Bimal Sharma
fonte
0

1) Nada está errado com um do -while(true)

2) Seu professor está errado.

NSFS !!:

3) A maioria dos professores são professores e não programadores.

Pacerier
fonte
@ Espere até o seu professor ouvir isso!
Pacerier
1
Sou autodidata na área de programação. Tudo o que tenho a julgar pela é a minha TI professor na escola secundária, que nos ensinou a fazer websites usando Dreamweaver, de tal forma que tudo era uma div posicionado absolutamente ...
Connell
@Connell o meu post para mostrar o seu contrato
Pacerier
2
"Quem não pode fazer, ensina." - Essa é uma generalização bastante grande, você não acha? Doutores, engenheiros, pilotos, etc, todos devem ser autodidatas, já que seus instrutores são incompetentes, de acordo com você?
Filip-fku
@ filip-fku uau uau ~ esfrie esfrie!
Pacerier 5/08/11
0

Pode ser ruim se o loop for executado em um encadeamento em segundo plano; portanto, quando você fechar o aplicativo encerrando um encadeamento da interface do usuário, esse trecho de código continuará sendo executado. Como já foi dito, você sempre deve usar algum tipo de verificação para fornecer uma forma de cancelamento.

Dmitry
fonte