GOTO ainda considerado prejudicial? [fechadas]

282

Todo mundo está ciente das Cartas de Dijkstra para o editor: vá para a declaração considerada prejudicial (também aqui transcrição .html e aqui .pdf) e houve um esforço formidável desde então para evitar a declaração goto sempre que possível. Embora seja possível usar goto para produzir um código constante e inatingível, ele permanece nas linguagens de programação modernas . Mesmo a estrutura avançada de controle de continuação no Scheme pode ser descrita como um goto sofisticado.

Que circunstâncias justificam o uso do goto? Quando é melhor evitar?

Como uma pergunta de acompanhamento: C fornece um par de funções, setjmp e longjmp, que fornecem a capacidade de ir não apenas dentro do quadro de pilha atual, mas dentro de qualquer um dos quadros de chamada. Devem ser considerados tão perigosos quanto ir? Mais perigoso?


O próprio Dijkstra lamentou esse título, pelo qual não era responsável. No final do EWD1308 (também aqui .pdf), ele escreveu:

Finalmente, uma pequena história para o registro. Em 1968, as Comunicações da ACM publicaram um texto meu sob o título " A declaração goto considerada prejudicial ", que em anos posteriores seria mais frequentemente referenciada, lamentavelmente, no entanto, muitas vezes por autores que não viram mais do que isso. título, que se tornou uma pedra angular da minha fama ao se tornar um modelo: veríamos todos os tipos de artigos sob o título "X considerado prejudicial" para quase qualquer X, incluindo um intitulado "Dijkstra considerado prejudicial". Mas o que tinha acontecido? Eu enviei um artigo com o título " Um caso contra a declaração goto", que, para acelerar sua publicação, o editor havia mudado para uma" carta ao editor "e, no processo, ele havia lhe dado um novo título de sua própria invenção! O editor era Niklaus Wirth.

Um artigo clássico bem pensado sobre esse tópico, a ser comparado ao de Dijkstra, é a Programação Estruturada com ir para Declarações , de Donald E. Knuth. A leitura ajuda a restabelecer o contexto e uma compreensão não dogmática do assunto. Neste artigo, a opinião de Dijkstra sobre este caso é relatada e é ainda mais forte:

Donald E. Knuth: Acredito que, ao apresentar tal opinião, não discordo totalmente das idéias de Dijkstra, pois ele escreveu recentemente o seguinte: "Por favor, não caia na armadilha de acreditar que sou terrivelmente dogmático sobre [o vá para a declaração]. Tenho a desconfortável sensação de que outros estão criando uma religião a partir dela, como se os problemas conceituais da programação pudessem ser resolvidos com um único truque, com uma forma simples de disciplina de codificação! "

MaD70
fonte
1
O goto do C # não é o mesmo que o Dijkstra estava falando, precisamente pelos motivos dos quais Dijkstra falou. Isso é tanto prejudicial quanto na época, mas também muito menos necessário, porque as linguagens modernas fornecem estruturas de controle alternativas. C # goto é extremamente restrito.
jalf
25
Gotos são bons quando acrescentam clareza. Se você tiver um loop aninhado longo, pode ser melhor sair dele do que definir variáveis ​​"break" e quebrar até sair.
simendsjo
28
Se você tiver um loop aninhado em 4 profundidades (não que seja uma coisa boa), interromper tudo exige a definição de valores temporários. Ir para cá é muito mais claro para mim, e o IDE deve mostrar facilmente onde está. Dito isto, o uso de goto deve ser escasso e, na minha opinião, apenas desça para pular código
simendsjo
7
Sugiro que você leia os nove mil e um tópicos marcados goto.
Matti Virkkunen
5
Há uma coisa claramente pior do que usar goto: hackear ferramentas de programação estruturada para implementar a goto.

Respostas:

184

As seguintes declarações são generalizações; Embora seja sempre possível alegar exceção, geralmente (na minha experiência e na minha humilde opinião) não vale a pena arriscar.

  1. O uso irrestrito de endereços de memória (GOTO ou ponteiros brutos) oferece muitas oportunidades para cometer erros facilmente evitáveis.
  2. Quanto mais maneiras houver para chegar a um "local" específico no código, menos confiante poderá estar em relação ao estado do sistema naquele momento. (Ver abaixo.)
  3. Programação estruturada O IMHO é menos sobre "evitar GOTOs" e mais sobre como fazer a estrutura do código corresponder à estrutura dos dados. Por exemplo, uma estrutura de dados repetida (por exemplo, matriz, arquivo seqüencial etc.) é processada naturalmente por uma unidade de código repetida. Ter estruturas embutidas (por exemplo, enquanto, para, até, para cada, etc.) permite ao programador evitar o tédio de repetir os mesmos padrões de código clichê.
  4. Mesmo que o GOTO seja um detalhe de implementação de baixo nível (nem sempre é o caso!), Está abaixo do nível que o programador deveria estar pensando. Quantos programadores equilibram seus cheques pessoais em binário bruto? Quantos programadores se preocupam com qual setor do disco contém um registro específico, em vez de apenas fornecer uma chave para um mecanismo de banco de dados (e quantas maneiras as coisas podem dar errado se realmente escrevemos todos os nossos programas em termos de setores do disco físico)?

Notas de rodapé para o acima:

Em relação ao ponto 2, considere o seguinte código:

a = b + 1
/* do something with a */

No ponto "faça alguma coisa" no código, podemos afirmar com alta confiança que aé maior que b. (Sim, estou ignorando a possibilidade de estouro de número inteiro não capturado. Não vamos atolar um exemplo simples.)

Por outro lado, se o código tivesse lido desta maneira:

...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...

A multiplicidade de maneiras de chegar ao rótulo 10 significa que temos que trabalhar muito mais para termos confiança nas relações entre ae bnesse momento. (De fato, no caso geral, é indecidível!)

Em relação ao ponto 4, toda a noção de "ir a algum lugar" no código é apenas uma metáfora. Nada está realmente "indo" a lugar algum dentro da CPU, exceto elétrons e fótons (pelo calor residual). Às vezes desistimos de uma metáfora para outra mais útil. Lembro-me de ter encontrado (algumas décadas atrás!) Um idioma em que

if (some condition) {
  action-1
} else {
  action-2
}

foi implementado em uma máquina virtual compilando a ação-1 e a ação-2 como rotinas sem parâmetros fora de linha e, em seguida, usando um único código de operação da VM de dois argumentos que usava o valor booleano da condição para chamar um ou outro. O conceito era simplesmente "escolha o que chamar agora" em vez de "vá aqui ou vá lá". Novamente, apenas uma mudança de metáfora.

