Isso justifica declarações goto?


15

Me deparei com essa pergunta um segundo atrás e estou retirando parte do material: existe um nome para a construção 'break n'?

Essa parece ser uma maneira desnecessariamente complexa para as pessoas terem de instruir o programa a interromper um loop for aninhado duas vezes:

for (i = 0; i < 10; i++) {
    bool broken = false;
    for (j = 10; j > 0; j--) {
        if (j == i) {
            broken = true;
            break;
        }
    }
    if (broken)
        break;
}

Eu sei que os livros didáticos gostam de dizer que as declarações goto são o diabo, e eu não gosto muito delas, mas isso justifica uma exceção à regra?

Estou procurando respostas que lidem com n-aninhados para loops.

NOTA: Se você responder sim, não ou em algum lugar, respostas completamente fechadas não são bem-vindas. Especialmente se a resposta for não, forneça uma boa razão legítima (que não está muito longe dos regulamentos do Stack Exchange).


2
Se você puder escrever a condição do loop interno for (j = 10; j > 0 && !broken; j--), não precisará da break;instrução. Se você mover a declaração de brokenpara antes do início do loop externo, se isso puder ser escrito como for (i = 0; i < 10 && !broken; i++)eu acho que não break; é necessário.
FrustratedWithFormsDesigner

8
Dado o exemplo é de C # - a referência de C # em goto: goto (Referência de C #) - "A instrução goto também é útil para sair de loops profundamente aninhados."

OMG, eu nunca soube que o c # tinha uma declaração goto! O que você aprende no StackExchange. (Não me lembro da última vez que desejei uma capacidade de declaração goto, ela parece nunca aparecer).
John Wu

6
IMHO realmente depende da sua "religião de programação" e da fase da lua atual. Para mim (e para a maioria das pessoas ao meu redor), é bom usar um goto para sair de ninhos profundos se o loop for pequeno e a alternativa seria uma verificação supérflua de uma variável bool introduzida apenas para que a condição do loop se rompa. Isso é mais importante quando você deseja sair do meio do corpo ou quando, após o loop mais interno, existe algum código que também precisa verificar o bool.
PlasmaHH

1
@JohnWu gotoé realmente necessário em um comutador C # para passar para outro caso depois de executar qualquer código dentro do primeiro caso. Esse é o único caso que usei goto, no entanto.
Dan Lyons

Respostas:


33

A aparente necessidade de uma declaração de aceitação surge da escolha de expressões condicionais ruins para os loops .

Você declara que deseja que o loop externo continue por mais tempo i < 10e o mais interno continue por tanto tempo j > 0.

Mas, na realidade, não era isso que você queria , você simplesmente não disse aos loops a condição real que queria que eles avaliassem, então você quer resolvê-lo usando break ou goto.

Se você disser aos loops suas verdadeiras intenções, para começar , não há necessidade de pausas ou ir.

bool alsoThis = true;
for (i = 0; i < 10 && alsoThis; i++)
{    
    for (j = 10; j > 0 && alsoThis; j--)
    {
        if (j == i)
        {
            alsoThis = false;
        }
    }
}

Veja meus comentários sobre as outras respostas. O código original é impresso ino final do loop externo, portanto é importante um curto-circuito no incremento para tornar o código 100% equivalente. Eu não estou dizendo que nada na sua resposta está errada, eu só queria salientar que quebrar o loop imediatamente foi um aspecto importante do código.
Pswg

1
@pswg Não vejo nenhum código sendo impresso ino final do loop externo na pergunta do OP.
Tulains Córdova

O OP referenciou o código da minha pergunta aqui , mas deixou de fora essa parte. Eu não te votei, BTW. A resposta está totalmente correta em relação à escolha correta das condições do loop.
Pswg

3
@pswg Eu acho que se você realmente quer justificar um goto, pode resolver um problema que precisa de um, por outro lado, estou programando profissionalmente há duas décadas e não tenho um único problema do mundo real que precise 1.
Tulains Córdova

1
O objetivo do código não era fornecer um caso de uso válido para goto(tenho certeza de que existem outros melhores), mesmo que ele tenha gerado uma pergunta que parece usá-lo como tal, mas ilustrando a ação básica de break(n)linguagens que não ' Não apóie.
Pswg

