As `break` e` continue` são más práticas de programação?

191

Meu chefe continua mencionando indiferentemente que maus programadores usam breake continueem loops.

Eu os uso o tempo todo porque fazem sentido; deixe-me mostrar a inspiração:

function verify(object) {
    if (object->value < 0) return false;
    if (object->value > object->max_value) return false;
    if (object->name == "") return false;
    ...
}

O ponto aqui é que primeiro a função verifica se as condições estão corretas e depois executa a funcionalidade real. O mesmo se aplica aos loops da IMO:

while (primary_condition) {
    if (loop_count > 1000) break;
    if (time_exect > 3600) break;
    if (this->data == "undefined") continue;
    if (this->skip == true) continue;
    ...
}

Eu acho que isso facilita a leitura do & debug; mas também não vejo uma desvantagem.

Mikhail
fonte
2
Não é preciso muito para esquecer qual deles faz o quê.
57
Não. Nem é assim. Saber quando usá-los é a chave. Eles são ferramentas na caixa de ferramentas. Você os usa quando eles fornecem código claro e sucinto.
ORJ
67
Não posso expressar meu apoio a esse estilo de codificação com força suficiente. Vários níveis de condicionais aninhados são muito piores que essa abordagem. Normalmente, eu não sou militante sobre o estilo de codificação, mas isso é quase um negócio para mim.
Emil H
9
Obviamente, seu chefe não escreve código (suficiente). Se o fizesse, ele saberia que todas as palavras-chave (sim, até goto) são úteis em alguns casos.
Sakisk
57
Programadores ruins usam interrupção e continuam não significa que bons programadores não. Programadores ruins usam se e enquanto também.
Mouviciel

Respostas:

240

Quando usados ​​no início de um bloco, como as primeiras verificações feitas, eles agem como pré-condições, por isso é bom.

Quando usados ​​no meio do bloco, com algum código ao redor, eles agem como armadilhas ocultas, então é ruim.

Klaim
fonte
6
@Klaim: Pode-se argumentar que qualquer rotina que tenha vários pontos de saída é uma rotina mal fatorada. Uma rotina devidamente fatorada deve fazer uma coisa e apenas uma coisa.
bit-twiddler
79
@ bit-twiddler: esta é uma mentalidade muito C-ish. Uma variável temporária pode ser modificada posteriormente, para que um único erro de digitação a 20 linhas daqui em diante possa apagar esse resultado cuidadosamente criado. Um retorno imediato (ou interrupção ou continuação), no entanto, é extremamente claro: posso parar de ler agora porque sei que não é possível modificá- lo mais adiante . É bom para o meu pequeno cérebro, realmente facilita a busca pelo código.
Matthieu M.
15
@ Matthieu eu concordo. Saia de um bloco quando receber um resultado que satisfaça a finalidade do bloco.
Evan Plaice
8
@ bit-twiddler - o princípio do ponto de saída único é separado do princípio de responsabilidade única. Não concordo que a verificação seja sempre separada da responsabilidade única do WRT. De fato, "responsabilidade única" sempre me parece um termo subjetivo. Por exemplo, em um solucionador quadrático, o cálculo do discriminante deve ser uma responsabilidade separada? Ou toda a fórmula quadrática pode ser de uma única responsabilidade? Eu diria que depende se você tem um uso separado para o discriminante - caso contrário, tratá-lo como uma responsabilidade separada provavelmente é excessivo.
Steve314
10
Essa resposta é uma regra de ouro, não uma regra difícil. Funciona na maioria dos casos, sinta-se à vontade para quebrá-lo se fizer sentido no seu contexto.
Klaim
87

Você pode ler o artigo de Donald Knuth sobre Programação Estruturada, de 1974 , em Statements , no qual ele discute vários usos dos go toestruturalmente desejáveis. Eles incluem o equivalente de breake continueinstruções (muitos dos usos de go tolá foram desenvolvidos em construções mais limitadas). Seu chefe é do tipo que chama Knuth de um mau programador?

(Os exemplos dados me interessam. Normalmente, breake continuenão são apreciados por pessoas que gostam de uma entrada e uma saída de qualquer parte do código, e esse tipo de pessoa também desaprova várias returndeclarações.)