joel.neely
fonte
2
Um bom argumento. Nas linguagens de nível superior, goto não significa nada (considere pular entre métodos em Java). Uma função Haskell pode consistir em uma única expressão; tente saltar disso com um goto!
Caracol mecânico
2
O Postscript funciona como o seu exemplo do ponto 4.
Luser droog
O Smalltalk funciona de maneira semelhante ao exemplo do ponto 4, se por "similarmente" você quer dizer "nada como as linguagens procedurais". : P Não há controle de fluxo no idioma; todas as decisões são tratadas via polimorfismo ( truee falsesão de tipos diferentes), e cada ramo de um if / else é basicamente um lambda.
cHao
Estes são pontos válidos, mas no final eles apenas reiteram o quão ruim pode ser "se mal utilizado". Interromper, continuar, sair, retornar, gosub, settimeout, global, include, etc. são todas técnicas modernas que requerem um rastreamento mental do fluxo de coisas e podem ser mal utilizadas para criar código espaguete e pular para criar incertezas nos estados variáveis. Para ser justo, embora eu nunca tenha visto um mau uso do goto em primeira mão, também o vi apenas uma ou duas vezes. Isso indica a afirmação de que sempre há coisas melhores para usar.
Beejor 12/09/15
gotoem uma linguagem de programação moderna (Go) stackoverflow.com/a/11065563/3309046 .
Nishanths
245

GOTO Comic do XKCD

Um colega meu disse que a única razão para usar um GOTO é se você se programou tão longe em um canto que é a única saída. Em outras palavras, projete com antecedência e você não precisará usar um GOTO posteriormente.

Eu pensei que esse quadrinho ilustra que lindamente "eu poderia reestruturar o fluxo do programa ou usar um pequeno 'GOTO'". Um GOTO é uma saída fraca quando você tem um design fraco. Velociraptores atacam os fracos .

Jim McKeeth
fonte
41
O GOTO pode saltar de um ponto arbitrário para outro ponto arbitrário. Velociraptor saltou para cá do nada!
R101:
29
Eu não acho a piada engraçada, porque todo mundo sabe que você também precisa vincular antes de poder executar.
Kinjal Dixit
31
Seu colega de trabalho está errado e obviamente não leu o artigo de Knuth.
Jim Balter
27
Eu não vi um fim do código ofuscado e distorcido ao longo dos anos, apenas para evitar um goto. @jimmcKeeth, O desenho animado xkcd acima não estabelece que o goto seja fraco. Está tirando sarro da histeria ao usá-lo.
4
Você percebe que o código fonte da biblioteca Delphi contém instruções goto. E esses são usos apropriados do goto.
David Heffernan
131

Às vezes, é válido usar o GOTO como uma alternativa ao tratamento de exceções em uma única função:

if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;

return;

err_cleanup:
...

O código COM parece cair nesse padrão com bastante frequência.

Rob Walker
fonte
29
Concordo, há casos de uso legítimos, onde, Goto pode simplificar o código e torná-lo mais legível / passível de manutenção, mas parece haver algum tipo de Goto-fobia flutuando ao redor ...
Pop Catalin
48
@ Bob: É difícil mover o código err_cleanup para uma sub-rotina se estiver limpando variáveis ​​locais.
Niki
4
Na verdade, eu usei-o em COM / VB6 só porque eu tinha nenhuma alternativa, não porque era uma alternativa. Como estou feliz hoje em dia com try / catch / finalmente.
Rui Craveiro
10
@ user4891 A maneira idiomática do C ++ não é tentar {} catch () {limpeza; }, mas RAII, onde os recursos que precisam ser limpos são feitos em destruidores. Todo construtor / destruidor gerencia exatamente um recurso.
11608 David Stone
5
Existem duas maneiras de escrever isso em C sem ir; e ambos são muito mais curtos. Ou: if (f ()) if (g ()) if (h ()) retornam êxito; Limpar(); falha de retorno; ou: se (f () && g () && h ()) retornar sucesso; Limpar(); falha de retorno;
LHP
124

Só me lembro de usar um goto uma vez. Eu tinha uma série de cinco loops contados aninhados e precisava ser capaz de romper toda a estrutura desde o início, com base em determinadas condições:

for{
  for{
    for{
      for{
        for{
          if(stuff){
            GOTO ENDOFLOOPS;
          }
        }
      }
    }
  }
}

ENDOFLOOPS:

Eu poderia ter declarado facilmente uma variável de quebra booleana e a usado como parte do condicional para cada loop, mas nesse caso eu decidi que um GOTO era tão prático quanto legível.

Nenhum velociraptor me atacou.

shsteimer
fonte
83
"Refatore em uma função e substitua goto por return :)", e a diferença é? realmente qual é a diferença? não é voltar a ir também? Devoluções também trava o fluxo estruturado de como Goto faz, e neste caso eles fazem isso da mesma forma (mesmo que Goto pode ser usado para coisas mais humildes)
Pop Catalin
38
Aninhar muitos loops geralmente é um cheiro de código próprio. A menos que você esteja fazendo, como, multiplicação de array em 5 dimensões, é difícil imaginar uma situação em que alguns dos loops internos não pudessem ser úteis para extrair funções menores. Como todas as regras de ouro, suponho que haja algumas exceções.
Doug McClean
5
Substituí-lo por um retorno só funcionará se você estiver usando um idioma que suporte retornos.
Loren Pechtel 24/10/2009
31
@leppie: A geração que se rebelou gotoe nos deu programação estruturada também rejeitou retornos antecipados, pelo mesmo motivo. Tudo se resume a quão legível o código é, quão claramente ele expressa a intenção do programador. Criar uma função com nenhum outro objetivo além de evitar o uso de uma palavra-chave maligna resulta em má coesão: a cura é pior que a doença.
Mud
21
@ButtleButkus: Francamente, isso é tão ruim, se não pior. Pelo menos com a goto, pode- se especificar explicitamente o destino. Com break 5;, (1) tenho que contar os fechamentos de loop para encontrar o destino; e (2) se a estrutura do loop mudar, poderá ser necessário alterar esse número para manter o destino correto. Se eu vou evitar goto, o ganho deve ser não ter que rastrear manualmente coisas assim.
cHao 26/05
93

Nós já tivemos essa discussão e eu mantenho meu argumento .

Além disso, estou farta de pessoas que descrevem estruturas de linguagem de alto nível como " gotodisfarçadas" porque elas claramente não entendem nada . Por exemplo:

Mesmo a estrutura avançada de controle de continuação no Scheme pode ser descrita como um goto sofisticado.

Isso é um absurdo completo. Toda estrutura de controle pode ser implementada em termos de, gotomas essa observação é totalmente trivial e inútil. gotonão é considerado prejudicial por causa de seus efeitos positivos, mas por suas consequências negativas e estas foram eliminadas pela programação estruturada.

Da mesma forma, dizer “GOTO é uma ferramenta e, como todas as ferramentas, pode ser usada e abusada” está completamente errado. Nenhum trabalhador da construção civil moderno usaria uma pedra e afirmaria que "é uma ferramenta". Rochas foram substituídas por martelos. gotofoi substituído por estruturas de controle. Se o trabalhador da construção estivesse preso na natureza sem um martelo, é claro que ele usaria uma pedra. Se um programador precisar usar uma linguagem de programação inferior que não possua o recurso X, é claro que talvez seja necessário usá-lo goto. Mas se ela o usa em qualquer outro lugar, em vez do recurso de idioma apropriado, ela claramente não entende o idioma corretamente e o usa incorretamente. É realmente tão simples quanto isso.

