É uma prática ruim usar interrupção no loop for? [fechadas]

123

É uma má prática usar breakdeclaração dentro de um forloop ?

Digamos, estou procurando um valor em uma matriz. Compare dentro de um loop for e, quando o valor for encontrado, break;saia do loop for.

Esta é uma prática ruim? Vi a alternativa usada: defina uma variável vFounde defina-a como true quando o valor for encontrado e verifique vFounda forcondição da instrução. Mas é necessário criar uma nova variável apenas para esse fim?

Estou perguntando no contexto de um loop normal C ou C ++.

PS: As diretrizes de codificação MISRA desaconselham o uso de interrupções.

kiki
fonte
28
Não coloque breakna mesma liga que goto:)
BoltClock
2
Er, eu não os coloquei na mesma liga que as ... Regras MISRA especificam quebra, se bem me lembro.
kiki
1
A única razão que eu posso pensar para não usar ruptura dentro de um loop é quando você ainda tem que processar mais itens que podem mudar o resultado do que você está fazendo ...
Younes
4
Regras Misra foram relaxadas: misra.org.uk/forum/viewtopic.php?f=75&t=298
Johan Kotliński

Respostas:

122

Muitas respostas aqui, mas ainda não vi isso mencionado:

A maioria dos "perigos" associados ao uso breakou continueem um loop for são negados se você criar loops organizados e de fácil leitura. Se o corpo do seu loop abranger vários comprimentos de tela e tiver vários sub-blocos aninhados, sim, você pode facilmente esquecer que algum código não será executado após a interrupção. Se, no entanto, o loop for curto e direto ao ponto, o objetivo da instrução break deve ser óbvio.

Se um loop estiver ficando muito grande, use uma ou mais chamadas de função bem nomeadas no loop. O único motivo real para evitar isso é processar gargalos.

e.James
fonte
11
Bem verdade. É claro que se o loop é tão grande e complexo que é difícil ver o que está acontecendo dentro dele, isso é um problema, se você tem uma pausa ou não.
21413 Jay
1
Outra questão é que a interrupção e a continuidade causam problemas com a refatoração. Isso pode ser um sinal de que é uma má prática. A intenção do código também é mais clara ao usar as instruções if.
Ed Greaves
Essas "chamadas de função bem nomeadas" podem ser funções lambda definidas localmente em vez de funções privadas definidas externamente que não serão usadas em nenhum outro lugar.
DavidRR
135

Não, quebrar é a solução correta.

Adicionar uma variável booleana dificulta a leitura do código e adiciona uma fonte potencial de erros.

smirkingman
fonte
2
acordado. Especialmente se você estiver procurando sair do loop sob mais de uma única condição. O booleano para deixar um loop pode ficar realmente confuso.
#Rotologist #
2
É por isso que eu uso gotopara interromper os loops aninhados de nível 2+ - porque não existembreak(level)
Петър Петров
3
Prefiro colocar o código do loop em uma função e apenas retornar. Isso evita o goto e divide seu código em partes menores.
precisa
53

Você pode encontrar todos os tipos de códigos profissionais com instruções 'break' neles. Faz perfeitamente sentido usá-lo sempre que necessário. No seu caso, essa opção é melhor do que criar uma variável separada apenas com o objetivo de sair do loop.

Amit S
fonte
47

Usar breaktanto quanto continueem forloop é perfeitamente adequado .

Simplifica o código e melhora sua legibilidade.


fonte
Sim. Por um tempo, eu não gostei da idéia e a codifiquei, mas não demorou muito para que eu percebesse que a "solução alternativa" costumava ser um pesadelo de manutenção. Bem, não é um pesadelo, mas mais propenso a erros.
MetalMikester 13/10/10
24

Longe das práticas recomendadas, o Python (e outras linguagens?) Estendeu a estrutura do forloop , de modo que parte dela será executada se o loop não for break .

for n in range(5):
    for m in range(3):
        if m >= n:
            print('stop!')
            break
        print(m, end=' ')
    else:
        print('finished.')

Resultado:

stop!
0 stop!
0 1 stop!
0 1 2 finished.
0 1 2 finished.