David Thornley
fonte
8
A maioria das pessoas que gosta de funções e procedimentos com pontos de entrada e saída únicos cresceu em Pascal. Pascal não foi o primeiro idioma que aprendi, mas teve um impacto profundo na forma como estruturo o código até hoje. As pessoas sempre comentam como é fácil ler meu código Java. Isso ocorre porque evito vários pontos de saída, além de misturar declarações com o código. Eu tento o meu melhor para declarar todas as variáveis ​​locais usadas em um método na parte superior do método. Essa prática evita divagar códigos forçando-me a manter métodos sucintos.
bit-twiddler
5
Pascal também tinha funções aninhadas. Apenas dizendo ...
Shog9
6
Pelo que me lembro, na época, a principal razão pela qual as pessoas não gostavam de várias instruções de retorno em funções era porque os depuradores não as tratavam adequadamente. Foi realmente difícil definir um ponto de interrupção no final de uma função, mas você nunca o atingiu devido a uma declaração de retorno anterior. Para cada compilador que uso hoje em dia, isso não é mais um problema.
Dunk
28
@ bit-twiddler: Não tenho tanta certeza disso. Ainda estou usando Pascal hoje e geralmente considero "saída única entrada única" ou pelo menos a parte de saída única como programação de culto à carga. Eu só considerar Break, Continuee Exitcomo ferramentas em minha caixa de ferramentas; Eu os uso onde eles facilitam o acompanhamento do código e não os uso onde tornaria as coisas mais difíceis de ler.
Mason Wheeler
2
@ bit-twiddler: amém a isso. Acrescentarei também que, quando você chegar aos blocos que cabem facilmente na tela, vários pontos de saída se tornam muito menos problemáticos.
Shog9
44

Eu não acredito que eles são ruins. A idéia de que eles são ruins vem dos dias da programação estruturada. Está relacionado à noção de que uma função deve ter um único ponto de entrada e um único ponto de saída, ou seja, apenas um returnpor função.

Isso faz algum sentido se sua função for longa e se você tiver vários loops aninhados. No entanto, suas funções devem ser curtas e você deve agrupar loops e seus corpos em funções curtas próprias. Geralmente, forçar uma função a ter um único ponto de saída pode resultar em lógica muito complicada.

Se sua função for muito curta, se você tiver um único loop ou, no máximo, dois loops aninhados, e se o corpo do loop for muito curto, ficará muito claro o que um breakou um continuefaz. Também está claro o que várias returndeclarações fazem.

Esses problemas são abordados em "Código Limpo", de Robert C. Martin e em "Refatoração", de Martin Fowler.

Dima
fonte
12
"Reduza suas funções. Reduza-as" - Robert C. Martin. Eu descobri que isso funciona surpreendentemente bem. Toda vez que você vir um bloco de código em uma função que precisa de um comentário explicando o que ele faz, envolva-o em uma função separada com um nome descritivo. Mesmo que sejam apenas algumas linhas, e mesmo que seja usado apenas uma vez. Essa prática elimina a maioria dos problemas com interrupção / continuação ou retornos múltiplos.
Dima
2
@ Mikhail: A complexidade ciclomática geralmente está fortemente correlacionada com o SLOC, o que significa que o conselho pode ser simplificado para "não escrever funções longas".
John R. Strohm
3
A ideia de um único ponto de saída é amplamente mal interpretada. Era uma vez, as funções não precisavam retornar o chamador. Eles poderiam voltar para outro local. Isso geralmente era feito em linguagem assembly. Fortran tinha uma construção especial para isso; você poderia passar um número de instrução precedido por e comercial CALL P(X, Y, &10)e, em caso de erro, a função poderia passar o controle para essa instrução, em vez de retornar ao ponto da chamada.
Kevin cline #
@kevincline como visto com Lua, por exemplo.
Qix
11
@cjsimon você conseguiu. Não apenas as funções devem ser pequenas. As aulas também devem ser pequenas.
Dima
39

Os programadores ruins falam em absolutos (assim como Sith). Bons programadores usam a solução mais clara possível ( todas as outras coisas são iguais ).