34

Nesse caso, você pode refatorar o código em uma rotina separada e depois apenas returnno loop interno. Isso negaria a necessidade de romper o loop externo:

bool isBroken() {
    for (i = 0; i < 10; i++)
        for (j = 10; j > 0; j--)
            if (j == i) return true;

    return false;
}

2
O que quer dizer que sim, o que você mostra é uma maneira desnecessariamente complexa de romper com loops aninhados, mas não, existe uma maneira de refatorar que não torna as coisas mais atraentes.
6114 Roger Roger

2
Apenas uma observação: no código original, ele é impresso iapós o final do loop externo. Então, para realmente refletir que ié o valor importante, aqui o return true;e return false;deve ser tanto return i;.
PSWG

O +1 estava prestes a postar esta resposta e acredito que essa deve ser a resposta aceita. Não acredito que a resposta aceita tenha sido aceita porque esse método funciona apenas para um subconjunto específico de algoritmos de loop aninhado e não necessariamente torna o código muito mais limpo. O método nesta resposta funciona geralmente.
Ben Lee

6

Não, não acho que gotoseja necessário. Se você escrever assim:

bool broken = false;
saved_i = 0;
for (i = 0; i < 10 && !broken; i++)
{
    broken = false;
    for (j = 10; j > 0 && !broken; j--)
    {
        if (j == i)
        {
            broken = true;
            saved_i = i;
        }
    }
}

Tendo &&!brokenem ambos os loops, você pode sair dos dois loops quando i==j.

Para mim, essa abordagem é preferível. As condições de loop são extremamente claros: i<10 && !broken.

Uma versão de trabalho pode ser vista aqui: http://rextester.com/KFMH48414

Código:

public static void main(String args[])
{
    int i=0;
    int j=0;
    boolean broken = false;
    for (i = 0; i < 10 && !broken; i++)
    {
        broken = false;
        for (j = 10; j > 0 && !broken; j--)
        {
            if (j == i)
            {
                broken = true;
                System.out.println("loop breaks when i==j=="+i);
            }
        }
    }
    System.out.println(i);
    System.out.println(j);
}

Resultado:

o loop quebra quando i == j == 1
2
0 0

Por que você se usar brokenem seu primeiro exemplo em tudo ? Basta usar a pausa no loop interno. Além disso, se você absolutamente deve usá-lo broken, por que declará-lo fora dos loops? Apenas declare-o dentro do iloop. Quanto ao whileloop, por favor, não use isso. Sinceramente, acho que ele consegue ser ainda menos legível do que a versão goto.
luiscubal

@luiscubal: Uma variável chamada brokené usada porque eu não gosto de usar a breakinstrução (exceto no caso de troca, mas é isso). É declarado fora do loop externo, caso você queira interromper TODOS os loops quando i==j. Você está certo, em geral, brokensó precisa ser declarado antes do loop mais externo que ele irá quebrar.
FrustratedWithFormsDesigner

Na sua atualização, i==2no final do ciclo. A condição de sucesso também deve causar um curto-circuito no incremento para que seja 100% equivalente a a break 2.
PSWG

2
Pessoalmente, prefiro broken = (j == i)o if e, se a linguagem permitir, colocar a declaração quebrada dentro da inicialização do loop externo. Embora seja difícil dizer essas coisas para este exemplo específico, como o loop inteiro é um não-op (ele não retorna nada e não tem efeitos colaterais) e se faz alguma coisa, possivelmente faz isso dentro do if (embora eu ainda goste broken = (j ==i); if broken { something(); }mais pessoalmente)
Martijn

@ Martijn No meu código original, ele é impresso iapós o final do loop externo. Em outras palavras, o único pensamento realmente importante é quando exatamente ele sai do circuito externo.
PSWG

3