Konrad Rudolph
fonte
82
Obviamente, o uso adequado de uma rocha não é como um martelo. Um de seus usos adequados é uma pedra de afiar ou para afiar outras ferramentas. Mesmo a rocha humilde, quando usada corretamente, é uma boa ferramenta. Você apenas precisa encontrar o uso adequado. O mesmo vale para goto.
Kibbee
10
Então, qual é o uso adequado do Goto? Para todos os casos imagináveis, existe outra ferramenta mais adequada. E até a sua pedra de moagem é atualmente substituída por ferramentas de alta tecnologia hoje em dia, mesmo que elas ainda sejam feitas de rocha. Há uma grande diferença entre uma matéria-prima e uma ferramenta.
Konrad Rudolph
8
@jalf: Goto certamente faz existir em C #. Veja stackoverflow.com/questions/359436/…
Jon Skeet
51
Estou consternado por tantas pessoas aprovarem este post. Sua postagem parecia eficaz apenas porque você nunca se preocupou em questionar qual lógica estava realmente realizando; assim, você não percebeu sua falácia. Permita-me parafrasear todo o seu post: "Existe uma ferramenta superior para o goto em todas as situações, portanto o gotos nunca deve ser usado." Este é um bicondicional lógico, e, como tal, todo o seu post está basicamente implorando a pergunta "Como você sabe que existe uma ferramenta superior para ir em todas as situações?"
Codificando com estilo
10
@ Codificação: Não, você perdeu completamente a essência da publicação. Foi mais uma réplica do que um argumento completo e isolado. Apenas apontei a falácia no argumento principal "a favor" goto. Você está certo na medida em gotoque não proponho um argumento em si - eu não pretendia -, então não há perguntas.
Konrad Rudolph
91

Goto está extremamente baixo na minha lista de itens a serem incluídos em um programa apenas por isso. Isso não significa que é inaceitável.

Ir pode ser bom para máquinas de estado. Uma instrução switch em um loop é (em ordem de importância típica): (a) não é realmente representativa do fluxo de controle, (b) é feia, (c) é potencialmente ineficiente, dependendo do idioma e do compilador. Então você acaba escrevendo uma função por estado e fazendo coisas como "return NEXT_STATE;" que até parecem goto.

Concedido, é difícil codificar máquinas de estado de uma maneira que as torne fáceis de entender. No entanto, nenhuma dessas dificuldades está relacionada ao uso do goto, e nada disso pode ser reduzido usando estruturas de controle alternativas. A menos que seu idioma tenha uma construção de 'máquina de estado'. O meu não.

Nas raras ocasiões em que seu algoritmo é realmente mais compreensível em termos de um caminho através de uma sequência de nós (estados) conectados por um conjunto limitado de transições permitidas (gotos), em vez de por qualquer fluxo de controle mais específico (loops, condicionais, outros enfeites ), isso deve ser explícito no código. E você deve desenhar um diagrama bonito.

setjmp / longjmp pode ser bom para implementar exceções ou comportamento semelhante a exceção. Embora não sejam universalmente elogiadas, as exceções geralmente são consideradas uma estrutura de controle "válida".

setjmp / longjmp são 'mais perigosos' do que o goto, no sentido de que são mais difíceis de usar corretamente, não importa de maneira compreensível.

Nunca houve, nem nunca haverá, nenhum idioma em que seja menos difícil escrever código incorreto. Donald Knuth.

Sair do C não tornaria mais fácil escrever um bom código em C. Na verdade, seria melhor não entender que C deveria ser capaz de agir como uma linguagem assembler glorificada.

Em seguida, serão "indicadores considerados prejudiciais" e, em seguida, "digitação de pato considerados prejudiciais". Então, quem ficará para defendê-lo quando eles removerem sua construção de programação insegura? Eh?

Steve Jessop
fonte
14
Pessoalmente, esse é o comentário que eu daria o cheque. Uma coisa que eu gostaria de destacar para os leitores é que o termo esotérico "máquinas de estado" inclui coisas do cotidiano, como analisadores lexicais. Confira a saída de lex em algum momento. Cheio de gotos.
TED
2
Você pode usar uma instrução switch dentro de um loop (ou manipulador de eventos) para executar corretamente as máquinas de estado. Já fiz muitas máquinas de estado sem precisar usar jmp ou goto.
23150 Scott Whitlock
11
+1 Essas setas nas máquinas de estado são mapeadas para 'ir' mais perto do que para qualquer outra estrutura de controle. Claro, você pode usar um interruptor dentro de um loop - assim como você pode usar um monte de gotos ao invés de um tempo para outros problemas, mas geralmente é uma idéia; qual é o ponto principal desta discussão.
Edmund
2
Posso citar você nesse último parágrafo?
23711 Chris Lutz
2
E, aqui em 2013, já atingimos (e meio que superamos) a fase "indicadores considerados prejudiciais".
Isiah Meadows
68

No Linux: Usando goto No Kernel Code no Kernel Trap, há uma discussão com Linus Torvalds e um "novato" sobre o uso de GOTOs no código Linux. Há alguns pontos muito bons lá e Linus se veste dessa arrogância habitual :)

Algumas passagens:

Linus: "Não, você sofreu uma lavagem cerebral por pessoas do CS que pensaram que Niklaus Wirth realmente sabia do que estava falando. Ele não sabia. Ele não tem idéia de nada."

-

Linus: "Eu acho que o Goto é bom e geralmente é mais legível do que grandes quantidades de indentação".

-

Linus: "É claro que, em linguagens estúpidas como Pascal, onde os rótulos não podem ser descritivos, o goto pode ser ruim."

Marcio Aguiar
fonte
9
Esse é um bom ponto, como? Eles estão discutindo seu uso em uma linguagem que não tem mais nada. Quando você está programando em montagem, todas as ramificações e saltos são salvas. E C é, e foi, uma "linguagem assembly portátil". Além disso, as passagens que você cita não dizem nada sobre por que ele acha que ir é bom.
jalf
5
Uau. É decepcionante ler isso. Você pensaria que um grande cara de SO, como Linus Torvalds, saberia melhor do que dizer isso. Pascal (pascal da velha escola, não a versão moderna do Object) foi o que o Mac OS Classic escreveu durante o período de 68k e foi o sistema operacional mais avançado de sua época.
Mason Wheeler
6
O @mason Classic Mac OS tinha algumas bibliotecas Pascal (eventualmente - o tempo de execução Pascal consumia muita memória nos Macs anteriores), mas a maioria do código principal foi escrita no Assembler, principalmente as rotinas gráficas e de interface do usuário.
22411 Jim Dovey
30
Linus apenas argumenta (explicitamente, como Rik van Riel nessa discussão) a favor de lidar com o status de saída, e ele o faz com base na complexidade que as construções alternativas de C trariam se fossem usadas.
Charles Stewart
21
IMHO Linus está certo sobre esta questão. Seu argumento é que o código do kernel, escrito em C, que precisa implementar algo semelhante ao tratamento de exceções, é mais claro e simplesmente escrito usando um goto. O idioma goto cleanup_and_exité um dos poucos "bons" usos de Goto deixou agora que temos for, whilee ifpara administrar nosso fluxo de controle. Veja também: programmers.stackexchange.com/a/154980
steveha
50