Usar break e continue com frequência dificulta o código. Mas se a substituição deles tornar o código ainda mais difícil de seguir, será uma mudança ruim.

O exemplo que você deu é definitivamente uma situação em que os intervalos e continuações devem ser substituídos por algo mais elegante.

Matthew Read
fonte
Sim, eu exagerei no número de condições para fornecer exemplos de casos para sair.
22811 Mikhail
9
Qual é um exemplo do código de substituição que você sugeriu? Eu pensei que era um exemplo bastante razoável de declarações de guarda.
Simpatico # 5/15
Parece-me que "a solução mais clara possível" é sempre ... possível? Como não poderia haver uma solução "mais clara"? Mas então, eu não sou a favor de absolutos, então talvez você esteja certo.
@nocomprende Não sei ao certo o que você está falando. O "possível" aqui não indica que a melhor solução não existe - apenas a clareza perfeita e definitiva não é uma coisa. Afinal, é subjetivo.
Matthew Leia
22

A maioria das pessoas pensa que é uma má ideia porque o comportamento não é facilmente previsível. Se você está lendo o código e vê while(x < 1000){}que presume que ele funcionará até x> = 1000 ... Mas se houver interrupções no meio, isso não será verdadeiro, então você não pode realmente confiar no seu looping ...

É a mesma razão pela qual as pessoas não gostam do GOTO: com certeza, ele pode ser usado bem, mas também pode levar a um código espaguete, onde o código salta aleatoriamente de seção para seção.

Para mim, se eu fosse fazer um loop que quebrasse em mais de uma condição, eu while(x){}alternaria X para false quando precisasse sair. O resultado final seria o mesmo, e qualquer pessoa que lesse o código saberia examinar mais de perto as coisas que alteravam o valor de X.

Satanicpuppy
fonte
2
+1 muito bem dito e +1 (se eu pudesse fazer outro) pela while(notDone){ }abordagem.
FrustratedWithFormsDesigner
5
Mikhail: O problema da quebra é que a condição final do loop nunca é simplesmente declarada em um só lugar. Isso dificulta a previsão da pós-condição após o loop. Nesse caso trivial (> = 1000), não é difícil. Adicione muitas instruções if e diferentes níveis de aninhamento, pode tornar-se muito, muito difícil determinar a pós-condição do loop.
S.Lott
S.Lott acertou a unha diretamente na cabeça. A expressão que controla a iteração deve incluir todas as condições que devem ser atendidas para continuar a iteração.
bit-twiddler
6
Substituir break;por x=false;não torna seu código mais claro. Você ainda precisa procurar essa afirmação no corpo. E no caso de x=false;você ter que verificar se não está x=true;mais longe.
precisa
14
Quando as pessoas dizem "eu vejo xe eu assumo y, mas se você fizer z essa suposição não é válida", eu tendem a pensar ", então não faça essa suposição estúpida". Muitas pessoas simplificariam isso para "quando vejo while (x < 1000)que suponho que ele será executado 1000 vezes". Bem, há muitas razões pelas quais isso é falso, mesmo que xseja inicialmente zero. Por exemplo, quem disse que xé incrementado precisamente uma vez durante o loop e nunca modificado de outra maneira? Mesmo para sua própria suposição, apenas porque algo é definido x >= 1000não significa que o loop será encerrado - ele pode voltar ao intervalo antes que a condição seja verificada.
Steve314
14

Sim, você pode [re] escrever programas sem instruções de interrupção (ou retorna do meio dos loops, que fazem a mesma coisa). Mas pode ser necessário introduzir variáveis ​​adicionais e / ou duplicação de código, as quais normalmente dificultam a compreensão do programa. Pascal (a linguagem de programação) era muito ruim, especialmente para programadores iniciantes por esse motivo. Seu chefe basicamente quer que você programe nas estruturas de controle de Pascal. Se Linus Torvalds estivesse no seu lugar, ele provavelmente mostraria ao seu chefe o dedo médio!

