Dentro de um for-loop, devo mover a condição de quebra para o campo de condição, se possível? [fechadas]

48

Às vezes, preciso de loops que precisem de uma pausa assim:

for(int i=0;i<array.length;i++){
    //some other code
    if(condition){
        break;
    }
}

Eu me sinto desconfortável com a escrita

if(condition){
    break;
}

porque consome 3 linhas de código. E eu encontrei o loop pode ser reescrito como:

                                   ↓
for(int i=0;i<array.length && !condition;i++){
    //some other code
}

Então, minha pergunta é: é uma boa prática mover a condição para o campo de condição para reduzir linhas de códigos, se possível?

    
por mmmaaa 27.02.2018 / 10:27
fonte

16 respostas

154

Esses dois exemplos que você deu não são funcionalmente equivalentes. No original, o teste de condição é feito após a seção "algum outro código", enquanto que na versão modificada, é feito primeiro, no início do corpo do loop.

O código nunca deve ser reescrito com o único propósito de reduzir o número de linhas. Obviamente, é um bom bônus quando funciona dessa maneira, mas nunca deve ser feito à custa de legibilidade ou correção.

    
por 27.02.2018 / 10:39
fonte
51

Eu não compro o argumento de que "consome 3 linhas de código" e, portanto, é ruim. Afinal, você poderia simplesmente escrever como:

if (condition) break;

e consuma apenas uma linha.

Se o if aparecer na metade do loop, é claro que ele deve existir como um teste separado. No entanto, se esse teste aparecer no final do bloco, você criou um loop que possui um teste continue em ambas as extremidades do loop, possivelmente aumentando a complexidade do código. Esteja ciente, porém, que às vezes o teste if (condition) só pode fazer sentido depois que o bloco foi executado.

Mas assumindo que não é o caso, adotando a outra abordagem:

for(int i=0;i<array.length && !condition;i++){
    //some other code
}

as condições de saída são mantidas juntas, o que pode simplificar as coisas (especialmente se " algum outro código " for longo).

Não há resposta certa aqui, é claro. Portanto, esteja ciente dos idiomas da linguagem, quais são as convenções de codificação de sua equipe etc. Mas, no geral, eu adotaria a abordagem de teste único quando fizer sentido funcional fazê-lo.

    
por 27.02.2018 / 10:42
fonte
48

Esse tipo de pergunta provocou um debate quase tão longo quanto a programação. Para jogar meu chapéu no ringue, eu iria para a versão com a condição break e aqui está o porquê (para este caso específico ):

O loop for está lá apenas para especificar os valores de iteração no loop. Codificar a condição de quebra dentro disso apenas confunde a lógica IMO.

Ter um break separado deixa claro que durante o processamento normal, os valores são especificados no loop for e o processamento deve continuar exceto quando a condição entrar.

Como pano de fundo, comecei a codificar um break , depois coloquei a condição no ciclo for por anos (sob a direção de um programador excepcional) e voltei para o estilo break porque parecia muito mais limpo (e era o estilo predominante nos vários tomos de código que eu estava consumindo).

É outra daquelas guerras santas no final do dia - alguns dizem que você deve nunca sair de um loop artificialmente, caso contrário, não é um loop real. Faça o que você sente é legível (tanto para você e para os outros). Se reduzir as teclas é realmente sua coisa, vá jogar golfe no fim de semana - cerveja opcional.

    
por 27.02.2018 / 12:23
fonte
43

for loops são para iteração sobre algo 1 - eles não são apenas lol-vamos-puxar-aleatoriamente-coisas-do-corpo -e-colocá-los-no-laço-cabeçalho - as três expressões têm significados muito específicos:

  • O primeiro é para inicializar o iterador . Pode ser um índice, um ponteiro, um objeto de iteração ou o que for - desde que seja usado para iteração .
  • O segundo é para verificar se chegamos ao fim.
  • O terceiro é para avançar o iterador.

A idéia é separar o tratamento de iteração ( como para iterar) da lógica dentro do loop ( o que fazer em cada iteração). Muitas linguagens geralmente têm um loop for-each que libera você dos detalhes da iteração, mas mesmo que sua linguagem não tenha essa construção, ou se ela não puder ser usada em seu cenário - você ainda deve limitar o cabeçalho de loop para manipulação de iteração.

Então, você precisa se perguntar: sua condição é sobre o tratamento da iteração ou sobre a lógica dentro do loop? As chances são, é sobre a lógica dentro do loop - por isso, deve ser verificado dentro do loop, em vez de no cabeçalho.