Em C, gotofunciona apenas dentro do escopo da função atual, que tende a localizar possíveis bugs. setjmpe longjmpsão muito mais perigosos, sendo não locais, complicados e dependentes da implementação. Na prática, porém, são muito obscuros e incomuns para causar muitos problemas.

Eu acredito que o perigo de gotoem C é muito exagerado. Lembre-se de que os gotoargumentos originais ocorreram nos dias de idiomas como o BASIC à moda antiga, onde os iniciantes escreviam código espaguete como este:

3420 IF A > 2 THEN GOTO 1430

Aqui, Linus descreve um uso apropriado de goto: http://www.kernel.org/doc/Documentation/CodingStyle (capítulo 7).

smh
fonte
7
Quando o BASIC foi disponibilizado pela primeira vez, não havia alternativa ao GOTO nnnn e ao GOSUB mmmm como formas de pular. Construções estruturadas foram adicionadas mais tarde.
24611 Jonathan Leffler
7
Você está perdendo o ponto ... mesmo assim, você não tem que escrever spaghetti ... seus GOTOs sempre pode ser usado de uma maneira disciplinada
JoelFan
Também é importante notar que o comportamento de setjmp/ longjmpsó foi especificado quando eles foram usados ​​como um meio de pular para um local dentro de um escopo de outros locais dentro desse mesmo escopo. Depois que o controle sai do escopo onde setjmpé executado, qualquer tentativa de uso longjmpna estrutura criada por setjmpresultará em um comportamento indefinido.
Supercat 13/05
9
Algumas versões do BASIC permitem GOTO A * 40 + B * 200 + 30. Não é difícil ver como isso foi muito útil e muito perigoso.
Jon Hanna
1
@Hjulle, calcularia a expressão e depois passaria para a linha de código com esse número (números de linha explícitos eram um requisito da maioria dos dialetos anteriores). ZX Spectrum básico era um que aceitaria que
Jon Hanna
48

Hoje, é difícil ver muita coisa sobre a GOTOafirmação, porque o pessoal da "programação estruturada" venceu o debate e as linguagens de hoje têm estruturas de fluxo de controle suficientes para evitar GOTO.

Conte o número de gotos em um programa C moderno. Agora adicione o número de break, continuee returndeclarações. Além disso, adicione o número de vezes que você usa if, else, while, switchou case. É sobre quantos GOTOprogramas seu programa teria se você estivesse escrevendo em FORTRAN ou BASIC em 1968, quando Dijkstra escreveu sua carta.

Faltavam linguagens de programação na época no fluxo de controle. Por exemplo, no Dartmouth BASIC original:

  • IFdeclarações não tinham ELSE. Se você queria um, tinha que escrever:

    100 IF NOT condition THEN GOTO 200
    ...stuff to do if condition is true...
    190 GOTO 300
    200 REM else
    ...stuff to do if condition is false...
    300 REM end if
    
  • Mesmo que sua IFdeclaração não precisasse de uma ELSE, ela ainda estava limitada a uma única linha, que geralmente consistia em a GOTO.

  • Não houve DO...LOOPdeclaração. Para non- FORloops, você tinha que terminar o loop com um explícito GOTOou IF...GOTOvoltar ao início.

  • Não houve SELECT CASE. Você teve que usar ON...GOTO.

Então, você acabou com um monte de GOTOs em seu programa. E você não podia depender da restrição de GOTOs para uma única sub-rotina (porque GOSUB...RETURNera um conceito tão fraco de sub-rotinas), para que elas GOTOpudessem ir a qualquer lugar . Obviamente, isso dificultava o fluxo de controle.

É daí GOTOque veio o anti- movimento.

dan04
fonte
Outra coisa a notar é que a maneira preferida de escrever código, se alguém tivesse algum código em um loop que precisaria executar raramente, seria 420 if (rare_condition) then 3000// 430 and onward: rest of loop and other main-line code// 3000 [code for rare condition]// 3230 goto 430. Escrever código dessa maneira evita quaisquer ramificações ou saltos no caso comum da linha principal, mas torna as coisas difíceis de seguir. Evitar ramificações no código de montagem pode ser pior, se algumas ramificações estiverem limitadas a, por exemplo, +/- 128 bytes e às vezes não tiverem pares complementares (por exemplo, "cjne" existe, mas não "cje").
Supercat
Certa vez, escrevi um código para o 8x51, que tinha uma interrupção que era executada uma vez a cada 128 ciclos. Todo ciclo extra gasto no caso comum do ISR reduziria a velocidade de execução do código da linha principal em mais de 1% (acho que cerca de 90 dos 128 estavam disponíveis para a linha principal) e qualquer instrução de ramificação levaria dois ciclos ( nos casos de ramificação e de queda). O código tinha duas comparações - uma que normalmente reportaria igual; o outro não é igual. Nos dois casos, o código de caso raro estava a mais de 128 bytes. Então ...
supercat
cjne r0,expected_value,first_comp_springboard/.../ cjne r1,unexpected_value,second_comp_fallthrough// `ajmp second_comp_target` // first_comp_springboard: ajmp first_comp_target// second_comp_fallthrough: .... Não é um padrão de codificação muito bom, mas quando os ciclos individuais são contados, fazemos essas coisas. É claro que, na década de 1960, esses níveis de otimização eram mais importantes do que hoje, especialmente porque as CPUs modernas geralmente exigem otimizações estranhas, e os sistemas de compilação just-in-time podem aplicar o código de otimização para CPUs que não existiam quando o código em questão foi escrito.
Supercat 13/05
34

Ir para pode fornecer uma espécie de substituto para o tratamento de exceções "real" em certos casos. Considerar:

ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;

Obviamente, esse código foi simplificado para ocupar menos espaço; portanto, não fique muito preocupado com os detalhes. Mas considere uma alternativa que eu já vi muitas vezes no código de produção por codificadores com comprimentos absurdos para evitar o uso de goto:

success=false;
do {
    ptr = malloc(size);
    if (!ptr) break;
    bytes_in = read(f_in,ptr,size);
    if (count=<0) break;
    bytes_out = write(f_out,ptr,bytes_in);
    if (bytes_out != bytes_in) break;
    success = true;
} while (false);

Agora, funcionalmente, esse código faz exatamente a mesma coisa. De fato, o código gerado pelo compilador é quase idêntico. No entanto, no zelo do programador em apaziguar Nogoto (o temido deus da repreensão acadêmica), esse programador quebrou completamente o idioma subjacente que o whileloop representa e fez um número real na legibilidade do código. Isto não é melhor.

Portanto, a moral da história é que, se você se deparar com algo realmente estúpido para evitar usar goto, não o faça.