Existe um resultado de ciência da computação chamado hierarquia de estruturas de controle de Kosaraju, que remonta a 1973 e é mencionado no famoso artigo de Knuth (mais) sobre gotos de 1974. (Este artigo de Knuth já foi recomendado acima por David Thornley, a propósito) .) O que S. Rao Kosaraju provou em 1973 é que não é possível reescrever todos os programas com quebras de profundidade em vários níveis n em programas com profundidade de quebra menor que n sem a introdução de variáveis ​​extras. Mas digamos que seja apenas um resultado puramente teórico. (Basta adicionar algumas variáveis ​​extras ?! Certamente você pode fazer isso para agradar seu chefe ...)

O que é muito mais importante do ponto de vista da engenharia de software é um artigo mais recente de Eric S. Roberts, de 1995, intitulado Saídas em loop e programação estruturada: reabrindo o debate ( http://cs.stanford.edu/people/eroberts/papers/SIGCSE- 1995 / LoopExits.pdf ). Roberts resume vários estudos empíricos conduzidos por outros antes dele. Por exemplo, quando um grupo de estudantes do tipo CS101 foi convidado a escrever código para uma função que implementava uma pesquisa seqüencial em uma matriz, o autor do estudo disse o seguinte sobre os alunos que usaram um intervalo / retorno / ir para sair do o loop de pesquisa sequencial quando o elemento foi encontrado:

Ainda não encontrei uma pessoa que tentou um programa usando [esse estilo] que produziu uma solução incorreta.

Roberts também diz que:

Os alunos que tentaram resolver o problema sem usar um retorno explícito do loop for se saíram muito menos: apenas sete dos 42 alunos que tentaram essa estratégia conseguiram gerar soluções corretas. Esse número representa uma taxa de sucesso inferior a 20%.

Sim, você pode ser mais experiente do que os alunos do CS101, mas sem usar a instrução break (ou retornar / sair equivalentemente do meio dos loops), eventualmente você escreverá um código que, embora seja bem estruturado nominalmente, seja cabeludo o suficiente em termos de lógica extra variáveis ​​e duplicação de código que alguém, provavelmente você mesmo, colocará erros de lógica ao tentar seguir o estilo de codificação do seu chefe.

Também vou dizer aqui que o artigo de Roberts é muito mais acessível para o programador médio, para uma leitura melhor do que a de Knuth. Também é mais curto e cobre um tópico mais restrito. Você provavelmente poderia até recomendar ao seu chefe, mesmo que ele seja o gerente e não o tipo de CS.

usuário3588161
fonte
2
Embora a programação estruturada tenha sido minha abordagem por muitos anos, nos últimos poucos passamos a usar inteiramente saídas explícitas na primeira oportunidade possível. Isso torna a execução mais rápida e quase elimina os erros lógicos que costumavam resultar em loops sem fim.
DocSalvager
9

Não considero usar nenhuma dessas práticas ruins, mas usá-las demais no mesmo loop deve garantir uma repensar a lógica usada no loop. Use-os com moderação.

Bernard
fonte
:) sim, o exemplo que eu forneci era conceitual
Mikhail
7

O exemplo que você deu não precisa de pausas nem continua:

while (primary-condition AND
       loop-count <= 1000 AND
       time-exec <= 3600) {
   when (data != "undefined" AND
           NOT skip)
      do-something-useful;
   }

Meu 'problema' com as 4 linhas do seu exemplo é que elas estão todas no mesmo nível, mas fazem coisas diferentes: algumas quebras, outras continuam ... Você precisa ler cada linha.

Na minha abordagem aninhada, quanto mais profundo você for, mais 'útil' o código se tornará.

Mas, se no fundo você encontrasse um motivo para interromper o loop (que não seja a condição primária), uma quebra ou retorno teria seu uso. Eu preferiria isso do que usar um sinalizador extra que deve ser testado na condição de nível superior. O intervalo / retorno é mais direto; é melhor indicar a intenção do que definir mais uma variável.

Peter Frings
fonte
+1 Mas, na verdade seus <comparações precisa ser <=para combinar a solução PO
El Ronnoco
3
Na maioria dos casos, se alguém é tão profundo que precisa usar interrupção / retorno para gerenciar o controle de fluxo, sua função / método é muito complexo.
bit-twiddler
6

