Pular para o conteúdo principal

Por que goto é considerado prejudicial?

Recentemente, o Fabiano Vasconcelos abriu uma discussão no Grupo de Usuários de C e C++:
De cara eu vi algo aqui um pouco estranho, se que o amigo Márcio me permite comentar: que muitos programadores, inclusive eu (se que posso ser rotulado como programador) foram instruídos com o princípio de NUNCA usar o goto, por ser considerado um mau estilo de programação.
Durante a discussão, o Eduardo Vieira puxou um artigo da KernelTrap sobre uma discussão similar ocorrida no grupo de desenvolvimento do Linux, onde Robert Wilken disse o seguinte:
In general, if you can structure your code properly, you should never need a goto, and if you don't need a goto you shouldn't use it. It's just "common sense" as I've always been taught. Unless you're intentionally trying to write code that's harder for others to read.
É preciso colocar a máxima "goto considered harmful" na perspectiva histórica adequada.

Acredito que praticamente todo programador treinado nos últimos vinte anos aprendeu a programar com if e while. Essas são as estruturas básicas de programas e aparecem rapidamente nos manuais e nos cursos de programação. Toda linguagem de programação moderna tem if e while, e variantes como switch, for etc.

Essas coisas são chamadas estruturas de controle porque fazem exatamente isso: controlam a execução do programa, às vezes indo para uma sequência de sentenças, às vezes indo para outra, de acordo com testes explícitos. Arranjar um programa com sequências de sentenças, if e while é fazer programação estruturada.

Agora, a não ser que algum curso maravilhoso e desconhecido por mim tenha história da programação no seu currículo, este programador provavelmente não percebe que if e while nem sempre existiram e que a programação estruturada foi inventada mais ou menos na década de 70.