tylerl
fonte
1
Embora eu tenha a tendência de concordar com o que você está dizendo aqui, o fato de as breakdeclarações estarem dentro de uma estrutura de controle deixa claro exatamente o que elas fazem. Com o gotoexemplo, a pessoa que lê o código precisa procurar o código para encontrar o rótulo, o que, em teoria, poderia estar antes da estrutura de controle. Eu não sou experiente o suficiente com o estilo antigo C para julgar que um é definitivamente melhor que o outro, mas existem compensações de qualquer maneira.
Vivian River
5
@DanielAllenLangdon: O fato de que os breaks estão dentro de um loop deixa claro exatamente que eles saem do loop . Isso não é "exatamente o que eles fazem", pois, na realidade, não há um loop! Nada nele tem chance de se repetir, mas isso não está claro até o fim. O fato de você ter um "loop" que nunca é executado mais de uma vez significa que as estruturas de controle estão sendo abusadas. Com o gotoexemplo, o programador pode dizer goto error_handler;. É mais explícito e ainda menos difícil de seguir. (Ctrl + F "error_handler:" para encontrar o alvo Tente fazer isso com "}"..)
Chao
1
Uma vez vi código semelhante ao seu segundo exemplo em um sistema de controle de tráfego aéreo - porque 'goto não está no nosso léxico'.
QED
30

Donald E. Knuth respondeu a essa pergunta no livro "Literate Programming", 1992 CSLI. Na p. 17, há um ensaio " Programação estruturada com instruções goto " (PDF). Acho que o artigo pode ter sido publicado em outros livros também.

O artigo descreve a sugestão de Dijkstra e descreve as circunstâncias em que isso é válido. Mas ele também fornece vários exemplos de contador (problemas e algoritmos) que não podem ser facilmente reproduzidos usando apenas loops estruturados.

O artigo contém uma descrição completa do problema, histórico, exemplos e exemplos contrários.

Bruno Ranschaert
fonte
25

Goto considerado útil.

Comecei a programar em 1975. Para os programadores da década de 1970, as palavras "goto considerado prejudicial" diziam mais ou menos que valia a pena tentar novas linguagens de programação com estruturas de controle modernas. Tentamos os novos idiomas. Nós convertemos rapidamente. Nós nunca voltamos.

Nunca voltamos, mas, se você é mais jovem, nunca esteve lá em primeiro lugar.

Agora, um histórico em linguagens de programação antigas pode não ser muito útil, exceto como um indicador da idade do programador. Não obstante, os programadores mais jovens não têm esse pano de fundo e, portanto, não entendem mais a mensagem que o slogan "considerou prejudicial" transmitido ao público pretendido no momento em que foi apresentado.

Slogans que não se entende não são muito esclarecedores. Provavelmente é melhor esquecer esses slogans. Esses slogans não ajudam.

Esse slogan em particular, no entanto, "Goto considerado prejudicial", ganhou vida própria como morto-vivo.

Goto não pode ser abusado? Resposta: claro, mas e daí? Praticamente todos os elementos de programação podem ser abusados. Os humildes, boolpor exemplo, são abusados ​​com mais frequência do que alguns de nós gostariam de acreditar.

Por outro lado, não me lembro de ter encontrado uma instância única e real de ir a abuso desde 1990.

O maior problema com o goto provavelmente não é técnico, mas social. Os programadores que não sabem muito às vezes parecem sentir que ir embora depreciativo os faz parecer inteligentes. Você pode ter que satisfazer esses programadores de tempos em tempos. Como a vida.

O pior de ir hoje é que ele não é usado o suficiente.

thb
fonte
24

Atraído por Jay Ballou, adicionando uma resposta, adicionarei meus 0,02 €. Se Bruno Ranschaert ainda não o tivesse feito, eu teria mencionado o artigo "Programação Estruturada com Instruções GOTO" de Knuth.

Uma coisa que não vi discutida é o tipo de código que, embora não seja exatamente comum, foi ensinado nos livros de texto do Fortran. Coisas como a faixa estendida de um loop DO e sub-rotinas de código aberto (lembre-se, isso seria Fortran II ou Fortran IV ou Fortran 66 - não Fortran 77 ou 90). Há pelo menos uma chance de que os detalhes sintáticos sejam inexatos, mas os conceitos devem ser precisos o suficiente. Os trechos em cada caso estão dentro de uma única função.

Observe que o excelente, mas datado (e esgotado) livro ' The Elements of Programming Style, 2nd Edn ' de Kernighan & Plauger inclui alguns exemplos reais de abuso do GOTO da programação de livros didáticos de sua época (final dos anos 70). O material abaixo não é desse livro, no entanto.

Faixa estendida para um loop DO

       do 10 i = 1,30
           ...blah...
           ...blah...
           if (k.gt.4) goto 37
91         ...blah...
           ...blah...
10     continue
       ...blah...
       return
37     ...some computation...
       goto 91

Uma das razões para tal bobagem foi o bom e velho cartão perfurado. Você pode notar que os rótulos (bem fora de sequência porque esse era o estilo canônico!) Estão na coluna 1 (na verdade, eles precisavam estar nas colunas 1-5) e o código nas colunas 7-72 ​​(a coluna 6 era a continuação coluna do marcador). As colunas 73-80 receberiam um número de sequência e havia máquinas que classificariam os decks de cartões perfurados na ordem dos números de sequência. Se você tivesse o seu programa em cartões sequenciados e precisasse adicionar alguns cartões (linhas) no meio de um loop, seria necessário redesenhar tudo depois dessas linhas extras. No entanto, se você substituir um cartão pelo material GOTO, poderá evitar reequilibrar todos os cartões - basta colocar os novos cartões no final da rotina com novos números de sequência. Considere ser a primeira tentativa de 'computação verde'

Ah, você também pode notar que estou trapaceando e não gritando - o Fortran IV foi escrito em letras maiúsculas normalmente.

Sub-rotina de código aberto

       ...blah...
       i = 1
       goto 76
123    ...blah...
       ...blah...
       i = 2
       goto 76
79     ...blah...
       ...blah...
       goto 54
       ...blah...
12     continue
       return
76     ...calculate something...
       ...blah...
       goto (123, 79) i
54     ...more calculation...
       goto 12

O GOTO entre os rótulos 76 e 54 é uma versão do goto computado. Se a variável i tiver o valor 1, vá para o primeiro rótulo na lista (123); se tiver o valor 2, vá para o segundo e assim por diante. O fragmento de 76 ao goto computado é a sub-rotina de código aberto. Era um pedaço de código executado como uma sub-rotina, mas escrito no corpo de uma função. (O Fortran também tinha funções de instrução - que eram funções incorporadas que cabiam em uma única linha.)

Havia construções piores do que o goto computado - você pode atribuir rótulos a variáveis ​​e, em seguida, usar um goto atribuído. O goto atribuído pelo Google me diz que foi excluído do Fortran 95. Giz um para a revolução da programação estruturada, que pode-se dizer que começou em público com a carta ou artigo "GOTO Considerado Prejudicial" de Dijkstra.

Sem algum conhecimento do tipo de coisas que foram feitas no Fortran (e em outras línguas, a maioria das quais caiu justamente no caminho), é difícil para nós, recém-chegados, entender o escopo do problema com o qual Dijkstra estava lidando. Caramba, eu não comecei a programar até dez anos após a publicação dessa carta (mas tive a infelicidade de programar no Fortran IV por um tempo).