1 Ao contrário de outros loops, que estão "esperando" que algo aconteça. Um loop for / foreach deve ter o conceito de uma "lista" de itens para iterar - mesmo que essa lista seja preguiçosa ou infinita ou abstrata.

    
por 27.02.2018 / 12:02
fonte
8

Eu recomendo usar a versão com if (condition) . É mais legível e mais fácil de depurar. Escrever 3 linhas extras de código não vai quebrar seus dedos, mas facilitará que a próxima pessoa entenda seu código.

    
por 27.02.2018 / 10:33
fonte
8

Se você estiver interagindo com uma coleção em que determinados elementos devem ser ignorados, recomendo uma nova opção:

  • Filtre sua coleção antes de iterar

Tanto o Java quanto o C # tornam isso relativamente trivial. Eu não ficaria surpreso se o C ++ tivesse uma maneira de fazer um iterador extravagante pular certos elementos que não se aplicam. Mas isso é realmente apenas uma opção se o idioma de sua escolha for compatível, e a razão pela qual você está usando break é porque existem condições para processar um elemento ou não.

Caso contrário, há uma boa razão para tornar sua condição uma parte do loop for - supondo que sua avaliação inicial esteja correta.

  • É mais fácil ver quando um loop termina no início

Eu trabalhei em código onde o loop for leva algumas centenas de linhas com vários condicionais e uma matemática complexa acontecendo lá. Os problemas com os quais você se depara são:

  • break comandos enterrados na lógica são difíceis de encontrar e algumas vezes surpreendentes
  • A depuração exige que você percorra cada iteração do loop para entender realmente o que está acontecendo
  • Grande parte do código não tem refatorações facilmente reversíveis disponíveis ou testes de unidade para ajudar na equivalência funcional após uma reescrita

Nessa situação eu recomendo as seguintes diretrizes em ordem de preferência:

  • Filtre a coleção para eliminar a necessidade de um break , se possível
  • Faça a parte condicional das condições do loop for
  • Coloque as condicionais com o break na parte superior do loop, se possível
  • Adicione um comentário de várias linhas chamando a atenção para o intervalo e por que ele está lá

Estas diretrizes estão sempre sujeitas à correção do seu código. Escolha a opção que melhor representa a intenção e melhore a clareza do seu código.

    
por 27.02.2018 / 14:43
fonte
8

Eu recomendo strongmente que você use a abordagem menos surpresa , a menos que você obtenha um benefício significativo fazendo o contrário.

As pessoas não lêem todas as letras ao lerem uma palavra, e não lêem todas as palavras ao ler um livro - se são adeptas da leitura, olham para os contornos de palavras e frases e deixam que o cérebro delas se preencha. no resto.

Portanto, as chances de o desenvolvedor ocasional assumir que isso é apenas um loop padrão e nem mesmo olhar para ele:

for(int i=0;i<array.length&&!condition;i++)

Se você quiser usar esse estilo independentemente, eu recomendo alterar as partes for(int i=0;i< e ;i++) que dizem ao cérebro do leitor que este é um padrão para loop.

Outro motivo para usar if - break é que nem sempre é possível usar sua abordagem com a condição for . Você tem que usar if - break quando a condição de quebra é muito complexa para se esconder dentro de uma instrução for , ou depende de variáveis que só são acessíveis dentro do laço for .