A resposta curta é "depende". Defendo a legibilidade e a compreensão do seu código. O caminho para o goto pode ser mais legível do que ajustar a condição ou vice-versa. Você também pode levar em consideração o princípio menos surpreendente, a diretriz usada na loja / projeto e a consistência da base de código. Por exemplo, ir para o switch ou para tratamento de erros é uma prática comum na base de código do kernel do Linux. Observe também que alguns programadores podem ter problemas para ler seu código (ou criados com o mantra "goto is evil", ou simplesmente não são aprendidos porque o professor pensa assim) e alguns deles podem até "julgá-lo".

De um modo geral, um ir além na mesma função geralmente é aceitável, especialmente se o salto não for muito longo. Seu caso de uso de deixar um loop aninhado é um exemplo conhecido de "não é tão mau".


2

No seu caso, goto é uma melhoria suficiente que parece tentadora, mas não é tanto uma melhoria que vale a pena quebrar a regra contra usá-los em sua base de código.

Pense no seu futuro revisor: ele / ela terá que pensar: "Essa pessoa é uma má codificadora ou é de fato um caso em que o goto valeu a pena? Deixe-me levar 5 minutos para decidir isso por conta própria ou pesquisar no Internet." Por que diabos você faria isso com seu futuro codificador quando não pode usar o goto inteiramente?

Em geral, essa mentalidade se aplica a todo código "ruim" e até o define: se funciona agora e é bom nesse caso, mas cria um esforço para verificar se está correto, o que nesta época um goto sempre fará, então não faça.

Dito isto, sim, você produziu um dos casos um pouco mais espinhosos. Você pode:

  • use estruturas de controle mais complexas, como um sinalizador booleano, para interromper os dois loops
  • coloque seu loop interno dentro de sua própria rotina (provavelmente ideal)
  • coloque os dois loops em uma rotina e retorne em vez de quebrar

É muito difícil para mim criar um caso em que um loop duplo não seja tão coeso que não deva ser sua própria rotina.

A propósito, a melhor discussão sobre isso que eu vi é Code Complete, de Steve McConnell . Se você gosta de pensar criticamente nesses problemas, recomendo fortemente a leitura, mesmo de capa a capa, apesar de sua imensidão.


1
É nosso programador de trabalho ler e entender código. Não apenas código simplista, a menos que essa seja a natureza da sua base de código. Existem e sempre haverá exceções à generalidade (não regra) contra o uso de gotos por causa do custo compreendido. Sempre que o benefício supera o custo, é quando o goto deve ser usado; como é o caso de todas as decisões de codificação em engenharia de software.
dietbuddha

1
@dietbuddha existe um custo em violar as convenções de codificação de software aceitas que devem ser adequadamente levadas em consideração. Há um custo no aumento da diversidade de estruturas de controle usadas em um programa, mesmo que essa estrutura de controle possa ser uma boa opção para uma situação. Há um custo em quebrar a análise estática do código. Se tudo isso ainda vale a pena, então quem sou eu para discutir.
21414 djechlin

1

TLD; DR: Não em específico, sim em geral

Para o exemplo específico que você usou, eu diria não pelas mesmas razões que muitos outros apontaram. Você pode facilmente refatorar o código em um formato equivalente, o que elimina a necessidade de goto.

Em geral, a proscrição contra gotoé uma generalidade baseada na natureza compreendida gotoe no custo de usá-la. Parte da engenharia de software é tomar decisões de implementação. A justificação dessas decisões ocorre ponderando o custo em relação aos benefícios e sempre que os benefícios superam o custo, a implementação é justificada. A controvérsia surge devido a diferentes avaliações, tanto de custo quanto de benefício.

O ponto é que sempre haverá exceções em que a gotoé justificada, mas apenas para algumas por causa de diferentes avaliações. A pena é que muitos engenheiros simplesmente excluam as técnicas por ignorância intencional, porque as leem na internet em vez de dedicar algum tempo para entender o porquê.


Você pode recomendar algum artigo na declaração goto que explique melhor o custo?
Thys

-1

Eu não acho que este caso justifique uma exceção, pois isso pode ser escrito como:

def outerLoop(){
    for (i = 0; i < 10; i++) {
        if( broken?(i) )
          return; // broken
    }
}

def broken?(i){
   for (j = 10; j > 0; j--) {
        if (j == i) {
            return true; // broken
        }
   }
   return false; // not broken
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.