Jonathan Leffler
fonte
2
Se você quiser ver um exemplo de código usando goto'in the wild', a pergunta Wanted: Working Bose-Hibbard Sort Algorithm mostra algum código (Algol 60) publicado em 1963. O layout original não se compara aos padrões modernos de codificação. O código esclarecido (recuado) ainda é bastante inescrutável. As gotodeclarações não fazer torná-lo (muito) difícil entender o que o algoritmo é até.
Jonathan Leffler
1
Sendo jovem demais para ter experimentado algo próximo a cartões perfurados, era esclarecedor ler sobre o problema de perfurar novamente. +1
Qix - MONICA FOI ERRADA EM 01/12
20

Não há coisas como GOTO consideradas prejudiciais .

GOTO é uma ferramenta e, como todas as ferramentas, pode ser usada e abusada .

No entanto, existem muitas ferramentas no mundo da programação que tendem a ser mais abusadas do que usadas , e o GOTO é uma delas. a declaração WITH de Delphi é outra.

Pessoalmente, eu também não uso no código típico , mas tive o uso estranho de GOTO e WITH que eram justificados, e uma solução alternativa teria mais código.

A melhor solução seria o compilador avisá-lo de que a palavra-chave estava corrompida e você teria que colocar algumas diretivas pragma na declaração para se livrar dos avisos.

É como dizer aos seus filhos para não correrem com tesouras . As tesouras não são ruins, mas talvez algumas delas não sejam a melhor maneira de manter a saúde.

Lasse V. Karlsen
fonte
O problema com o GOTO é que ele quebra invariantes importantes que tomamos como garantidos pelas linguagens de programação modernas. Para dar um exemplo, se eu chamar uma função, presumimos que, quando a função for concluída, ela retornará o controle ao chamador, normalmente, ou através do excepcional desenrolamento da pilha. Se essa função usa mal o GOTO, é claro que isso não é mais verdade. Isso dificulta muito o raciocínio sobre nosso código. Não é suficiente evitar cuidadosamente o mau uso do GOTO. O problema ainda ocorre se GOTO é mal utilizado por nossos dependências ...
Jonathan Hartley
... Portanto, para saber se podemos argumentar sobre nossas chamadas de função, precisamos examinar todas as linhas do código-fonte de cada uma de nossas dependências transitivas, verificando se elas não usam mal o GOTO. Portanto, apenas a existência do GOTO no idioma quebrou nossa capacidade de raciocinar com confiança sobre nosso próprio código, mesmo se o usarmos perfeitamente (ou nunca o usarmos). Por esse motivo, o GOTO não é apenas uma ferramenta a ser usada com cuidado. Ele é quebrado sistemicamente e sua existência em um idioma é considerada unilateralmente prejudicial.
Jonathan Hartley
Mesmo que a gotopalavra - chave tenha sido removida do C #, o conceito de "pular aqui" ainda existe em IL. Um loop infinito pode ser facilmente construído sem a palavra-chave goto. Se essa falta de garantia de que o código chamado retornará, na sua opinião, significa "incapaz de raciocinar sobre o código", então eu diria que nunca tivemos essa capacidade.
Lasse V. Karlsen
Ah, você encontrou com perspicácia a fonte de nossa falta de comunicação. O gotoem C # não é um "ir" real no sentido original da palavra. É uma versão muito mais fraca que permite apenas saltar dentro de uma função. O sentido em que estou usando "goto" permite saltar para qualquer lugar do processo. Portanto, embora o C # tenha uma palavra-chave "goto", sem dúvida nunca teve um goto real. Sim, um goto real está disponível no nível IL, da mesma maneira que quando qualquer idioma é compilado no assembly. Mas o programador é protegido disso, incapaz de usá-lo em circunstâncias normais, portanto não conta.
Jonathan Hartley
Aliás, a opinião que estou descrevendo aqui não é originalmente minha, mas acho que é o núcleo do artigo original de Dijkstra de 1967, re-intitulado por seu editor "Goto considerado prejudicial", que se tornou um meme tão citado por 50 anos precisamente porque era tão revolucionário, perspicaz e universalmente aceito.
Jonathan Hartley
17

Nunca foi, desde que você fosse capaz de pensar por si mesmo.

stesch
fonte
17

Desde que comecei a fazer algumas coisas no kernel do linux, o gotos não me incomoda tanto quanto antes. No começo, fiquei horrorizado ao ver que eles (caras do kernel) adicionaram gotos ao meu código. Desde então, acostumei-me ao uso de gotos, em alguns contextos limitados, e agora os utilizarei ocasionalmente. Normalmente, é necessário ir para o final de uma função para fazer algum tipo de limpeza e resgate, em vez de duplicar a mesma limpeza e resgate em vários locais da função. E tipicamente, não é algo grande o suficiente para passar para outra função - por exemplo, liberar algumas variáveis ​​localmente (k) malloc'ed é um caso típico.

Escrevi código que usava setjmp / longjmp apenas uma vez. Foi em um programa de seqüenciador de bateria MIDI. A reprodução ocorreu em um processo separado de toda a interação do usuário, e o processo de reprodução usou a memória compartilhada com o processo da interface do usuário para obter as informações limitadas necessárias para a reprodução. Quando o usuário quis interromper a reprodução, o processo de reprodução fez apenas um longjmp "do início ao fim", em vez de um desenrolar complicado de onde ele estava sendo executado quando o usuário queria que ele parasse. Funcionou muito bem, era simples e nunca tive problemas ou bugs relacionados a ele nesse caso.

setjmp / longjmp têm seu lugar - mas esse lugar provavelmente não será visitado, mas de vez em quando.

Edit: Acabei de olhar para o código. Na verdade, foi o siglongjmp () que eu usei, não o longjmp (não que seja um grande negócio, mas eu tinha esquecido que o siglongjmp existia).

smcameron
fonte
15

Se você está escrevendo uma VM em C, acontece que o uso de gotos computados (gcc) é o seguinte:

char run(char *pc) {
    void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
    #define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
    NEXT_INSTR(0);
    op_inc:
    ++acc;
    NEXT_INSTR(1);
    op_lda_direct:
    acc = ram[++pc];
    NEXT_INSTR(1);
    op_hlt:
    return acc;
}

funciona muito mais rápido que o comutador convencional dentro de um loop.

vt.
fonte
O único problema, que não é padrão, é?
Calmarius
&&op_inccertamente não compila, porque (à esquerda) &espera um lvalue, mas (à direita) &produz um rvalue.
Fredoverflow
7
@FredO: É um operador especial do GCC. No entanto, eu rejeitaria esse código em todas as circunstâncias menos graves, porque certamente não consigo entender o que está acontecendo.
Puppy
Embora este seja um exemplo (otimização máxima para velocidade) que justifique o uso de código goto e criptográfico, ele ainda deve ser altamente comentado para explicar a necessidade de otimização, aproximadamente como ele funciona e a melhor linha por linha comentários que podem ser feitos. Portanto, ele ainda falha, mas porque deixa os programadores de manutenção no escuro. Mas eu gosto do exemplo da VM. Obrigado.
precisa
14

Porque gotopode ser usado para confundir metaprogramação

Gotoé uma expressão de controle de alto e baixo nível e, como resultado, simplesmente não possui um padrão de design apropriado para a maioria dos problemas.