for(int i=0;i<array.length&&!((someWidth.X==17 && y < someWidth.x/2) || (y == someWidth.x/2 && someWidth.x == 18);i++)

Então, se você decidir usar if - break , estará coberto. Mas se você decidir ir com for -condições, você tem que usar uma mistura de if - break e for -conditions. Para ser consistente, você terá que mover as condições para trás e para frente entre as for -conditions e o if - break sempre que as condições mudarem.

    
por 28.02.2018 / 02:09
fonte
4

Sua transformação está assumindo que qualquer condition é avaliado como true quando você entrar no loop. Não podemos comentar sobre a exatidão disso, neste caso, porque não está definido.

Tendo sucesso em "reduzir linhas de código" aqui, você pode continuar a olhar para

for(int i=0;i<array.length;i++){
    // some other code
    if(condition){
        break;
    }
    // further code
}

e aplicar a mesma transformação, chegando a

for(int i=0;i<array.length && !condition;i++){
    // some other code
    // further code
}

O que é uma transformação inválida, pois agora você faz incondicionalmente further code onde antes não o fazia.

Um esquema de transformação muito mais seguro é extrair some other code e avaliar condition para uma função separada

bool evaluate_condition(int i) // This is an horrible name, but I have no context to improve it
{
     // some other code
     return condition;
}

for(int i=0;i<array.length && !evaluate_condition(i);i++){
    // further code
}
    
por 27.02.2018 / 12:50
fonte
4

TL; DR Faça o que for melhor em sua circunstância particular

Vamos dar um exemplo:

for ( int i = 1; i < array.length; i++ ) 
{
    if(something) break;
    // perform operations
}

Nesse caso, não queremos que qualquer código no loop for seja executado se something for true, portanto, mover o teste de something para o campo de condição é razoável.

for ( int i = 1; i < array.length && !something; i++ )

Então, novamente, dependendo se something pode ser definido como true antes do loop, mas não dentro dele, isso pode oferecer mais clareza:

if(!something)
{
    for ( int i = 1; i < array.length; i++ )
    {...}
}

Agora imagine isso:

for ( int i = 1; i < array.length; i++ ) 
{
    // perform operations
    if(something) break;
    // perform more operations
}

Estamos enviando uma mensagem muito clara por lá. Se something se tornar verdadeiro durante o processamento da matriz, abandone a operação inteira nesse ponto. Para mover o cheque para o campo de condição, você precisa fazer:

for ( int i = 1; i < array.length && !something; i++ ) 
{
    // perform operations
    if(!something)
    {
        // perform more operations
    }
}

Indiscutivelmente, a "mensagem" tornou-se enlameada, e estamos verificando a condição de falha em dois lugares - e se a condição de falha mudar e esquecermos um desses lugares?

Existem, é claro, muito mais formas diferentes para loop e condição, todas com suas próprias nuances.

Escreva o código para que ele leia bem (isso é obviamente um tanto subjetivo) e de forma concisa. Fora de um conjunto draconiano de diretrizes de codificação, all "rules" tem exceções. Escreva o que você acha que expressa melhor a decisão e minimize as chances de erros futuros.

    
por 28.02.2018 / 15:21
fonte
2

Embora possa ser atraente porque você vê todas as condições no cabeçalho de loop e break pode ser confuso em blocos grandes, um for loop é um padrão em que a maioria dos programadores espera fazer um loop em algum intervalo.

Especialmente desde que você faz isso, adicionar uma condição de quebra adicional pode causar confusão e é mais difícil de ver se é funcionalmente equivalente.

Geralmente, uma boa alternativa é usar um loop while ou do {} while , que verifica ambas as condições, supondo que sua matriz tenha pelo menos um elemento.

int i=0
do {
    // some other code
    i++; 
} while(i < array.length && condition);

Usando um loop que está apenas verificando uma condição e não executando o código do cabeçalho do loop, você fica muito claro quando a condição é verificada e qual é a condição real e o código com efeitos colaterais.

    
por 27.02.2018 / 14:59
fonte
1

Isso é ruim, já que o código deve ser lido por seres humanos - os for não-idiomáticos são confusos para ler, o que significa que os bugs são mais propensos a se esconder aqui, mas o código original pode ter sido possível para melhorar, mantendo-o curto e legível.

Se o que você deseja é encontrar um valor em uma matriz (o exemplo de código fornecido é um pouco genérico, mas pode ser esse padrão), você pode tentar usar explicitamente uma função fornecida por uma linguagem de programação especificamente para esse propósito. Por exemplo (pseudo-código, como uma linguagem de programação não foi especificada, as soluções variam dependendo de um idioma específico).

array.find(item -> item.valid)

Isso deve reduzir visivelmente a solução e simplificá-la, já que seu código diz especificamente o que você precisa.

    
por 28.02.2018 / 10:45
fonte
1

Algumas razões na minha opinião dizem que você não deveria.

  1. Isso reduz a legibilidade.
  2. A saída pode não ser a mesma. No entanto, isso pode ser feito com um loop do...while (não while ), pois a condição é verificada após a execução de algum código.

Mas além disso, considere isso,

for(int i=0;i<array.length;i++){

    // Some other code
    if(condition1){
        break;
    }

    // Some more code
    if(condition1){
        break;
    }

    // Some even more code
}

Agora, você pode realmente conseguir isso adicionando essas condições nessa condição for ?

    
por 28.02.2018 / 05:53
fonte
0

Acho que remover o break pode ser uma boa ideia, mas não para salvar linhas de código.

A vantagem de escrevê-lo sem break é que a pós-condição do loop é óbvia e explícita. Quando você terminar o loop e executar a próxima linha do programa, sua condição de loop i < array.length && !condition deverá ser falsa. Portanto, i foi maior ou igual ao tamanho da matriz ou condition é verdadeiro.

Na versão com break , há uma pegadinha oculta. A condição de loop diz que o loop termina quando você executa algum outro código para cada índice de matriz válido, mas na verdade existe uma instrução break que pode terminar o faça um loop antes disso, e você não verá a menos que você revise o código de loop com muito cuidado. Além disso, os analisadores estáticos automatizados, incluindo a otimização de compiladores, podem ter dificuldade em deduzir quais são as pós-condições do loop também.

Esse foi o motivo original pelo qual Edgar Dijkstra escreveu "Goto Considered Harmful". Não era uma questão de estilo: sair de um loop dificulta raciocinar formalmente sobre o estado do programa. Escrevi muitas declarações break; na minha vida e, uma vez como adulto, escrevi um goto (para romper vários níveis de um loop interno crítico para o desempenho e continuar com outro ramo da árvore de pesquisa ). No entanto, é muito mais provável que eu escreva uma instrução return condicional de um loop (que nunca executa o código após o loop e, portanto, não pode alterar suas pós-condições) do que um break .

Postscript

Na verdade, refatorar o código para dar o mesmo comportamento pode realmente precisar de mais linhas de código. Sua refatoração pode ser válida para algum outro código ou se pudermos provar que condition começará falso. Mas o seu exemplo é equivalente no caso geral para:

if ( array.length > 0 ) {
  // Some other code.
}
for ( int i = 1; i < array.length && !condition; i++ ) {
  // Some other code.
}

Se algum outro código não for um de uma linha, você pode então movê-lo para uma função para não se repetir, e então você quase refactou seu loop em uma cauda função recursiva.

Eu reconheço que este não era o ponto principal da sua pergunta, e você estava mantendo isso simples.

    
por 28.02.2018 / 01:21
fonte
0

Sim, mas sua sintaxe não é convencional e, portanto, é ruim (tm)

Espero que seu idioma suporte algo como

while(condition) //condition being a condition which if true you want to continue looping
{
    do thing; //your conditionally executed code goes here
    i++; //you can use a counter if you want to reference array items in order of index
}
    
por 27.02.2018 / 10:52
fonte
0

Se você está preocupado com uma declaração for excessivamente complexa sendo difícil de ler, essa é definitivamente uma preocupação válida. Perder o espaço vertical também é um problema (ainda que menos crítico).

(Pessoalmente eu não gosto da regra que if declarações sempre devem ter chaves, que é em grande parte a causa do espaço vertical perdido aqui. Note que o problema é desperdiçar espaço vertical. O uso de espaço vertical com linhas significativas não deve ser um problema.

Uma opção é dividi-lo em várias linhas. Isso é definitivamente o que você deve fazer se estiver usando instruções for realmente complexas, com várias variáveis e condições. (Isso é para mim um uso melhor do espaço vertical, dando tantas linhas quanto possível algo significativo.)

for (
   int i = 0, j = 0;
   i < array.length && !condition;
   i++, j++
) {
   // stuff
}

Uma abordagem melhor pode ser tentar usar uma forma mais declarativa e menos imperativa. Isso irá variar dependendo do idioma, ou você pode precisar escrever suas próprias funções para ativar esse tipo de coisa. Também depende de qual condition realmente é. Presumivelmente, ele deve ser capaz de mudar de alguma forma, dependendo do que está na matriz.

array
.takeWhile(condition)  // condition can be item => bool or (item, index) => bool
.forEach(doSomething); // doSomething can be item => void or (item, index) => void
    
por 02.03.2018 / 18:54
fonte
0

Uma coisa que foi esquecida: Quando eu quebro de um loop (não faço todas as iterações possíveis, não importa o quão implementado), normalmente é o caso que eu não só quero sair do loop, eu também quero saber porque isso aconteceu. Por exemplo, se eu procurar por um item X, não só eu quebro o loop, eu também quero saber que X foi encontrado e onde ele está.

Nessa situação, ter uma condição fora do loop é bastante natural. Então eu começo com

bool found = false;
size_t foundIndex;

e no loop vou escrever

if (condition) {
    found = true;
    foundIndex = i;
    break;
}

com o loop for

for (i = 0; i < something && ! found; ++i)

Agora, verificar a condição no loop é bastante natural. Se existe uma instrução "break" depende se há processamento adicional no loop que você deseja evitar. Se algum processamento for necessário e outro processamento não for, você poderá ter algo como

if (! found) { ... }

em algum lugar no seu loop.

    
por 03.03.2018 / 15:42
fonte