A "maldade" depende de como você as usa. Normalmente, uso SOMENTE interrupções nas construções de loop quando isso me salva de ciclos que não podem ser salvos através da refatoração de um algoritmo. Por exemplo, percorrer uma coleção procurando um item com um valor em uma propriedade específica definida como true. Se tudo o que você precisa saber é que um dos itens teve essa propriedade configurada como verdadeira, quando você atingir esse resultado, é bom fazer uma pausa para finalizar o loop adequadamente.

Se o uso de uma interrupção não tornar o código mais fácil de ler, mais curto para executar ou salvar ciclos de processamento de maneira significativa, é melhor não usá-los. Costumo codificar para o "menor denominador comum" sempre que possível, para garantir que qualquer pessoa que me siga possa olhar facilmente para o meu código e descobrir o que está acontecendo (nem sempre sou bem-sucedido nisso). As quebras reduzem isso porque introduzem pontos de entrada / saída ímpares. Quando mal utilizados, eles podem se comportar como uma declaração "goto" fora de sintonia.

Joel Etherton
fonte
Eu concordo com os dois pontos! Quanto ao seu segundo ponto - acho que é mais fácil seguir minha postagem original, porque ela parece em inglês. Se você tem uma combinação de condições na instrução 1 if, é quase uma decifração descobrir o que deve acontecer para que o if seja executado true.
Mikhail
@ Mikhail: As amostras que você forneceu são um pouco altruístas para realismo específico. A meu ver, essas amostras são claras, concisas e fáceis de ler. A maioria dos loops não é assim. A maioria dos loops possui algum outro tipo de lógica que eles estão executando e condicionais potencialmente muito mais complicados. É nesses casos que interromper / continuar pode não ser o melhor uso, porque altera a lógica quando lida.
Joel Etherton
4

Absolutamente não ... Sim, o uso de gotoé ruim porque deteriora a estrutura do seu programa e também é muito difícil entender o fluxo de controle.

Mas o uso de declarações como breake continueé absolutamente necessário hoje em dia e não é considerado uma má prática de programação.

E também não é tão difícil de entender o fluxo de controle no uso de breake continue. Em construções como switcha breakafirmação é absolutamente necessária.

Radheshyam Nayak
fonte
2
Não uso "continue" desde que aprendi C em 1981. É um recurso de linguagem desnecessário, pois o código ignorado pela instrução continue pode ser envolvido por uma instrução de controle condicional.
bit-twiddler
12
Eu prefiro usar continue nesses casos, pois garante que meu código não se torne código de seta. Eu odeio código de seta mais do que ir para instruções do tipo. Também li como "se essa afirmação for verdadeira, pule o restante desse loop e continue com a próxima iteração". Muito útil quando está no início de um loop for (menos útil nos loops while).
jsternberg
@jsternberg Pela vitória! :-)
Notinlist 26/09
3

A noção essencial vem de poder analisar semanticamente seu programa. Se você tiver uma única entrada e uma única saída, a matemática necessária para indicar possíveis estados é consideravelmente mais fácil do que se você tiver que gerenciar caminhos de bifurcação.

Em parte, essa dificuldade reflete a possibilidade de raciocinar conceitualmente sobre seu código.

Francamente, seu segundo código não é óbvio. O que isso está fazendo? Continua 'continua' ou 'segue' o loop? Eu não faço ideia. Pelo menos o seu primeiro exemplo é claro.

Paul Nathan
fonte
Quando eu trabalho em um projeto que o gerente obriga a usá-los, eu tive que usar um fluxograma para lembrar o seu uso, e os vários caminhos de saída, fez código th mais confuso ...
umlcat
seu comentário "sincero" é sintático - "próximo" é uma coisa perl; línguas normais usar "continuar" para significar "pular este laço"
Mikhail
@ Mik, talvez "pular esta iteração" seria uma descrição melhor. Atenciosamente, Dr. IM Pedantic
Pete Wilson
@Mikhail: Claro. Mas quando se trabalha em vários idiomas, isso pode levar a erros quando a sintaxe não é comum entre os idiomas.
Paul Nathan
Mas matemática não é programação. O código é mais expressivo que a matemática. Eu obter que de entrada única / única saída pode fazer fluxogramas olhar mais agradável, mas a que custo (Ie., Onde pausa / retorno pode tornar o código um melhor desempenho)?
Evan Plaice
3

