Considere o seguinte código:
public void doSomething(int input)
{
while(true)
{
TransformInSomeWay(input);
if(ProcessingComplete(input))
break;
DoSomethingElseTo(input);
}
}
Suponha que esse processo envolva um número finito, mas dependente de entrada, de etapas; o loop é projetado para terminar sozinho como resultado do algoritmo e não é projetado para ser executado indefinidamente (até ser cancelado por um evento externo). Como o teste para ver se o loop deve terminar está no meio de um conjunto lógico de etapas, o próprio loop while atualmente não verifica nada significativo; a verificação é realizada no local "apropriado" dentro do algoritmo conceitual.
Foi-me dito que esse código é ruim, porque é mais suscetível a erros devido ao fato de a condição final não ter sido verificada pela estrutura do loop. É mais difícil descobrir como você sairia do loop e poderia convidar bugs, pois a condição de quebra pode ser ignorada ou omitida acidentalmente, devido a alterações futuras.
Agora, o código pode ser estruturado da seguinte maneira:
public void doSomething(int input)
{
TransformInSomeWay(input);
while(!ProcessingComplete(input))
{
DoSomethingElseTo(input);
TransformInSomeWay(input);
}
}
No entanto, isso duplica uma chamada para um método no código, violando DRY; se TransformInSomeWay
posteriormente fossem substituídos por outro método, as duas chamadas teriam que ser encontradas e alteradas (e o fato de existirem duas pode ser menos óbvio em um trecho de código mais complexo).
Você também pode escrever como:
public void doSomething(int input)
{
var complete = false;
while(!complete)
{
TransformInSomeWay(input);
complete = ProcessingComplete(input);
if(!complete)
{
DoSomethingElseTo(input);
}
}
}
... mas agora você tem uma variável cujo único objetivo é mudar a verificação da condição para a estrutura do loop, e também deve ser verificada várias vezes para fornecer o mesmo comportamento da lógica original.
Pela minha parte, digo que, dado o algoritmo que esse código implementa no mundo real, o código original é o mais legível. Se você estivesse passando por isso você mesmo, seria assim que pensaria e, portanto, seria intuitivo para as pessoas familiarizadas com o algoritmo.
Então, o que é "melhor"? é melhor atribuir a responsabilidade da verificação de condições ao loop while, estruturando a lógica em torno do loop? Ou é melhor estruturar a lógica de uma maneira "natural", conforme indicado por requisitos ou por uma descrição conceitual do algoritmo, mesmo que isso possa significar ignorar os recursos internos do loop?
do ... until
construção também é útil para esses tipos de loops, pois eles não precisam ser "preparados".Respostas:
Fique com sua primeira solução. Os loops podem ficar muito envolvidos e uma ou mais quebras limpas podem ser muito mais fáceis para você, qualquer outra pessoa olhando para o seu código, o otimizador e o desempenho do programa do que instruções if e variáveis adicionadas. Minha abordagem usual é configurar um loop com algo como "while (true)" e obter a melhor codificação possível. Frequentemente, eu posso substituir as quebras por um controle de loop padrão; a maioria dos loops é bem simples, afinal. A condição final deve ser verificada pela estrutura do loop pelos motivos mencionados, mas não ao custo de adicionar variáveis totalmente novas, adicionar instruções if e repetir o código, os quais são ainda mais propensos a erros.
fonte
É apenas um problema significativo se o corpo do loop não for trivial. Se o corpo é tão pequeno, analisá-lo dessa maneira é trivial e não é um problema.
fonte
Tenho problemas para encontrar as outras soluções melhores que as que estão com problemas. Bem, eu prefiro a maneira Ada, que permite fazer
mas a solução de interrupção é a renderização mais limpa.
Meu ponto de vista é que, quando há uma estrutura de controle ausente, a solução mais limpa é imitá-la com outras estruturas de controle, sem usar o fluxo de dados. (E da mesma forma, quando tenho um problema de fluxo de dados, prefiro soluções puras de fluxo de dados àquelas que envolvem estruturas de controle *).
Não se engane, a discussão não aconteceria se a dificuldade não fosse a mesma: a condição é a mesma e está presente no mesmo local.
Qual dos
e
tornar melhor a estrutura pretendida? Estou votando pela primeira vez, você não precisa verificar se as condições correspondem. (Mas veja meu recuo).
fonte
while (true) e break são expressões comuns. É a maneira canônica de implementar loops de saída média em linguagens tipo C. Adicionar uma variável para armazenar a condição de saída e um if () na segunda metade do loop é uma alternativa razoável. Algumas lojas podem padronizar oficialmente uma ou outra, mas nenhuma é superior; eles são equivalentes.
Um bom programador que trabalha nessas linguagens deve saber rapidamente o que está acontecendo, caso veja uma delas. Pode ser uma boa e pequena pergunta para entrevista; entregue a alguém dois loops, um usando cada idioma, e pergunte qual é a diferença (resposta adequada: "nada", com talvez uma adição de "mas eu costumo usar esse").
fonte
Suas alternativas não são tão ruins quanto você imagina. Um bom código deve:
Indique claramente com bons nomes de variáveis / comentários sob quais condições o loop deve ser encerrado. (A condição de quebra não deve estar oculta)
Indique claramente qual é a ordem das operações. É aqui que colocar a terceira operação opcional (
DoSomethingElseTo(input)
) dentro da if é uma boa ideia.Dito isto, é fácil deixar instintos perfeccionistas e polidores de latão consumirem muito tempo. Eu apenas ajustaria o if como mencionado acima; comente o código e siga em frente.
fonte
As pessoas dizem que é uma má prática usar
while (true)
e na maioria das vezes elas estão certas. Se a suawhile
declaração contiver muitos códigos complicados e não for claro quando você sair do if, será difícil manter o código e um bug simples poderá causar um loop infinito.No seu exemplo, você está correto de usar
while(true)
porque seu código é claro e fácil de entender. Não faz sentido criar código ineficiente e difícil de ler, simplesmente porque algumas pessoas consideram sempre uma má prática usarwhile(true)
.Fui confrontado com um problema semelhante:
O uso
do ... while(true)
evitaisset($array][$key]
desnecessariamente ser chamado duas vezes, o código é mais curto, mais limpo e mais fácil de ler. Não há absolutamente nenhum risco de um bug de loop infinito ser introduzidoelse break;
no final.É bom seguir boas práticas e evitar más práticas, mas sempre há exceções e é importante manter a mente aberta e perceber essas exceções.
fonte
Se um determinado fluxo de controle é naturalmente expresso como uma declaração de interrupção, essencialmente nunca é uma escolha melhor usar outros mecanismos de controle de fluxo para emular uma declaração de interrupção.
(observe que isso inclui desenrolar parcialmente o loop e deslizar o corpo do loop para baixo, para que o loop comece coincidindo com o teste condicional)
Se você pode reorganizar seu loop para que o fluxo de controle seja naturalmente expresso com uma verificação condicional no início do loop, ótimo. Pode até valer a pena gastar algum tempo pensando se isso é possível. Mas não tente não encaixar um pino quadrado em um buraco redondo.
fonte
Você também pode ir com isso:
Ou menos confuso:
fonte
Quando a complexidade da lógica se torna pesada, o uso de um loop ilimitado com interrupções no meio do loop para controle de fluxo faz realmente mais sentido. Eu tenho um projeto com algum código que se parece com o seguinte. (Observe a exceção no final do loop.)
Para fazer essa lógica sem quebras de loop ilimitadas, eu terminaria com algo como o seguinte:
Portanto, a exceção agora é lançada em um método auxiliar. Também tem bastante
if ... else
nidificação. Não estou satisfeito com essa abordagem. Portanto, acho que o primeiro padrão é o melhor.fonte