Durante a discussão no grupo de desenvolvimento do Linux, Scott Robert Ladd disse o seguinte:
Your attitude against "goto" is perhaps based upon an excellent but dated article, "Goto Considered Harmful", written by Edsger W. Dijkstra, and published by the ACM in 1968. (A recent reprint can be found at http://www.acm.org/classics/oct95/.) As you can tell from the date, this article predates modern programming languages and idioms; it comes from a time when Fortran ruled, and before Fortran 77 provided significant tools for avoiding spaghetti code.
e mais o seguinte:
Used over short distances with well-documented labels, a "goto" can be more effective, faster, and cleaner than a series of complex flags or other constructs. The "goto" may also be safer and more intuitive than the alternative. A "break" is a goto; a "continue" is a "goto" -- these are statements that move the point of execution explicitly.
Dijkstra, e outros, iniciaram a pequena revolta estruturada e assim ocorreu que diversas linguagens de programação introduziram novidades como if e while. Mas como é possível programar sem if e while?

Considere este fragmento:

char * i = input;
while (*i != '\0')
 if (*i == SPECIAL) goto exit;
 else ++i;
:exit
return i;

Este fragmento _não_ é o objeto da objeção ao uso de goto. Este fragmento exibe honrada programação estruturada. goto está ali como bem poderia estar break. Tanto faz.

Agora, observe este fragmento:

char * i = input;
:loop
if (*i == '\0') goto exit;
if (*i == SPECIAL) goto exit;
++i;
goto loop;
:exit
return i;

Isto é o que você deve evitar.

O objetivo do combate ao goto era combater a programação não-estruturada, e esse combate foi vencido com a introdução das linguagens estruturadas. Tais linguagens levam o programador naturalmente ao caminho certo e proíbem os maiores absurdos.

Existem linguagens em que o último fragmento acima era _a única alternativa_. (Ou coisa pior.)

Em retrospectiva, sabemos que a programação não-estruturada dominante na época causava um modo de pensar não-estruturado sobre os programas, de modo que o "spagetthi code" clássico era um único fluxo de instruções com gotos arbitrários para cima e para baixo, algo muito pior que o último fragmento acima, o tipo de código que eu não saberia construir artificialmente.

Se você conhece e pratica a programação de acordo com os bons princípios da programação estruturada, não precisa temer o goto. Existem diversas situações excepcionais às quais as estruturas de controle do C não se adequam perfeitamente; aquela que me ocorre com mais frequência são switches dentro de for quando é preciso terminar o for dentro de um case.

for (char * i = input; *i != '\0'; ++i)
 switch (state)
 {
 case FOO:
  /* many things */
  break;
 case BAR:
  if (*i == SPECIAL) goto exit;
  /* many things */
  break;
 }
exit:

Sempre existe uma maneira de remover o goto; nem sempre essa maneira é desejável. O goto acima, cujo label está exatamente na saída do laço, me parece uma parte bastante adequada da estrutura de controle. Java, que não possui goto, permite dizer "break exit", com exatamente o mesmo significado.

Durante a discussão no grupo de desenvolvimento do Linux, Robert Love disse o seguinte sobre a substituição do goto por outras estruturas:
As a final argument, it does not let us cleanly do the usual stack-esque wind and unwind, i.e.
do A
if (error) goto out_a;
do B
if (error) goto out_b;
do C
if (error) goto out_c;
goto out;
out_c:
undo C
out_b:
undo B:
out_a:
undo A
out:
return ret;
Now stop this.

Comentários

Postagens mais visitadas deste blog

Análise vs. Projeto

Eu diria que a ruptura entre Análise e o Projeto acontece quando acabam as considerações sobre o problema e começam as considerações sobre a solução. Em outras palavras, o papel do Analista é considerar o problema, enquanto o papel do Projetista é o de considerar a solução. Assim, o Analista aparece quando um problema aparece, e dá lugar ao Projetista quando o problema foi devidamente analisado, restando produzir para ele uma solução. E diria que a ruptura entre a Especificação e a Implementação acontece quando acabam os artefatos para consumo por humanos e começam os artefatos para consumo por máquinas. Em outras palavras, o papel do Especificador é produzir artefatos para consumo por humanos, enquanto o papel do Implementador é produzir artefatos para consumo por máquinas. Assim, o Especificador aparece quando a atividade é produzir efeitos sobre humanos, e dá lugar ao Implementador quando os humanos estão devidamente satisfeitos e a atividade prossegue para produzir efeito...

JNDI e Plan 9

Acabei de realizar quão similar é o J2EE e o Plan 9 quando observados pela lente da arquitetura de componentes: no Plan 9, o sistema de arquivos é tão capaz quanto o JNDI no J2EE. Um componente no qual eu estou trabalhando agora depende de um componente de banco de dados e de um componente de correio eletrônico. O modo de ligação entre esses componentes se dá através de JNDI: meu componente faz referência aos nomes "dataSource" e "mailSession" no seu contexto local de nomes. Na configuração do componente em uma instalação, esses nomes locais são ligados aos nomes globais que o administrador preparou para oferecer as dependências. É claro que tanto o meu componente quanto suas dependências mantém um contrato entre si, na forma das interfaces java EntityManager e Session. No Plan 9, meu componente seria um programa que faz referência a dois arquivos no seu file system -- um arquivo que repr...

O Problema de Gestão de I/O

A palestra que dei no Sétimo Encontro do Grupo de Usuários de C e C++ do Brasil apresentou meu resultado atual na pesquisa de um framework baseado em uma forma de orientação a aspectos em C++ descrita no "Modern C++ Design" do Alexandrescu. Minha palestra não apresentou, porém, o que é exatamente o problema a resolver, assumindo que fosse razoavelmente conhecido. Seu foco foi apresentar todos os mecanismos disponíveis para se resolver o problema no Linux 2.6, suas peculiaridades e seus aspectos comuns. Uma certa razão me motivou, após esta apresentação, a rebobinar meu raciocínio e considerar o problema original. [1] O que há exatamente de tão problemático na gestão de I/O em um programa? A imagem inicial é a de um programa cujo objetivo é interagir com um dos dispositivos ligados ao computador. A forma básica dessa interação é a cópia de dados do dispositivo para a memória e da memória para o dispositivo, sendo que trabalho útil por parte do programa ocorre enquanto os...