Eu substituiria seu segundo trecho de código por

while (primary_condition && (loop_count <= 1000 && time_exect <= 3600)) {
    if (this->data != "undefined" && this->skip != true) {
        ..
    }
}

não por qualquer motivo de concisão - na verdade, acho que é mais fácil ler e alguém entender o que está acontecendo. De um modo geral, as condições para seus loops devem estar contidas apenas nas condições de loop que não estão espalhadas por todo o corpo. No entanto, existem algumas situações em que breake continuepodem ajudar na legibilidade. breakmais do que continueeu poderia acrescentar: D

El Ronnoco
fonte
Embora eu discorde de "Eu realmente acho que isso é mais fácil de ler", isso realiza exatamente o mesmo objetivo que o código acima. Eu realmente nunca pensei em 'quebrar' como um operador de curto-circuito até agora, mas faz todo o sentido. Quanto a: "De um modo geral, as condições para seus loops devem estar contidas apenas nessas condições de loop", o que você faz quando processa um loop foreach, pois não há realmente nenhuma maneira de inserir lógica condicional na definição de loop?
Evan Plaice 26/03
@Evan A condição não é aplicável a um foreachloop, pois isso itera todos os itens de uma coleção. Um forloop é semelhante, pois não deve ter um ponto final condicional. Se você precisar de um ponto final condicional, precisará usar um whileloop.
El Ronnoco
11
@Evan, no entanto, entendo o seu ponto - ou seja, 'e se você precisar sair de um loop foreach?' - Bem, breakna minha opinião , deve haver apenas um máximo.
El Ronnoco
2

Eu discordo do seu chefe. Existem locais apropriados para breake continueserem usados. De fato, a razão pela qual as exceções e o tratamento de exceções foram introduzidos nas linguagens de programação modernas é que você não pode resolver todos os problemas usando apenas structured techniques.

Em uma nota lateral , não quero iniciar uma discussão religiosa aqui, mas você pode reestruturar seu código para ficar ainda mais legível assim:

while (primary_condition) {
    if (loop_count > 1000) || (time_exect > 3600) {
        break;
    } else if ( ( this->data != "undefined") && ( !this->skip ) ) {
       ... // where the real work of the loop happens
    }
}

Em outra nota lateral

Pessoalmente, não gosto do uso de ( flag == true )in condicionais, porque se a variável já é booleana, você está introduzindo uma comparação adicional que precisa acontecer quando o valor do booleano tiver a resposta que você deseja - a menos que você tenha certeza de que seu compilador otimizar essa comparação extra de distância.

Zeke Hansell
fonte
Há anos que discuto essa questão. Seu jeito ('if (flag) {...}') é muito mais conciso e talvez pareça mais 'profissional' ou 'especialista'. Mas, você sabe, muitas vezes significa que um leitor / mantenedor precisa interromper-se brevemente para lembrar o que a construção significa; e ter certeza do sentido do teste. Atualmente, estou usando 'if (flag == true) {...}' apenas porque parece ser uma documentação melhor. Próximo mês? Quien sabe?
Pete Wilson
2
@Pete - O termo não é conciso, mas elegante . Você pode waffle tudo o que quiser, mas se estiver preocupado que o leitor / mantenedor não entenda o que booleané ou o que a terminologia concisa / elegante significa é, talvez seja melhor contratar alguns mantenedores mais inteligentes ;-)
Zeke Hansell
@Pete, também mantenho minha declaração sobre o código gerado. Você está fazendo mais uma comparação comparando um sinalizador com um valor constante antes de avaliar o valor booleano da expressão. Por que tornar mais difícil do que deveria ser, a variável flag já tem o valor que você deseja!
Zeke Hansell
+1 bom trabalho. Definitivamente, muito mais elegante que o exemplo anterior.
Evan Plaice
2

Eu concordo com o seu chefe. Eles são ruins porque produzem métodos com alta complexidade ciclomática. Tais métodos são difíceis de ler e difíceis de testar. Felizmente, há uma solução fácil. Extraia o corpo do loop em um método separado, onde o "continue" se tornará "retorno". "Retorno" é melhor porque depois que o "retorno" acaba - não há preocupações com o estado local.

