É while (true) com break bad programming practice?

Eu geralmente uso esse padrão de código:

while(true) { //do something if() { break; } } 

Outro programador me disse que isso era uma prática ruim e que eu deveria substituí-lo pelo mais padrão:

 while(!) { //do something } 

Seu raciocínio era que você poderia “esquecer o intervalo” com muita facilidade e ter um loop infinito. Eu disse a ele que no segundo exemplo você poderia facilmente colocar em uma condição que nunca retornou verdadeira e tão facilmente ter um loop infinito, então ambas são práticas igualmente válidas.

Além disso, muitas vezes eu prefiro o primeiro, pois torna o código mais fácil de ler quando você tem vários pontos de quebra, ou seja, várias condições que saem do loop.

Alguém pode enriquecer esse argumento adicionando evidências para um lado ou para o outro?

Existe uma discrepância entre os dois exemplos. O primeiro executará o “fazer alguma coisa” pelo menos uma vez a cada vez, mesmo que a afirmação nunca seja verdadeira. A segunda só fará “alguma coisa” quando a afirmação for avaliada como verdadeira.

Eu acho que o que você está procurando é um loop do-while. Eu concordo 100% que while (true) não é uma boa idéia porque torna difícil manter este código e a maneira como você está escapando do loop é muito goto que é considerada uma prática ruim.

Experimentar:

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

Verifique sua documentação de idioma individual para a syntax exata. Mas olhe para esse código, ele basicamente faz o que está em execução e, em seguida, verifica a porção while para ver se deve fazer isso novamente.

Para citar esse notável desenvolvedor de dias passados, Wordsworth:


Na verdade a prisão, para a qual nós condenamos
Nós mesmos, não há prisão; e daí para mim
Em diversos humores, era um passatempo obrigatório
Dentro do escasso terreno do Soneto;
Satisfeito se algumas almas (para tais necessidades devem ser)
Quem sentiu o peso da liberdade demais?
Deve encontrar um breve consolo lá, como eu encontrei.

Wordsworth aceitou os requisitos estritos do soneto como um quadro libertador, e não como um colete de forças. Eu sugeriria que o coração da “programação estruturada” é desistir da liberdade de construir charts de stream arbitrariamente complexos em favor de uma facilidade de compreensão libertadora.

Eu concordo livremente que às vezes uma saída antecipada é a maneira mais simples de expressar uma ação. No entanto, minha experiência tem sido que, quando me forço a usar as estruturas de controle mais simples possíveis (e realmente penso em projetar dentro dessas restrições), geralmente descubro que o resultado é um código mais simples e mais claro. A desvantagem com

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

é que é fácil deixar que action0 e action0 se tornem pedaços de código maiores ou maiores, ou adicionar “apenas mais uma” sequência de teste e quebra de ação, até se tornar difícil apontar para uma linha específica e responder à pergunta “Que condições eu sei segurar neste momento? ” Então, sem fazer regras para outros programadores, eu tento evitar o while (true) {...} no meu próprio código sempre que possível.

Quando você pode escrever seu código no formulário

 while (condition) { ... } 

ou

 while (!condition) { ... } 

sem saídas ( break , continue , ou goto ) no corpo , essa forma é preferida, porque alguém pode ler o código e entender a condição de terminação apenas olhando para o header . Isso é bom.

Mas muitos loops não se encheckboxm nesse modelo, e o loop infinito com saída (s) explícita (s) no meio é um modelo honroso . (Loops com continue são geralmente mais difíceis de entender do que loops com break ). Se você quiser alguma evidência ou autoridade para citar, não procure mais do que o famoso artigo de Don Knuth sobre Programação Estruturada com Goto Statements ; você encontrará todos os exemplos, argumentos e explicações que você poderia desejar.

Um ponto de menor importância: escrever while (true) { ... } marca como um antigo programador Pascal ou talvez hoje em dia um programador Java. Se você está escrevendo em C ou C ++, o idioma preferido é

 for (;;) { ... } 

Não há uma boa razão para isso, mas você deve escrever desta forma porque é assim que os programadores de C esperam vê-lo.

eu prefiro

 while(!) { //do something } 

mas acho que é mais uma questão de legibilidade do que o potencial de “esquecer o intervalo”. Eu acho que esquecer o break é um argumento bastante fraco, já que seria um erro e você encontraria e consertaria isso imediatamente.

O argumento que eu tenho contra o uso de um break para sair de um loop infinito é que você está essencialmente usando a instrução break como um goto . Eu não sou religiosamente contra o uso do goto (se o idioma o suporta, é um jogo justo), mas eu tento substituí-lo se houver uma alternativa mais legível.

No caso de muitos pontos de break eu os replaceia por

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

Consolidar todas as condições de parada dessa maneira torna muito mais fácil ver o que vai terminar este loop. declarações de break podem ser espalhadas, e isso é tudo, menos legível.

while (true) pode fazer sentido se você tiver muitas declarações e quiser parar se alguma falha

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

é melhor que

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

Javier fez um comentário interessante sobre minha resposta anterior (a que cita Wordsworth):

Eu acho que while (true) {} é uma construção mais ‘pura’ do que while (condição) {}.

e eu não consegui responder adequadamente em 300 caracteres (desculpe!)

No meu ensino e mentoria, eu defini informalmente “complexidade” como “Quanto do resto do código eu preciso ter em mente para poder entender essa linha ou expressão única?” Quanto mais coisas eu tenho que ter em mente, mais complexo é o código. Quanto mais o código me diz explicitamente, menos complexo.

Assim, com o objective de reduzir a complexidade, deixe-me responder a Javier em termos de integridade e força, em vez de pureza.

Eu penso neste fragment de código:

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

como expressando duas coisas simultaneamente:

  1. o corpo (inteiro) será repetido enquanto c1 permanecer verdadeiro, e
  2. no ponto 1, onde a1 é executado, c1 tem a garantia de manter.

A diferença é de perspectiva; o primeiro deles tem a ver com o comportamento dynamic externo de todo o loop em geral, enquanto o segundo é útil para entender a garantia estática interna com a qual eu posso contar enquanto penso em a1 em particular. É claro que o efeito líquido de a1 pode invalidar c1 , exigindo que eu pense mais sobre o que posso contar no ponto 2, etc.

Vamos colocar um exemplo específico (minúsculo) para pensar sobre a condição e a primeira ação:

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

O problema "externo" é que o loop está claramente fazendo algo dentro de someString que só pode ser feito enquanto o index estiver posicionado no someString . Isso cria uma expectativa de que estaremos modificando index ou someString dentro do corpo (em um local e maneira desconhecida até que eu examine o corpo) para que a terminação eventualmente ocorra. Isso me dá contexto e expectativa para pensar sobre o corpo.

A questão "interna" é que temos a garantia de que a ação seguinte ao ponto 1 será legal, por isso, ao ler o código no ponto 2, posso pensar no que está sendo feito com um valor de caractere que sei que foi legalmente obtido. (Não podemos sequer avaliar a condição se someString é uma referência nula, mas também estou assumindo que protegemos contra isso no contexto em torno deste exemplo!)

Em contraste, um loop da forma:

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

me decepciona em ambos os problemas. No nível externo, fico me perguntando se isso significa que eu realmente deveria esperar que esse loop fosse ciclo para sempre (por exemplo, o loop de despacho do evento principal de um sistema operacional), ou se há algo mais acontecendo. Isso não me dá nem um contexto explícito para a leitura do corpo, nem uma expectativa do que constitui progresso em direção à terminação (incerta).

No nível interno, não tenho absolutamente nenhuma garantia explícita sobre quaisquer circunstâncias que possam ocorrer no ponto 1. A condição true , que é, obviamente, verdadeira em todos os lugares, é a declaração mais fraca possível sobre o que podemos saber em qualquer ponto do programa. Entender as condições prévias de uma ação é uma informação muito valiosa ao tentar pensar sobre o que a ação realiza!

Então, sugiro que o while (true) ... idioma é muito mais incompleto e fraco, e portanto mais complexo do que while (c1) ... acordo com a lógica que descrevi acima.

O primeiro é OK se houver muitas maneiras de interromper o loop ou se a condição de quebra não puder ser expressa facilmente na parte superior do loop (por exemplo, o conteúdo do loop precisa ser executado na metade mas a outra metade não deve ser executada , na última iteração).

Mas se você puder evitá-lo, você deve, porque a programação deve ser sobre escrever coisas muito complexas da maneira mais óbvia possível, ao mesmo tempo em que implementa os resources corretamente e com desempenho. É por isso que seu amigo, no caso geral, está correto. A maneira do seu amigo de escrever construções de laço é muito mais óbvia (supondo que as condições descritas no parágrafo anterior não sejam obtidas).

Às vezes você precisa de loop infinito, por exemplo, escutando na porta ou esperando por conexão.

Então, enquanto (verdadeiro) … não deve ser classificado como bom ou ruim, deixe a situação decidir o que usar

Depende do que você está tentando fazer, mas em geral prefiro colocar o condicional no tempo.

  • É mais simples, já que você não precisa de outro teste no código.
  • É mais fácil de ler, já que você não precisa caçar por um intervalo dentro do loop.
  • Você está reinventando a roda. O objective é fazer algo desde que um teste seja verdadeiro. Por que subverter isso colocando a condição de quebra em outro lugar?

Eu usaria um loop while (true) se eu estivesse escrevendo um daemon ou outro processo que deveria ser executado até ser morto.

Se houver uma (e apenas uma) condição de quebra não excepcional, colocar essa condição diretamente na construção do stream de controle (o tempo) é preferível. Vendo while (true) {…} me faz como leitor de código pensar que não há uma maneira simples de enumerar as condições de quebra e me faz pensar “olhe cuidadosamente para isso e pense cuidadosamente sobre as condições de quebra (o que é definido antes -los no loop atual e o que poderia ter sido definido no loop anterior) ”

Resumindo, estou com seu colega no caso mais simples, mas enquanto (true) {…} não é incomum.

A resposta do consultor perfeito: depende. Na maioria dos casos, a coisa certa a fazer é usar um loop while

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

ou um “repeat until” que é feito em uma linguagem semelhante ao C com

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

Se algum desses casos funcionar, use-os.

Às vezes, como no loop interno de um servidor, você realmente quer dizer que um programa deve continuar até que algo externo o interrompa. (Considere, por exemplo, um daemon httpd – ele não irá parar a menos que falhe ou seja interrompido por um desligamento.)

ENTÃO E APENAS ENTÃO use um tempo (1):

 while(1) { accept connection fork child process } 

Caso final é a rara ocasião em que você quer fazer alguma parte da function antes de terminar. Nesse caso, use:

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

Há uma questão substancialmente idêntica já em SO em É VERDADEIRA … BREAK … END ENQUANTO um bom design? . @Glomek respondeu (em um post subestimado):

Às vezes é muito bom design. Veja Programação estruturada com instruções Goto por Donald Knuth para alguns exemplos. Eu uso essa idéia básica frequentemente for loops que são executados “uma vez e meia”, especialmente loops de leitura / processo. No entanto, eu geralmente tento ter apenas uma instrução break. Isso facilita o raciocínio sobre o estado do programa após o término do loop.

Um pouco mais tarde, eu respondi com o comentário relacionado, e também lamentavelmente subestimado (em parte porque eu não notei a primeira vez que o Glomek apareceu):

Um artigo fascinante é “Programação Estruturada com Indicações”, de Knuth, de 1974 (disponível em seu livro “Programação Literária”, e provavelmente também em outros lugares). Ele discute, entre outras coisas, maneiras controladas de romper os loops e (sem usar o termo) a declaração loop-and-a-half.

Ada também fornece construções em loop, incluindo

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

O código da pergunta original é semelhante a isso na intenção.

Uma diferença entre o item SO referenciado e este é o ‘break final’; é um loop de disparo único que usa break para sair do loop antecipadamente. Houve perguntas sobre se esse também é um bom estilo – não tenho a referência cruzada em mãos.

Não, isso não é ruim, já que você nem sempre sabe a condição de saída ao configurar o loop ou pode ter várias condições de saída. No entanto, requer mais cuidado para evitar um loop infinito.

Não é tanto a parte do tempo (verdadeira) que é ruim, mas o fato de você ter que quebrar ou sair disso é o problema. break e goto não são methods realmente aceitáveis ​​de controle de stream.

Eu também não vejo realmente o ponto. Mesmo em algo que percorre toda a duração de um programa, você pode pelo menos ter como um booleano chamado Quit ou algo que você defina como true para sair do loop corretamente em um loop como while (! Quit) … Não apenas chamando break em algum ponto arbitrário e pulando,

O problema é que nem todo algoritmo adere ao modelo “while (cond) {action}”.

O modelo de loop geral é assim:

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

Quando não há ação_A, você pode substituí-lo por:

 loop_prepare while(cond) action_B after_loop_code 

Quando não há action_B, você pode substituí-lo por:

 loop_prepare do action_A while(cond) after_loop_code 

No caso geral, action_A será executado n vezes e action_B será executado (n-1) vezes.

Um exemplo da vida real é: imprima todos os elementos de uma tabela separados por vírgulas. Queremos todos os n elementos com vírgulas (n-1).

Você sempre pode fazer alguns truques para manter o modelo while-loop, mas isso sempre repetirá o código ou verificará duas vezes a mesma condição (para cada loop) ou adicionará uma nova variável. Então você sempre será menos eficiente e menos legível do que o modelo while-true-break loop.

Exemplo de (mau) “truque”: adicionar variável e condição

 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 

Exemplo de (mau) “truque”: repita o código. O código repetido não deve ser esquecido durante a modificação de uma das duas seções.

 loop_prepare action_A while(cond): action_B action_A after_loop_code 

Nota: no último exemplo, o programador pode ofuscar (voluntariamente ou não) o código misturando o “loop_prepare” com o primeiro “action_A”, e action_B com o segundo action_A. Então ele pode ter a sensação de que não está fazendo isso.

Ele provavelmente está correto.

Funcionalmente, os dois podem ser idênticos.

No entanto, para facilitar a leitura e compreender o stream do programa, o while (condição) é melhor. A quebra parece mais uma espécie de goto. O while (condição) é muito claro nas condições que continuam o loop, etc. Isso não significa que o break está errado, apenas pode ser menos legível.

Algumas vantagens de usar o último constructo que vem à minha mente:

  • é mais fácil entender o que o loop está fazendo sem procurar por quebra no código do loop.

  • se você não usar outras quebras no código de loop, haverá apenas um ponto de saída em seu loop e essa é a condição while ().

  • geralmente acaba sendo menos código, o que aumenta a legibilidade.

Eu prefiro o tempo (!) Abordagem porque mais clara e imediatamente transmite a intenção do loop.

Tem havido muita conversa sobre a legibilidade aqui e é muito bem construída, mas como com todos os loops que não são fixos em tamanho (ou seja, enquanto e enquanto) você corre em risco.

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

Dentro de um loop while você está, de fato, pedindo por um processo que roda indefinidamente a menos que algo aconteça, e se algo não acontecer dentro de um determinado parâmetro, você terá exatamente o que você queria … um loop infinito.

Eu acho que o benefício de usar “while (true)” é provavelmente deixar várias condições de saída mais fáceis de escrever, especialmente se essas condições de saída tiverem que aparecer em locais diferentes dentro do bloco de códigos. No entanto, para mim, pode ser caótico quando eu tenho que executar o código para ver como o código interage.

Pessoalmente vou tentar evitar enquanto (verdade). A razão é que, sempre que olho para o código escrito anteriormente, geralmente descubro que preciso descobrir quando ele é executado / termina mais do que realmente faz. Portanto, ter que localizar os “intervalos” primeiro é um pouco problemático para mim.

Se houver necessidade de várias condições de saída, tenho a tendência de refatorar a lógica de determinação de condição em uma function separada, para que o bloco de loop pareça limpo e mais fácil de entender.

O que o seu amigo recomenda é diferente do que você fez. Seu próprio código é mais parecido com

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

que sempre executa o loop pelo menos uma vez, independentemente da condição.

Mas há momentos em que as pausas estão perfeitamente bem, como mencionado por outros. Em resposta à preocupação do seu amigo de “esquecer o intervalo”, eu frequentemente escrevo da seguinte forma:

 while(true){ // do something if() break; // continue do something } 

Por um bom recuo, o ponto de quebra é claro para o leitor de primeira vez do código, pareça tão estrutural quanto os códigos que quebram no início ou no final de um loop.

usando loops como

while (1) {fazer coisas}

é necessário em algumas situações. Se você faz qualquer programação de sistemas embarcados (pense em microcontroladores como PICs, MSP430 e programação DSP) então quase todo o seu código estará em um loop while (1). Ao codificar para DSPs, às vezes você só precisa de um tempo (1) {} e o resto do código é uma rotina de serviço de interrupção (ISR).

Eu gosto for loops melhor:

 for (;;) { ... } 

ou até mesmo

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

Mas se a function init() parece com o corpo do loop, você é melhor com

 do { ... } while(condition); 
    Intereting Posts