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

return void();

É uma pequeneza mas eu gostaria muito que as linguagens da família do C permitissem retornar void quando a função retorna void . Escrever da forma a seguir me aborrece. public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null)      {        log("x was null");        return;      }      bar(x, "default");    } } Eu ficaria mais feliz escrevendo assim: public class Foo  {    public void log (String msg) { }    public void bar (String x, String y) { }    public void bar (String x)    {      if (x == null) return log("x was null");      bar(x, "default");     } }

.NET COM Interop, IStream e Visual Studio

Investigando oportunidades para otimizar um componente, experimentei definir métodos que não solicitam dados (em arrays etc.) como argumentos mas sim instâncias de IStream , e me deparei com um problema interessante: interoperabilidade com .NET. Usar componentes COM em programas .NET não é particularmente difícil; no Visual Studio, é possível adicionar componentes COM como referências no projeto. Se o componente for capaz de Automation, é possível usar a ferramenta tlbimp.exe para gerar manualmente um callable wrapper para o componente, que, se eu entendi as coisas corretamente, não precisa ser distribuído com a aplicação. Porém, todos os métodos que eu aprendi tem essa peculiaridade: eles parecem assumir que um componente não usa interfaces definidas por outros componentes -- como, por exemplo, declarando parâmetros com tipo IStream , uma interface definida pelo sistema. Pelo método do callable wrapper , a ferramenta assume que as interfaces mencionadas integram o componente...

Eclipse PDE com Gradle

Na virada de 2016 para 2017, adotamos oficialmente o Gradle como ferramenta de construção de software na Prodist. A fila da migração está fluindo confortavelmente desde então. Já temos as provas de conceito necessárias para todos os projetos, inclusive os projetos nativos para compilador C++. Para fontes Java usamos Eclipse. Integramos projetos Gradle ao Eclipse usando Buildship . Acho que o Buildship ainda tem um longo caminho pela frente, mas realiza tudo o que necessitamos. Já estamos entregando com base nesse esquema. O problema mais importante que encontramos foi na integração de projetos OSGi. No Eclipse, trabalhamos projetos OSGi com o PDE. Nosso ciclo de desenvolvimento inclui rodar configurações "OSGi Framework" no Eclipse para teste. Infelizmente, o PDE assume uma estrutura de projeto própria e não funciona corretamente caso contrário. Estes problemas estão registrados em bugs como, por exemplo, o  bug 153023 . Nossos projetos OSGi com Gradle usam o layout ...