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".