Para "break", extraia o próprio loop em um método separado, substituindo "break" por "return".

Se os métodos extraídos exigirem um grande número de argumentos, isso é uma indicação para extrair uma classe - colete-os em um objeto de contexto.

Kevin Cline
fonte
0

Eu acho que é apenas um problema quando aninhado profundamente dentro de vários loops. É difícil saber em qual loop você está quebrando. Pode ser difícil seguir uma continuação também, mas acho que a verdadeira dor vem das quebras - a lógica pode ser difícil de seguir.

davidhaskins
fonte
2
Na verdade, é fácil ver se você recua adequadamente, tem a semântica dessas instruções internalizada e não está com muito sono.
@delnan - que é um monte de suposições;)
davidhaskins
2
Sim, especialmente o último.
Michael K
Bem, nº 1 é necessário para programação séria de qualquer maneira, # 2 é de se esperar de todos chamavam programador, e # 3 é bastante útil em geral;)
Algumas linguagens (apenas scripts que eu conheço) suportam break 2; para outros, eu acho bandeiras bool temporários são usados
Mikhail
0

Contanto que eles não sejam usados ​​como saltos disfarçados, como no exemplo a seguir:

do
{
      if (foo)
      {
             /*** code ***/
             break;
      }

      if (bar)
      {
             /*** code ***/
             break;
      }
} while (0);

Eu estou bem com eles. (Exemplo visto no código de produção, meh)

SuperBloup
fonte
Você recomendaria para casos como esses criar funções?
Mikhail
Sim e, se não for possível, vá direto. Quando vejo um fazer, penso repetição, não contexto para simular salto.
SuperBloup 29/03
oh eu concordo, eu só estava me perguntando como você faria isso :) Os cientistas da computação fazê-lo repetidamente
Mikhail
0

Eu não gosto de nenhum desses estilos. Aqui está o que eu preferiria:

function verify(object)
{
    if not (object->value < 0) 
       and not(object->value > object->max_value)
       and not(object->name == "") 
       {
         do somethign important
       }
    else return false; //probably not necessary since this function doesn't even seem to be defined to return anything...?
}

Eu realmente não gosto de usar returnpara abortar uma função. Parece um abuso de return.

O uso breaktambém nem sempre é claro para a leitura.

Melhor ainda pode ser:

notdone := primarycondition    
while (notDone)
{
    if (loop_count > 1000) or (time_exect > 3600)
    {
       notDone := false; 
    }
    else
    { 
        skipCurrentIteration := (this->data == "undefined") or (this->skip == true) 

        if not skipCurrentIteration
        {
           do something
        } 
        ...
    }
}

menos aninhamento e as condições complexas são refatoradas em variáveis ​​(em um programa real, você teria que ter nomes melhores, obviamente ...)

(Todo o código acima é pseudo-código)

FrustratedWithFormsDesigner
fonte
11
Você realmente prefere 3 níveis de aninhamento sobre o que eu digitei acima?
21411 Mikhail
@ Mikhail: Sim, ou eu atribuiria o resultado da condição a uma variável. Acho muito mais fácil entender do que a lógica de breake continue. Anormalmente, terminar um loop parece estranho e eu não gosto.
FrustratedWithFormsDesigner
11
Ironicamente, você interpretou mal minhas condições. continuesignifica pular a funcionalidade, vá para o próximo loop; não "continue com a execução"
Mikhail
@Mikhail: Ah. Não o uso frequentemente e quando leio, fico confuso com o significado. Outra razão pela qual eu não gosto. : P Dê-me um minuto para atualizar ...
FrustratedWithFormsDesigner
7
Aninhamento em excesso destrói a legibilidade. E, às vezes, evitando a ruptura / continuar introduz a necessidade de inverter a sua lógica em seus testes condicionais, o que pode levar a erros de interpretação do que o seu código está fazendo - eu só estou dizendo
Zeke Hansell
0

Não. É uma maneira de resolver um problema e há outras maneiras de resolvê-lo.