É de baixo nível no sentido de que um goto é uma operação primitiva que implementa algo maior como whileou foreachou algo assim.

É de alto nível, no sentido de que, quando usado de certas maneiras, leva o código que é executado em uma sequência clara, de forma ininterrupta, exceto para loops estruturados, e o transforma em pedaços de lógica que são, com gotos suficientes , - saco de lógica sendo dinamicamente remontado.

Portanto, existe um lado prosaico e um lado malignogoto .

O lado prosaico é que um goto apontando para cima pode implementar um loop perfeitamente razoável e um goto apontando para baixo pode fazer um breakou perfeitamente razoável return. É claro que um real while, breakou returnseria muito mais legível, pois o pobre humano não precisaria simular o efeito do gotopara obter uma visão geral. Então, uma má ideia em geral.

O lado mau envolve uma rotina que não usa goto por um tempo, pausa ou retorno, mas usa-a para o que se chama lógica de espaguete . Nesse caso, o desenvolvedor goto-feliz está construindo pedaços de código a partir de um labirinto de goto, e a única maneira de entendê-lo é simulá-lo mentalmente como um todo, uma tarefa terrivelmente cansativa quando há muitos goto. Quero dizer, imagine o problema de avaliar o código onde elsenão é precisamente o inverso do if, onde ifs aninhados podem permitir algumas coisas que foram rejeitadas pelo externo if, etc.

Finalmente, para realmente cobrir o assunto, devemos observar que essencialmente todas as línguas antigas, exceto Algol, inicialmente fizeram apenas declarações individuais sujeitas às suas versões de if-then-else. Portanto, a única maneira de fazer um bloco condicional era gotocontorná-lo usando uma condicional inversa. Insano, eu sei, mas eu li algumas especificações antigas. Lembre-se de que os primeiros computadores foram programados em código de máquina binário, portanto, suponho que qualquer tipo de HLL seja um salva-vidas; Eu acho que eles não eram muito exigentes sobre exatamente quais recursos da HLL eles tinham.

Tendo dito tudo o que costumava colocar gotoem cada programa que escrevi "apenas para irritar os puristas" .

DigitalRoss
fonte
4
+1 por irritar os puristas! :-)
Lumi
10

Negar o uso da declaração GOTO aos programadores é como dizer a um carpinteiro que não use um martelo, pois pode danificar a parede enquanto ele está martelando um prego. Um verdadeiro programador sabe como e quando usar um GOTO. Eu segui atrás de alguns desses chamados 'Programas Estruturados'. Vejo esse código Horrid apenas para evitar o uso de um GOTO, que eu poderia atirar no programador. Ok, em defesa do outro lado, eu também vi códigos de espaguete de verdade, e esses programadores também devem ser atingidos.

Aqui está apenas um pequeno exemplo de código que encontrei.

  YORN = ''
  LOOP
  UNTIL YORN = 'Y' OR YORN = 'N' DO
     CRT 'Is this correct? (Y/N) : ':
     INPUT YORN
  REPEAT
  IF YORN = 'N' THEN
     CRT 'Aborted!'
     STOP
  END

-----------------------OU----------------------

10:  CRT 'Is this Correct (Y)es/(N)o ':

     INPUT YORN

     IF YORN='N' THEN
        CRT 'Aborted!'
        STOP
     ENDIF
     IF YORN<>'Y' THEN GOTO 10
CurtTampa
fonte
3
CRT 'Isso está correto? (S / N): ': ENTRADA YORN ATÉ YORN =' Y 'OU YORN =' N '; etc
joel.neely
5
De fato, mas mais importante, um programador real sabe quando não usar um goto- e sabe o porquê . Evitando uma construção de linguagem tabu porque o $ programming_guru disse isso, essa é a própria definição de programação de cultos de carga.
Piskvor saiu do prédio
1
É uma boa analogia. Para pregar um prego sem danificar o substrato, você não elimina o martelo. Em vez disso, você usa uma ferramenta simples, conhecida como um soco na unha. Este é um pino metálico com uma extremidade cônica que possui um recorte oco na ponta, para acoplar-se com segurança à cabeça da unha (essas ferramentas são fornecidas em tamanhos diferentes para unhas diferentes). A outra extremidade brusca do perfurador da unha é atingida por um martelo.
Kaz
9

"Neste link http://kerneltrap.org/node/553/2131 "

Ironicamente, a eliminação do goto introduziu um bug: a chamada do spinlock foi omitida.

Jay Ballou
fonte
+1 para 'eliminar o goto introduziu um bug'
QED
7

O artigo original deve ser considerado como "GOTO Incondicional Considerado Prejudicial". Ele defendia, em particular, uma forma de programação baseada em construções condicionais ( if) e iterativas ( while), em vez do teste e salto comum ao código inicial. gotoainda é útil em alguns idiomas ou circunstâncias, onde não existe uma estrutura de controle apropriada.

John Millikin
fonte
6

O único lugar em que concordo que Goto poderia ser usado é quando você precisa lidar com erros, e cada ponto em que ocorre um erro requer um tratamento especial.

Por exemplo, se você está pegando recursos e usando semáforos ou mutexes, precisa pegá-los em ordem e sempre deve liberá-los da maneira oposta.

Algum código requer um padrão muito estranho de capturar esses recursos, e você não pode simplesmente escrever uma estrutura de controle de fácil manutenção e compreensão para lidar corretamente com a captura e a liberação desses recursos para evitar conflitos.

É sempre possível fazer o que é certo sem ir, mas neste caso e em alguns outros, o Ir é na verdade a melhor solução, principalmente para facilitar a leitura e a manutenção.

-Adão

Adam Davis
fonte
5

Um uso moderno do GOTO é pelo compilador C # para criar máquinas de estado para enumeradores definidos pelo retorno de rendimento.

GOTO é algo que deve ser usado por compiladores e não por programadores.

Brian Leahy
fonte
5
Quem exatamente você acha que cria os compiladores?
tloach 23/09/08
10
Compiladores, é claro!
Seiti
3
Eu acho que ele quer dizer "GOTO é algo que só deve ser usado por código emitido por um compilador".
Simon Buchan
Este é um caso contra goto. Onde podemos usar gotoem uma máquina de estado codificada manualmente para implementar um enumerador, podemos apenas usá-lo yield.
Jon Hanna
ative várias strings (para impedir a compilação para if-else) com as compilações de maiúsculas e minúsculas padrão para alternar com a instrução goto.
M.kazem Akhgary
5

Até que C e C ++ (entre outros culpados) rotulem quebras e continuem, goto continuará tendo um papel.

DrPizza
fonte
Então rotular ou continuar seria diferente de ir como?
Matthew Whited
3
Eles não permitem saltos totalmente arbitrários no fluxo de controle.
DrPizza
5

Se o próprio GOTO fosse ruim, os compiladores seriam ruins, porque eles geram JMPs. Se pular em um bloco de código, especialmente após um ponteiro, fosse inerentemente mau, a instrução RETurn seria má. Pelo contrário, o mal está em potencial para abuso.