Código equivalente sem breake útil else:

for n in range(5):
    aborted = False
    for m in range(3):
        if not aborted:
            if m >= n:
                print('stop!')
                aborted = True
            else:            
                print(m, end=' ')
    if not aborted:
        print('finished.')
Nick T
fonte
2
Oooh, eu gosto disso e às vezes também desejei isso na sintaxe C. Agradável, que poderia ser adaptada para uma futura linguagem do tipo C sem ambiguidade.
Supercat 13/10
"Linguagem do tipo C": P
nirvanaswap 18/03/16
15

Não há nada inerentemente errado em usar uma instrução break, mas os loops aninhados podem ficar confusos. Para melhorar a legibilidade, muitas linguagens ( pelo menos Java suporta ) quebram os rótulos, o que melhorará bastante a legibilidade.

int[] iArray = new int[]{0,1,2,3,4,5,6,7,8,9};
int[] jArray = new int[]{0,1,2,3,4,5,6,7,8,9};

// label for i loop
iLoop: for (int i = 0; i < iArray.length; i++) {

    // label for j loop
    jLoop: for (int j = 0; j < jArray.length; j++) {

        if(iArray[i] < jArray[j]){
            // break i and j loops
            break iLoop;
        } else if (iArray[i] > jArray[j]){  
            // breaks only j loop
            break jLoop;
        } else {
            // unclear which loop is ending
            // (breaks only the j loop)
            break;
        }
    }
}

Eu direi que as instruções de quebra (e retorno) geralmente aumentam a complexidade ciclomática, o que dificulta a comprovação de que o código está fazendo a coisa correta em todos os casos.

Se você estiver pensando em usar uma pausa ao iterar sobre uma sequência para um item em particular, convém reconsiderar a estrutura de dados usada para armazenar seus dados. Usar algo como um Conjunto ou Mapa pode fornecer melhores resultados.

cyber-monge
fonte
4
+1 para complexidade ciclomática.
Sp00m
Programadores .NET podem querer explorar o uso do LINQ como uma alternativa potencial a forloops complexos .
DavidRR
14

break é uma declaração completamente aceitável de usar (assim como continue , btw). É tudo sobre legibilidade do código - contanto que você não tenha loops complicados demais, tudo bem.

Não é como se eles fossem da mesma liga que goto . :)

Riviera
fonte
Eu já vi argumentar que uma declaração de quebra é efetivamente um pulo .
glenatron
3
O problema com o goto é que você pode apontá-lo para qualquer lugar. Tanto a quebra quanto a continuidade preservam a modularidade do código. Esse argumento está no mesmo nível de ter um único ponto de saída para cada função.
Zachary Yates
1
Ouvi dizer que ter vários pontos de saída para uma função é efetivamente um pulo . ; D
glenatron
2
@glenatron O problema com o goto não é a declaração goto, é a introdução do rótulo que o goto salta para esse é o problema. Quando você lê uma declaração goto, não há incerteza sobre o fluxo de controle. Quando você lê um rótulo, há muita incerteza sobre o fluxo de controle (onde estão todos os gotos que transferem o controle para esse rótulo?). Uma declaração de interrupção não introduz um rótulo, portanto, não introduz novas complicações.
Stephen C. Steel
1
O "intervalo" é um goto especial que só pode deixar blocos. Os problemas históricos com "goto" eram (1) que poderia ser, e frequentemente foi, usado para construir blocos de loop sobrepostos de uma maneira que não é possível com estruturas de controle apropriadas; (2) uma maneira comum de executar uma construção "se-então" que era geralmente "falsa" era fazer com que o caso verdadeiro se ramificasse em um local não relacionado no código e depois se ramificasse novamente. Isso custaria duas ramificações no caso raro e zero no caso comum, mas fez o código parecer horrível.
supercat
14

Regra geral: se seguir uma regra exigir que você faça algo mais estranho e difícil de ler, quebre a regra e quebre a regra.

No caso de fazer um loop até encontrar algo, você se depara com o problema de distinguir achado versus não encontrado quando sai. Isso é:

for (int x=0;x<fooCount;++x)
{
  Foo foo=getFooSomehow(x);
  if (foo.bar==42)
    break;
}
// So when we get here, did we find one, or did we fall out the bottom?

Então, ok, você pode definir um sinalizador ou inicializar um valor "encontrado" para nulo. Mas

Por isso, em geral, prefiro inserir minhas pesquisas em funções:

Foo findFoo(int wantBar)
{
  for (int x=0;x<fooCount;++x)
  {
    Foo foo=getFooSomehow(x);
    if (foo.bar==wantBar)
      return foo;
  }
  // Not found
  return null;
}

Isso também ajuda a organizar o código. Na linha principal, "localizar" se torna uma única declaração e, quando as condições são complexas, elas são escritas apenas uma vez.

Jay
fonte
1
Exatamente o que eu queria dizer. Frequentemente, quando me vejo usando break, tento encontrar uma maneira de refatorar o código em uma função, para que eu possa usá-lo return.
Rene Saarsoo 13/10/10
Eu concordo 100% com você, não há nada inerentemente errado em usar o intervalo. No entanto, geralmente indica que o código em que ele está pode precisar ser extraído para uma função em que a quebra seria substituída pelo retorno. Deve ser considerado um 'cheiro de código' em vez de um erro definitivo.
vascowhite
Sua resposta pode levar alguns a explorar a conveniência de usar várias instruções de retorno .
DavidRR
13

Depende do idioma. Enquanto você pode verificar uma variável booleana aqui:

for (int i = 0; i < 100 && stayInLoop; i++) { ... }

não é possível fazer isso ao percorrer uma matriz:

for element in bigList: ...

Enfim, breaktornaria ambos os códigos mais legíveis.

eumiro
fonte
7

No seu exemplo, você não sabe o número de iterações para o loop for . Por que não usar enquanto laço em vez disso, o que permite que o número de iterações a ser indeterminado no início?

É, por conseguinte, não é necessário o uso de ruptura statemement em geral, como o ciclo pode ser melhor demonstrados como uma enquanto loop.

Programador
fonte
1
Um loop "for" geralmente é bom se houver um número máximo conhecido de iterações. Dito isto, um loop while (1) às vezes é melhor em um cenário em que alguém está procurando por um item e planeja criá-lo se ele não existir. Nesse caso, se o código encontrar o item, ele fará uma interrupção direta e, se atingir o final da matriz, criará um novo item e fará uma pausa.
supercat 13/10/10
2
No exemplo dado - pesquisando uma matriz por uma matriz, o loop for é a construção idiomática adequada. Você não usa um loop while para percorrer uma matriz. Você usa um loop for. (ou um loop para cada se disponível). Você faz isso como uma cortesia para outros programadores, pois eles reconhecem a construção "for (int i = 0; i <ra.length; i ++) {}" imediatamente como "Percorrer a matriz" A adição de uma interrupção a esta construção é simplesmente uma declaração "Pare cedo se puder".
22810 Chris Cudmore
7

Eu concordo com outras pessoas que recomendam o uso break. A pergunta conseqüente óbvia é por que alguém recomendaria o contrário? Bem ... quando você usa break, pula o restante do código no bloco e as demais iterações. Às vezes, isso causa bugs, por exemplo:

  • um recurso adquirido na parte superior do bloco pode ser liberado na parte inferior (isso é verdade mesmo para blocos dentro de forloops), mas essa etapa de liberação pode ser acidentalmente ignorada quando uma saída "prematura" é causada por uma breakdeclaração (em "moderno" C ++, "RAII" é usado para lidar com isso de uma maneira confiável e protegida contra exceções: basicamente, os destruidores de objetos liberam recursos de maneira confiável, independentemente de como um escopo é encerrado)

  • alguém pode alterar o teste condicional na fordeclaração sem perceber que existem outras condições de saída deslocalizadas

  • A resposta de ndim observa que algumas pessoas podem evitar breaks para manter um tempo de execução de loop relativamente consistente, mas você estava comparando breakcom o uso de uma variável de controle booleana de saída antecipada onde isso não se aplica