Muitas linguagens mainstream atuais (Java, .NET (C # + VB), PHP, escreva você mesmo) usam "break" e "continue" para pular loops. Ambos sentenciaram "goto (s) estruturados".

Sem eles:

String myKey = "mars";

int i = 0; bool found = false;
while ((i < MyList.Count) && (not found)) {
  found = (MyList[i].key == myKey);
  i++;   
}
if (found)
  ShowMessage("Key is " + i.toString());
else
  ShowMessage("Not found.");

Com eles:

String myKey = "mars";

for (i = 0; i < MyList.Count; i++) {
  if (MyList[i].key == myKey)
    break;
}
ShowMessage("Key is " + i.toString());

Observe que o código "break" e "continue" é mais curto e geralmente transforma "while" sentenças em sentenças "for" ou "foreach".

Ambos os casos são uma questão de estilo de codificação. Eu prefiro não usá-los , porque o estilo detalhado me permite ter mais controle do código.

Na verdade, eu trabalho em alguns projetos, onde era obrigatório usar essas frases.

Alguns desenvolvedores podem pensar que não são necesarilly, mas hipotéticos, se tivéssemos que removê-los, temos que remover "while" e "do while" ("repita até", pessoal do pascal) também ;-)

Conclusão, mesmo se eu preferir não usá-los, acho que é uma opção, não uma má prática de programação.

umlcat
fonte
Desculpe por ser exigente, mas seu segundo exemplo está faltando na saída quando a chave não é encontrada (é claro que parece muito mais curta).
FrustratedWithFormsDesigner
2
para não mencionar que o primeiro exemplo só funciona se a chave for a última da lista.
Mikhail
@FrustratedWithFormsDesigner EXATAMENTE. Coloquei purpouse para denotar por isso que é preferível que o método ;-)
umlcat
no entanto, você tem duas rotinas com semântica diferente; portanto, eles não são logicamente equivalentes.
bit-twiddler
2
seu segundo exemplo tem dois erros, um sintático e outro lógico. 1. Ele não será compilado porque o iterador não está declarado fora do escopo do loop for (portanto, não está disponível na saída da string). 2. Mesmo que o iterador tenha sido declarado fora do loop, se a chave não for encontrada na coleção, a saída da string imprimirá a chave do último item da lista.
Evan Plaice
0

Eu não sou contra continuee breakem princípio, mas acho que são construções de nível muito baixo que muitas vezes podem ser substituídas por algo ainda melhor.

Estou usando C # como exemplo aqui, considere o caso de querer iterar sobre uma coleção, mas queremos apenas os elementos que atendem a algum predicado e não queremos fazer mais do que 100 iterações.

for (var i = 0; i < collection.Count; i++)
{
    if (!Predicate(item)) continue;
    if (i >= 100) break; // at first I used a > here which is a bug. another good thing about the more declarative style!

    DoStuff(item);
}

Isso parece razoavelmente limpo. Não é muito difícil de entender. Eu acho que valeria muito a pena ser mais declarativo. Compare com o seguinte:

foreach (var item in collection.Where(Predicate).Take(100))
    DoStuff(item);

Talvez as chamadas Onde e Receber não devam estar nesse método. Talvez essa filtragem deva ser feita ANTES que a coleção seja passada para esse método. De qualquer forma, afastando-se de coisas de baixo nível e concentrando-se mais na lógica de negócios real, fica mais claro o que realmente interessa. Torna-se mais fácil separar nosso código em módulos coesos que aderem mais às boas práticas de design e, portanto, em.

Ainda existem coisas de baixo nível em algumas partes do código, mas queremos ocultar o máximo possível, porque é preciso energia mental que poderíamos estar usando para raciocinar sobre os problemas de negócios.

kai
fonte
-1

O Code Complete tem uma boa seção sobre o uso gotoe vários retornos da rotina ou loop.

Em geral, não é uma prática ruim. breakou continuediga exatamente o que acontece a seguir. E eu concordo com isso.

Steve McConnell (autor do Code Complete) usa quase os mesmos exemplos que você para mostrar vantagens de usar várias gotoinstruções.

No entanto, o uso excessivo breakou continuepode levar a software complexo e insustentável.

Grzegorz Gierlik
fonte