Às vezes, eu tive que escrever aplicativos que precisavam acompanhar vários objetos em que cada objeto tinha que seguir uma intrincada sequência de estados em resposta a eventos, mas a coisa toda era definitivamente um thread único. Uma sequência típica de estados, se representada no pseudo-código, seria:

request something
wait for it to be done
while some condition
    request something
    wait for it
    if one response
        while another condition
            request something
            wait for it
            do something
        endwhile
        request one more thing
        wait for it
    else if some other response
        ... some other similar sequence ...
    ... etc, etc.
endwhile

Tenho certeza de que isso não é novo, mas a maneira como lidei com C (++) foi definir algumas macros:

#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1

#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...

Então (assumindo que o estado é inicialmente 0), a máquina de estados estruturados acima se transforma no código estruturado:

{
    DISPATCH4; // or as high a number as needed
    request something;
    WAIT(1); // each WAIT has a different number
    while (some condition){
        request something;
        WAIT(2);
        if (one response){
            while (another condition){
                request something;
                WAIT(3);
                do something;
            }
            request one more thing;
            WAIT(4);
        }
        else if (some other response){
            ... some other similar sequence ...
        }
        ... etc, etc.
    }
    DONE;
}

Com uma variação disso, pode haver CALL e RETURN, para que algumas máquinas de estado possam agir como sub-rotinas de outras máquinas de estado.

Isso é incomum? Sim. É preciso algum aprendizado por parte do mantenedor? Sim. Esse aprendizado vale a pena? Acho que sim. Isso poderia ser feito sem os GOTOs que pulam em blocos? Não.

Mike Dunlavey
fonte
1
Evitar um recurso de linguagem por medo é um sinal de dano cerebral. Isso também é mais legal do que o inferno.
Vector Gorgoth
4

Eu o evito, pois um colega / gerente indubitavelmente questiona seu uso em uma revisão de código ou quando se depara com ela. Embora eu ache que ele tenha usos (o caso de manipulação de erros, por exemplo) - você encontrará algum outro desenvolvedor que terá algum tipo de problema com ele.

Não vale a pena.

Aardvark
fonte
O bom dos blocos Try ... Catch em C # é que eles cuidam da limpeza da pilha e de outros recursos alocados (chamados de desenrolamento da pilha), pois a exceção é transformada em manipulador de exceções. Isso faz com que o Try ... Catch seja muito melhor que o Goto, então se você tiver o Try ... Catch, use-o.
Scott Whitlock
Eu tenho uma solução melhor: agrupe o pedaço de código que você deseja exibir em 'do {...} while (0);' ciclo. Dessa forma, você pode saltar da mesma maneira que um goto sem a sobrecarga do loop try / catch (eu não sei sobre C #, mas em C ++, a tentativa é de baixo custo e a captura é de alto custo, então parece excessivo para lançar uma exceção em que um simples salto seria suficiente).
22411 Jim Dovey
10
Jim, o problema disso é que não passa de uma maneira estupidamente indireta de obter um goto.
Codificando com estilo
4

Na verdade, eu me vi forçado a usar um goto, porque eu literalmente não conseguia pensar em uma maneira melhor (mais rápida) de escrever esse código:

Eu tinha um objeto complexo e precisava fazer alguma operação nele. Se o objeto estivesse em um estado, eu poderia fazer uma versão rápida da operação, caso contrário, teria que fazer uma versão lenta da operação. O fato é que, em alguns casos, no meio da operação lenta, era possível perceber que isso poderia ter sido feito com a operação rápida.

SomeObject someObject;    

if (someObject.IsComplex())    // this test is trivial
{
    // begin slow calculations here
    if (result of calculations)
    {
        // just discovered that I could use the fast calculation !
        goto Fast_Calculations;
    }
    // do the rest of the slow calculations here
    return;
}

if (someObject.IsmediumComplex())    // this test is slightly less trivial
{
    Fast_Calculations:
    // Do fast calculations
    return;
}

// object is simple, no calculations needed.

Isso estava em uma parte crítica da velocidade do código da interface do usuário em tempo real, então sinceramente acho que um GOTO foi justificado aqui.

Hugo

Rocketmagnet
fonte
A maneira não GOTO seria usar uma função fast_calculations, que incorre em alguma sobrecarga. Provavelmente não é perceptível na maioria das circunstâncias, mas como você disse, isso era crítico em termos de velocidade.
Kyle Cronin
1
Bem, isso não é surpreendente. Todo código que possui diretrizes de desempenho absolutamente insanas sempre quebrará praticamente todas as práticas recomendadas. As práticas recomendadas são de acessibilidade e manutenção, não para espremer mais 10 milissegundos ou economizar 5 bytes a mais de RAM.
Jonathon
1
@ JonathonWisnoski, os usos legítimos do goto também eliminam quantidades insanas de código spagetti interferindo nos ninhos de variáveis ​​de um rato para acompanhar onde estamos indo.
vonbrand
4

Quase todas as situações em que um goto pode ser usado, você pode fazer o mesmo usando outras construções. Goto é usado pelo compilador de qualquer maneira.

Eu pessoalmente nunca o uso explicitamente, nunca preciso.

Adam Houldsworth
fonte
4

Uma coisa que eu não vi em nenhuma das respostas aqui é que uma solução 'goto' geralmente é mais eficiente do que uma das soluções de programação estruturada mencionadas com frequência.

Considere o caso de muitos aninhados loops, onde usar 'goto' em vez de if(breakVariable)várias seções é obviamente mais eficiente. A solução "Coloque seus loops em uma função e use return" é muitas vezes totalmente irracional. No caso provável de os loops estarem usando variáveis ​​locais, agora você precisa passá-los por todos os parâmetros de função, potencialmente manipulando cargas de dores de cabeça extras que surgem disso.

Agora considere o caso de limpeza, que eu já usei com bastante frequência, e é tão comum que provavelmente fui responsável pela estrutura try {} catch {} que não está disponível em muitos idiomas. O número de verificações e variáveis ​​extras necessárias para realizar a mesma coisa é muito pior do que as uma ou duas instruções para dar o salto e, novamente, a solução de função adicional não é uma solução. Você não pode me dizer que é mais gerenciável ou mais legível.

Agora, o espaço do código, o uso da pilha e o tempo de execução podem não ser suficientes em muitas situações para muitos programadores, mas quando você está em um ambiente incorporado com apenas 2 KB de espaço para trabalhar, 50 bytes de instruções extras para evitar uma definição clara. 'goto' é simplesmente risível, e essa não é uma situação tão rara quanto muitos programadores de alto nível acreditam.

A afirmação de que "ir é prejudicial" foi muito útil para avançar para a programação estruturada, mesmo que sempre tenha sido uma generalização excessiva. Neste ponto, todos nós já ouvimos o suficiente para ser cauteloso em usá-lo (como deveríamos). Quando é obviamente a ferramenta certa para o trabalho, não precisamos ter medo disso.

gkimsey
fonte
3

Você pode usá-lo para interromper um loop profundamente aninhado, mas na maioria das vezes seu código pode ser refatorado para ficar mais limpo sem loops profundamente aninhados.

Brian R. Bondy
fonte