De vez em quando, as pessoas que observam esses erros percebem que podem ser evitadas / atenuadas por essa regra de "sem interrupções" ... na verdade, existe toda uma estratégia relacionada à programação "mais segura" chamada "programação estruturada", onde cada função deve ter um único ponto de entrada e saída também (ou seja, sem ir, sem retorno antecipado). Pode eliminar alguns erros, mas sem dúvida apresenta outros. Por que eles fazem isso?

  • eles têm uma estrutura de desenvolvimento que incentiva um estilo particular de programação / código e têm evidências estatísticas de que isso produz um benefício líquido nessa estrutura limitada, ou
  • foram influenciados por diretrizes ou experiência de programação dentro de tal estrutura, ou
  • eles são apenas idiotas ditatoriais, ou
  • qualquer uma das inércias históricas acima (relevante em que as justificativas são mais aplicáveis ​​a C do que em C ++ moderno).
Tony Delroy
fonte
Bem, sim, mas novamente vi erros no código por pessoas que tentaram evitar religiosamente break. Eles o evitaram, mas não conseguiram pensar nas condições que substituíram o uso do intervalo.
Tomáš Zato - Restabelece Monica
5

É perfeitamente válido de usar break- como outros já apontaram, não está na mesma liga que goto.

Embora você queira usar a vFoundvariável quando quiser verificar fora do loop se o valor foi encontrado na matriz. Também do ponto de vista da capacidade de manutenção, ter uma bandeira comum sinalizando os critérios de saída pode ser útil.

Ás
fonte
5

Fiz algumas análises sobre a base de código em que estou trabalhando (40.000 linhas de JavaScript).

Encontrei apenas 22 breakafirmações, das quais:

  • 19 foram usadas dentro de switchinstruções (temos apenas 3 instruções de chave no total!).
  • 2 foram usados ​​dentro de forloops - um código que eu imediatamente classifiquei para ser refatorado em funções separadas e substituído por returndeclaração.
  • Quanto ao loop breakinterno final while... Corri git blamepara ver quem escreveu essa porcaria!

Então, de acordo com minhas estatísticas: Se breakfor usado fora deswitch , é um cheiro de código.

Eu também procurei por continuedeclarações. Não encontrou nenhum.

Rene Saarsoo
fonte
4

Não vejo nenhuma razão para que seja uma prática recomendada, desde que você queira concluir o processamento de STOP nesse momento.

Cornelius J. van Dyk
fonte
4

No mundo incorporado, há muito código por aí que usa a seguinte construção:

    while(1)
    { 
         if (RCIF)
           gx();
         if (command_received == command_we_are_waiting_on)
           break;
         else if ((num_attempts > MAX_ATTEMPTS) || (TickGet() - BaseTick > MAX_TIMEOUT))
           return ERROR;
         num_attempts++;
    }
    if (call_some_bool_returning_function())
      return TRUE;
    else
      return FALSE;

Este é um exemplo muito genérico, muitas coisas estão acontecendo atrás da cortina, interrompendo em particular. Não use isso como código padrão, estou apenas tentando ilustrar um exemplo.

Minha opinião pessoal é que não há nada errado em escrever um loop dessa maneira, desde que sejam tomados os devidos cuidados para evitar que ele permaneça indefinidamente.

Nate
fonte
1
Você poderia elaborar isso? Parece-me que o loop não faz absolutamente nada ... a menos que haja continueinstruções dentro da lógica do aplicativo. Existem?
Rene Saarsoo 13/10/10
1
As instruções continue são desnecessárias, pois você já está vinculado por um loop infinito. Basicamente, você executa a lógica necessária para atingir uma meta e, se essa meta não for alcançada em um número n de tentativas ou se uma condição de tempo limite expirar, você sai do loop por meio de uma construção de interrupção e executa uma ação corretiva ou informa o código de chamada de um erro. Eu vejo esse tipo de código frequentemente nos UCs ​​que se comunicam através de um barramento comum (RS-485 etc.). Você basicamente tenta apreender a linha e se comunicar sem colisões. Se ocorrerem colisões, você espera uma duração determinada por um número aleatório e tenta novamente.
Nate
1
Por outro lado, se tudo correr bem, você também usará a instrução break para sair assim que a condição for atendida. Nesse caso, o código após o loop é executado e todos estão felizes.
Nate
"if (call_some_bool_returning_function ()) retorna TRUE; else return FALSE;" pode ser apenas "return call_some_bool_returning_function ()" #
frankster
3

