Entendo que, exceto por romper loops aninhados em loops; a goto
declaração é evadida e difamada como um estilo de programação propenso a erros, para nunca ser usado.
Texto alternativo: "Neal Stephenson acha fofo nomear seus rótulos como 'dengo'"
Veja a história em quadrinhos original em: http://xkcd.com/292/
Porque eu aprendi isso cedo; Eu realmente não tenho nenhuma percepção ou experiência sobre quais tipos de bugs goto
realmente levam. Então, do que estamos falando aqui:
- Instabilidade?
- Código inalterável ou ilegível?
- Vulnerabilidades de segurança?
- Algo completamente diferente?
A que tipos de erros as instruções "goto" realmente levam? Existem exemplos historicamente significativos?
Respostas:
Aqui está uma maneira de ver isso que ainda não vi nas outras respostas.
É sobre escopo. Um dos principais pilares das boas práticas de programação é manter seu escopo restrito. Você precisa de um escopo restrito porque não possui a capacidade mental de supervisionar e entender mais do que apenas algumas etapas lógicas. Então você cria pequenos blocos (cada qual se torna "uma coisa") e depois constrói com eles para criar blocos maiores que se tornam uma coisa, e assim por diante. Isso mantém as coisas gerenciáveis e compreensíveis.
Um goto efetivamente infla o escopo de sua lógica para todo o programa. Isso certamente derrotará seu cérebro em qualquer um dos programas menores, abrangendo apenas algumas linhas.
Portanto, você não será capaz de dizer se cometeu mais um erro, porque há muito para absorver e verificar sua pobre cabecinha. Este é o verdadeiro problema, os erros são apenas um resultado provável.
fonte
Não é que isso
goto
seja ruim por si só. (Afinal, toda instrução de salto em um computador é um pulo.) O problema é que existe um estilo humano de programação que antecede a programação estruturada, o que poderia ser chamado de programação de "fluxograma".Na programação de fluxogramas (que as pessoas da minha geração aprenderam e foram usadas para o programa lunar Apollo), você faz um diagrama com blocos para execuções de instruções e diamantes para decisões, e pode conectá-los com linhas que se espalham por todo o lugar. (Chamado "código de espaguete").
O problema com o código espaguete é que você, o programador, pode "saber" que estava certo, mas como você pode provar isso para si mesmo ou para outras pessoas? De fato, ele pode realmente ter um mau comportamento em potencial, e seu conhecimento de que ele está sempre correto pode estar errado.
Junto veio a programação estruturada com blocos de início e fim, para, enquanto, se mais, e assim por diante. Eles tinham a vantagem de você ainda poder fazer qualquer coisa neles, mas se você fosse cuidadoso, poderia ter certeza de que seu código estava correto.
Obviamente, as pessoas ainda podem escrever código espaguete mesmo sem
goto
. O método comum é escrever awhile(...) switch( iState ){...
, onde casos diferentes definemiState
valores diferentes. De fato, em C ou C ++, pode-se escrever uma macro para fazer isso e nomeá-laGOTO
, portanto, dizer que você não está usandogoto
é uma distinção sem diferença.Como um exemplo de como a prova de código pode impedir irrestritamente
goto
, há muito tempo deparei com uma estrutura de controle útil para alterar dinamicamente as interfaces de usuário. Eu chamei de execução diferencial . É totalmente Turing universal, mas sua prova de correcção depende de programação estruturada pura - semgoto
,return
,continue
,break
, ou exceções.fonte
Por que o goto é perigoso?
goto
não causa instabilidade por si só. Apesar de cerca de 100.000goto
s, o kernel do Linux ainda é um modelo de estabilidade.goto
por si só não deve causar vulnerabilidades de segurança. No entanto, em alguns idiomas, misturá-lo com os blocos de gerenciamento detry
/catch
exceção pode levar a vulnerabilidades, conforme explicado nesta recomendação do CERT . Os principais compiladores C ++ sinalizam e evitam esses erros, mas infelizmente os compiladores mais antigos ou mais exóticos não.goto
causa código ilegível e impossível de manter. Isso também é chamado de código de espaguete , porque, como em uma placa de espaguete, é muito difícil acompanhar o fluxo de controle quando há muitos gotos.Mesmo se você conseguir evitar o código de espaguete e usar apenas alguns gotos, eles ainda facilitarão erros como o vazamento de recursos:
goto
declaração, você quebra esse fluxo direto e quebra as expectativas. Por exemplo, você pode não perceber que ainda precisa liberar recursos.goto
em lugares diferentes podem enviar você para um único alvo. Portanto, não é óbvio saber com certeza o estado em que você se encontra ao chegar a esse lugar. O risco de fazer suposições erradas / infundadas é, portanto, bastante grande.Informações e citações adicionais:
E.Dijkstra escreveu um ensaio inicial sobre o tópico já em 1968: " Ir para a declaração considerada prejudicial "
Brian.W.Kernighan & Dennis.M.Ritchie escreveram na linguagem de programação C:
James Gosling e Henry McGilton escreveram em seu white paper sobre o ambiente da linguagem Java de 1995 :
Bjarne Stroustrup define goto em seu glossário nestes termos convidativos:
Quando o goto pode ser usado?
Como K&R, eu não sou dogmático sobre gotos. Eu admito que há situações em que ir poderia facilitar a vida de alguém.
Normalmente, em C, o goto permite saída de loop multinível ou tratamento de erros que exigem atingir um ponto de saída apropriado que libere / desbloqueie todos os recursos que foram alocados até agora (alocação múltipla em sequência significa vários rótulos). Este artigo quantifica os diferentes usos do goto no kernel do Linux.
Pessoalmente, prefiro evitá-lo e, em 10 anos de C, usei no máximo 10 gotos. Prefiro usar
if
s aninhados , que acho mais legíveis. Quando isso levaria a um aninhamento muito profundo, eu optaria por decompor minha função em partes menores ou usar um indicador booleano em cascata. Os compiladores de otimização de hoje são inteligentes o suficiente para gerar quase o mesmo código que o mesmo códigogoto
.O uso de goto depende muito do idioma:
No C ++, o uso adequado do RAII faz com que o compilador destrua automaticamente objetos que ficam fora do escopo, de modo que os recursos / bloqueio sejam limpos de qualquer maneira e sem necessidade real de ir mais longe.
Em Java, não há necessidade de Goto (ver citação autor de Java acima e este excelente resposta Stack Overflow ): o coletor de lixo que limpa a bagunça,
break
,continue
, etry
/catch
o tratamento de exceção cobrir todo o caso em quegoto
poderia ser útil, mas em um mais seguro e melhor maneira. A popularidade de Java prova que a declaração goto pode ser evitada em uma linguagem moderna.Dê um zoom na famosa vulnerabilidade SSL para falhar
Isenção de responsabilidade importante: em vista da discussão acirrada nos comentários, quero esclarecer que não pretendo que a declaração goto seja a única causa desse bug. Eu não finjo que sem ir, não haveria bug. Eu só quero mostrar que um goto pode estar envolvido em um bug sério.
Não sei a quantos bugs sérios estão relacionados
goto
na história da programação: os detalhes geralmente não são comunicados. No entanto, houve um famoso bug do SSL da Apple que enfraqueceu a segurança do iOS. A afirmação que levou a esse bug foi umagoto
afirmação errada .Alguns argumentam que a causa raiz do bug não foi a declaração goto em si, mas uma cópia / pasta incorreta, uma indentação enganosa, faltando chaves no bloco condicional ou talvez os hábitos de trabalho do desenvolvedor. Não posso confirmar nenhum deles: todos esses argumentos são prováveis hipóteses e interpretação. Ninguém realmente sabe. ( enquanto isso, a hipótese de uma mesclagem que deu errado, como alguém sugeriu nos comentários, parece ser um candidato muito bom em vista de outras inconsistências de indentação na mesma função ).
O único fato objetivo é que um duplicado
goto
levou a sair prematuramente da função. Observando o código, a única outra declaração única que poderia ter causado o mesmo efeito teria sido um retorno.O erro está em função
SSLEncodeSignedServerKeyExchange()
de esse arquivo :De fato, chaves em volta do bloco condicional poderiam ter evitado o erro:
isso levaria a um erro de sintaxe na compilação (e, portanto, a uma correção) ou a um goto redundante e inofensivo. A propósito, o GCC 6 poderá detectar esses erros graças ao aviso opcional para detectar recuo inconsistente.
Mas, em primeiro lugar, todos esses gotos poderiam ter sido evitados com um código mais estruturado. Portanto, o goto é pelo menos indiretamente uma causa desse bug. Existem pelo menos duas maneiras diferentes de evitá-lo:
Abordagem 1: se cláusula ou
if
s aninhadosEm vez de testar sequencialmente muitas condições de erro e sempre que enviar para um
fail
rótulo em caso de problema, poderia-se optar por executar as operações criptográficas em umaif
declaração que o faria apenas se não houvesse uma condição prévia errada:Abordagem 2: use um acumulador de erros
Essa abordagem é baseada no fato de que quase todas as instruções aqui chamam alguma função para definir um
err
código de erro e executam o restante do código apenas seerr
for 0 (ou seja, função executada sem erro). Uma boa alternativa segura e legível é:Aqui, não há um único passo: não há risco de pular rapidamente para o ponto de saída da falha. E visualmente seria fácil identificar uma linha desalinhada ou uma esquecida
ok &&
.Essa construção é mais compacta. É baseado no fato de que em C, a segunda parte de uma lógica e (
&&
) é avaliada apenas se a primeira parte for verdadeira. De fato, o assembler gerado por um compilador de otimização é quase equivalente ao código original com gotos: O otimizador detecta muito bem a cadeia de condições e gera código, que no primeiro valor de retorno não nulo salta até o fim ( prova online ).Você pode até prever uma verificação de consistência no final da função que durante a fase de teste identifique incompatibilidades entre o sinalizador ok e o código de erro.
Erros como um erro
==0
inadvertidamente substituído por um!=0
ou conector lógico seriam facilmente detectados durante a fase de depuração.Como dito: não finjo que construções alternativas teriam evitado qualquer bug. Eu só quero dizer que eles poderiam ter tornado o bug mais difícil de ocorrer.
fonte
goto
dentro de umaif
instrução sem chave. Mas se você está sugerindo que evitargoto
tornará seu código imune aos efeitos de linhas duplicadas aleatoriamente, tenho más notícias para você.if
declaração? Tempos divertidos. :)O famoso artigo da Dijkstra foi escrito em um momento em que algumas linguagens de programação eram realmente capazes de criar sub-rotinas com vários pontos de entrada e saída. Em outras palavras, você poderia literalmente pular no meio de uma função e pular em qualquer lugar dentro da função, sem realmente chamar essa função ou retornar dela da maneira convencional. Isso ainda é verdade na linguagem assembly. Ninguém nunca argumenta que essa abordagem é superior ao método estruturado de escrever software que usamos agora.
Na maioria das linguagens de programação modernas, as funções são definidas especificamente com uma entrada e um ponto de saída. O ponto de entrada é onde você especifica os parâmetros para a função e a chama, e o ponto de saída é onde você retorna o valor resultante e continua a execução na instrução após a chamada da função original.
Dentro dessa função, você deve ser capaz de fazer o que quiser, dentro da razão. Se colocar um goto ou dois na função torna mais claro ou melhora sua velocidade, por que não? O objetivo de uma função é seqüestrar um pouco da funcionalidade claramente definida, para que você não precise mais pensar em como ela funciona internamente. Depois de escrito, basta usá-lo.
E sim, você pode ter várias instruções de retorno dentro de uma função; ainda há sempre um lugar em uma função adequada a partir da qual você retorna (a parte traseira da função, basicamente). Isso não é o mesmo que saltar de uma função com um goto antes que ela tenha a chance de retornar corretamente.
Portanto, não se trata realmente de usar goto's. É sobre evitar o abuso deles. Todos concordam que você pode criar um programa terrivelmente complicado usando gotos, mas também pode fazer o mesmo abusando de funções (é muito mais fácil abusar dos gotos).
Pelo que vale a pena, desde que me formei nos programas BASIC no estilo de número de linha para programação estruturada usando as linguagens Pascal e chaves, nunca tive que usar um goto. A única vez em que me senti tentado a usar um é fazer uma saída antecipada de um loop aninhado (em idiomas que não suportam saídas antecipadas de vários níveis a partir de loops), mas geralmente consigo encontrar outra maneira mais limpa.
fonte
Eu costumava usar
goto
declarações ao escrever programas BASIC quando criança como uma maneira simples de obter loops for e while (o Commodore 64 BASIC não tinha loops while, e eu era imaturo demais para aprender a sintaxe e o uso adequados dos loops for ) Meu código era frequentemente trivial, mas qualquer erro de loop podia ser imediatamente atribuído ao meu uso do goto.Agora eu uso principalmente o Python, uma linguagem de programação de alto nível que determinou que não precisa ir.
Quando Edsger Dijkstra declarou "Goto considerado prejudicial" em 1968, ele não deu um punhado de exemplos em que erros relacionados poderiam ser atribuídos ao goto, mas declarou que
goto
era desnecessário para idiomas de nível superior e que deveria ser evitado em favor do que hoje consideramos o fluxo de controle normal: loops e condicionais. Suas palavras:Ele provavelmente tinha montanhas de exemplos de bugs toda vez que ele depurava códigos
goto
nele. Mas seu artigo era uma declaração de posição generalizada, apoiada em provasgoto
desnecessárias para idiomas de nível superior. O erro generalizado é que você pode não ter capacidade de analisar estaticamente o código em questão.O código é muito mais difícil de analisar estaticamente com
goto
instruções, especialmente se você voltar ao seu fluxo de controle (o que eu costumava fazer) ou a alguma seção não relacionada do código. O código pode e foi escrito dessa maneira. Foi feito para otimizar altamente recursos de computação muito escassos e, portanto, as arquiteturas das máquinas.Um exemplo apócrifo
Havia um programa de blackjack que um mantenedor achou bastante otimizado de maneira elegante, mas também impossível de "consertar" devido à natureza do código. Foi programado em código de máquina que depende muito de gotos - então eu acho essa história bastante relevante. Este é o melhor exemplo canônico em que consigo pensar.
Um contra-exemplo
No entanto, a fonte C do CPython (a implementação mais comum e de referência do Python) usa
goto
declarações com grande efeito. Eles são usados para ignorar o fluxo de controle irrelevante dentro das funções para chegar ao fim das funções, tornando as funções mais eficientes sem perder a legibilidade. Isso respeita um dos ideais da programação estruturada - ter um único ponto de saída para funções.Para constar, acho esse contra-exemplo bastante fácil de analisar estaticamente.
fonte
Quando a tenda era a arte arquitetônica atual, seus construtores poderiam, sem dúvida, dar conselhos práticos sobre a construção de tendas, como deixar a fumaça escapar e assim por diante. Felizmente, os construtores de hoje provavelmente podem esquecer a maioria desses conselhos.
Quando a diligência era uma arte de transporte atual, seus motoristas poderiam, sem dúvida, dar conselhos práticos sobre cavalos de diligência, como se defender de ladrões de estrada e assim por diante. Felizmente, os motoristas de hoje também podem esquecer a maioria desses conselhos.
Quando os cartões perfurados eram arte de programação atual, seus praticantes também davam conselhos práticos sobre a organização dos cartões, como numerar declarações e assim por diante. Não tenho certeza de que esse conselho seja muito relevante hoje.
Você tem idade suficiente para conhecer a frase "to number numbers" ? Você não precisa saber, mas, se não, não está familiarizado com o contexto histórico no qual o aviso contra
goto
era principalmente relevante.O aviso contra
goto
hoje não é muito relevante. Com o treinamento básico em enquanto / para loops e chamadas de função, você nem pensa em emitir isso comgoto
muita frequência. Quando você pensa sobre isso, provavelmente tem um motivo, então vá em frente.Mas a
goto
declaração não pode ser abusada?Resposta: claro, pode ser abusado, mas seu abuso é um problema minúsculo na engenharia de software, comparado a erros muito mais comuns, como o uso de uma variável em que uma constante servirá ou a programação de recortar e colar (caso contrário, negligência em refatorar). Duvido que você esteja em muito perigo. A menos que você esteja usando
longjmp
ou transferindo o controle para um código distante, se você pensa em usar umgoto
ou gostaria de experimentá-lo por diversão, vá em frente. Você vai ficar bem.Você pode notar a falta de histórias de horror recentes nas quais
goto
interpreta o vilão. A maioria ou todas essas histórias parecem ter 30 ou 40 anos. Você se mantém firme se considera essas histórias principalmente obsoletas.fonte
Para adicionar uma coisa às outras excelentes respostas, com as
goto
declarações, pode ser difícil dizer exatamente como você chegou a um determinado local do programa. Você pode saber que ocorreu uma exceção em alguma linha específica, mas se houvergoto
no código, não há como saber quais instruções executadas resultam nessa exceção causando estado sem pesquisar o programa inteiro. Não há pilha de chamadas nem fluxo visual. É possível que exista uma declaração a 1000 linhas que o coloque em um estado incorreto e executegoto
a linha que gerou uma exceção.fonte
On Error Goto errh
, poderáResume
tentar novamente,Resume Next
ignorar a linha incorreta eErl
saber qual linha era (se você usar números de linha).Resume
executará novamente essa função desde o início eResume Next
ignorará tudo na função que ainda não tenha sido.On Error Goto 0
Depuração manualmente.Resume
deve ser usado após a verificaçãoErr.Number
e os ajustes necessários.goto
só funciona com linhas rotuladas, que parecem ter sido substituídas por loops rotulados .Goto é mais difícil para os seres humanos raciocinarem do que outras formas de controle de fluxo.
Programar o código correto é difícil. Escrever programas corretos é difícil, determinar se os programas estão corretos é difícil, provar que os programas corretos são difíceis.
Conseguir código para fazer vagamente o que você quer é fácil comparado a tudo o mais sobre programação.
goto resolve alguns programas com a obtenção de código para fazer o que você deseja. Isso não ajuda a facilitar a verificação da correção, enquanto suas alternativas costumam ajudar.
Existem estilos de programação em que o goto é a solução apropriada. Existem até estilos de programação em que seu irmão gêmeo do mal, comefrom, é a solução apropriada. Em ambos os casos, é preciso tomar muito cuidado para garantir que você o esteja usando de acordo com um padrão compreendido e muita verificação manual de que você não está fazendo algo difícil para garantir a correção.
Como exemplo, há um recurso de alguns idiomas chamado coroutines. Corotinas são threads sem thread; estado de execução sem um encadeamento para executá-lo. Você pode solicitar que eles executem, e eles podem executar parte de si mesmos e depois se suspender, devolvendo o controle de fluxo.
As rotinas "rasas" em idiomas sem suporte a rotinas (como C ++ pré-C ++ 20 e C) são possíveis usando uma mistura de gotos e gerenciamento manual de estados. As rotinas "profundas" podem ser feitas usando as funções
setjmp
elongjmp
de C.Há casos em que as corotinas são tão úteis que vale a pena escrevê-las manualmente e com cuidado.
No caso do C ++, eles são considerados úteis o suficiente para estender a linguagem para suportá-los. O
goto
gerenciamento manual e de estado está sendo oculto por trás de uma camada de abstração de custo zero, permitindo que os programadores os escrevam sem a dificuldade de provar que sua bagunça de goto,void**
s, construção / destruição manual de estado etc. está correta.O goto fica escondido atrás de uma abstração de nível superior, como
while
oufor
ouif
ouswitch
. Essas abstrações de nível superior são mais fáceis de provar e corrigir.Se o idioma estava faltando alguns deles (como algumas linguagens modernas estão faltando corotinas), usar um padrão que não se encaixa no problema ou usar goto torna-se suas alternativas.
Conseguir que um computador faça vagamente o que você quer fazer em casos comuns é fácil . Escrever código robusto e confiável é difícil . Ir para o primeiro ajuda muito mais do que para o segundo; portanto, "foi considerado prejudicial", pois é um sinal de código "superficialmente superficial", com erros profundos e difíceis de rastrear. Com esforço suficiente, você ainda pode criar código com "goto" confiável e robusto; portanto, manter a regra como absoluta está errada; mas como regra geral, é uma boa.
fonte
longjmp
é legal apenas em C para recuperar a pilha de volta para asetjmp
em uma função pai. (Como romper com vários níveis de aninhamento, mas para chamadas de função em vez de loops).longjjmp
para um contexto de umsetjmp
feito em uma função que retornou desde então não é legal (e suspeito que não funcione na prática na maioria dos casos). Não estou familiarizado com as co-rotinas, mas, a partir da descrição de uma co-rotina semelhante a um encadeamento de espaço do usuário, acho que ele precisaria de sua própria pilha e de um contexto de registro salvo, além delongjmp
fornecer apenas o registro -saving, não uma pilha separada.Dê uma olhada neste código, em http://www-personal.umich.edu/~axe/research/Software/CC/CC2/TourExec1.1.f.html, que na verdade fazia parte da simulação de um grande dilema do prisioneiro. Se você já viu códigos FORTRAN ou BASIC antigos, perceberá que não é tão incomum.
Há muitos problemas aqui que vão muito além da declaração do GOTO aqui; Sinceramente, acho que a declaração do GOTO foi um bode expiatório. Mas o fluxo de controle não é absolutamente claro aqui, e o código é misturado de maneiras que tornam muito claro o que está acontecendo. Mesmo sem adicionar comentários ou usar nomes de variáveis melhores, alterar isso para uma estrutura de blocos sem GOTOs tornaria muito mais fácil ler e seguir.
fonte
Um dos princípios da programação sustentável é o encapsulamento . O ponto é que você faz interface com um módulo / rotina / sub-rotina / componente / objeto usando uma interface definida, e somente essa interface, e os resultados serão previsíveis (assumindo que a unidade foi efetivamente testada).
Dentro de uma única unidade de código, o mesmo princípio se aplica. Se você aplicar consistentemente a programação estruturada ou os princípios de programação orientada a objetos, não irá:
Alguns dos sintomas mais comuns desses erros de processamento incluem vazamentos de memória, acumulação de memória, estouros de ponteiros, falhas, registros de dados incompletos, exceções de adição / alteração de registro / exclusão de registro, falhas de página de memória e assim por diante.
As manifestações observáveis pelo usuário desses problemas incluem bloqueio da interface do usuário, desempenho gradualmente reduzido, registros de dados incompletos, incapacidade de iniciar ou concluir transações, dados corrompidos, interrupções na rede, interrupções de energia, falhas nos sistemas incorporados (da perda do controle de mísseis à perda da capacidade de rastreamento e controle nos sistemas de controle de tráfego aéreo), falhas no temporizador e assim por diante. O catálogo é muito extenso.
fonte
goto
nem uma vez. :)Ir é perigoso porque geralmente é usado onde não é necessário. Usar qualquer coisa que não seja necessária é perigoso, mas vá especialmente. Se você pesquisar no Google, encontrará muitos erros causados pelo goto, isso não é um motivo para não usá-lo (os erros sempre acontecem quando você usa recursos de linguagem porque isso é intrínseco à programação), mas alguns deles são claramente altamente relacionados para ir ao uso.
Razões para usar / não usar goto :
Se você precisar de um loop, precisará usar
while
oufor
.Se você precisar fazer um salto condicional, use
if/then/else
Se você precisar de um procedimento, chame uma função / método.
Se você precisar sair de uma função, apenas
return
.Posso contar com os dedos locais onde vi
goto
usados e usados corretamente.No libKTX, existe uma função que possui o seguinte código
Agora neste local
goto
é útil porque o idioma é C:Esse caso de uso é útil, pois em C não temos classes, portanto, a maneira mais simples de limpar é usando a
goto
.Se tivéssemos o mesmo código em C ++, não há mais necessidade de ir para:
Que tipo de erros ele pode levar? Qualquer coisa. Loops infinitos, ordem de execução incorreta, parafuso de pilha ..
Um caso documentado é uma vulnerabilidade no SSL que permitiu ataques Man in the middle causados pelo uso incorreto do goto: aqui está o artigo
Esse é um erro de digitação, mas passou despercebido por um tempo, se o código fosse estruturado de outra maneira, testado corretamente, esse erro poderia não ter sido possível.
fonte