Depende do seu caso de uso. Existem aplicativos em que o tempo de execução de um loop for precisa ser constante (por exemplo, para satisfazer algumas restrições de tempo ou para ocultar seus dados internos de ataques baseados em tempo).

Nesses casos, fará sentido definir um sinalizador e somente verificar o valor do sinalizador APÓS todas as foriterações do loop terem sido executadas. Obviamente, todas as iterações de loop for precisam executar código que ainda leva aproximadamente o mesmo tempo.

Se você não se importa com o tempo de execução ... use break;e continue;para facilitar a leitura do código.

ndim
fonte
1
Se o tempo é importante, é melhor fazer algumas sonecas e economizar recursos do sistema do que permitir que o programa faça operações que são conhecidas como inúteis.
Bgs
2

Nas regras MISRA 98, usadas na minha empresa em C dev, a declaração de interrupção não deve ser usada ...

Edit: Break é permitido em MISRA '04

Benoît
fonte
2
Exatamente por que eu fiz essa pergunta! Não encontro nenhuma boa razão para que essa regra exista como uma diretriz de codificação. Além disso, como todo mundo aqui disse, quebre; parece melhor.
kiki
1
Não sei exatamente por que é proibido (uma pessoa mais inteligente do que eu pensava nisso ...), mas quebrar (e retorno múltiplo) é melhor para facilitar a leitura (na minha opinião, mas minhas opiniões não são regras corporativas ... )
Benoît
1
As regras do MISRA permitem o uso de declarações de quebra: misra.org.uk/forum/viewtopic.php?f=75&t=298
luis.espinal
Usamos as regras do MISRA 98 ... É exato que o MISRA 2004 altera as regras
Benoît
0

Claro, break;é a solução para interromper o loop for ou foreach. Eu usei em php em foreach e for loop e encontrei trabalhando.

gautamlakum
fonte
0

Discordo!

Por que você ignoraria a funcionalidade interna de um loop for para criar sua própria? Você não precisa reinventar a roda.

Eu acho que faz mais sentido ter seus cheques no topo do seu loop for, assim

for(int i = 0; i < myCollection.Length && myCollection[i].SomeValue != "Break Condition"; i++)
{
//loop body
}

ou se você precisar processar a linha primeiro

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
//loop body
}

Dessa forma, você pode escrever uma função para executar tudo e criar um código muito mais limpo.

for(int i = 0; i < myCollection.Length && (i == 0 ? true : myCollection[i-1].SomeValue != "Break Condition"); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

Ou, se sua condição for complicada, você também pode mudar esse código!

for(int i = 0; i < myCollection.Length && BreakFunctionCheck(i, myCollection); i++)
{
    DoAllThatCrazyStuff(myCollection[i]);
}

"Código profissional", repleto de pausas, não parece realmente um código profissional para mim. Parece codificação lenta;)

Biff MaGriff
fonte
4
codificação preguiçosa É código profissional :)
Jason
Só se não é uma dor na bunda para manter :)
Biff MaGriff
2
Eu costumo praticar "GTFO O MAIS CEDO POSSÍVEL". Ou seja, se eu puder sair de uma estrutura de forma limpa, devo fazê-lo o mais rápido possível e o mais próximo possível da condição que especifica a saída.
Chris Cudmore
Bem, isso também funciona. Acho que o argumento que estou tentando entender é que, se você estiver usando um loop for, não custa usar a funcionalidade incorporada para tornar seu código legível e modular. Se você absolutamente precisar de instruções de interrupção dentro do seu loop, eu sentaria e pensaria sobre por que esse nível de complexidade é necessário.
Biff MaGriff
1
Haha Considere o fato de que a maioria das pessoas que desencoraja breakargumenta que é para legibilidade do código. Agora olhe novamente para a louca condição de 8 km de comprimento nos loops acima ...
Tomáš Zato - Restabelece Monica