Meu Deus! Isso é coisa do capeta!
Muito se fala mal do bom e velho goto. Os professores abominam seu uso, o Java não tem (embora o use indiretamente por meio dos breaks) e a maioria dos livros mandam você manter distância desta função. Até mesmo o velho Dijkstra escreveu um artigo intitulado "Go To Statement Considered Harmful", então se ele falou está falado, não é?
Mas não é bem assim. O ódio ao goto pode ser explicado por um contexto histórico. O bom e velho Assembly não possui if/then/else, while, for ou coisa do tipo. O Fortran, nos seus primórdios, também não possuía. Todos os laços e decisões do código eram feitos por meio de go tos. Assim, os primeiros programadores de C estavam acostumados com gotos e os utilizavam sem dó. Isso é um convite para transformar seu código em um belo dum espaguete, impossível de ser compreendido.
Porém os tempos são outros. Usar gotos para decisões e laços é, sim, uma bela duma merda, mas existem situações em que o goto pode te ajudar a construir um código mais limpo.
Vamos ao exemplo:
Exemplo 1:
//Procurando Nemo
for ( i = 0; i < 1000; ++i )
{
for ( j = 0; j < 1000; ++j )
{
for ( k = 0; k < 1000; ++k )
{
//faz milhares de coisas aqui
if ( oceano[i][j][k] == NEMO )
{
//Encontrei! Quero cair fora daqui!
}
}
}
}
}
Ok, você encontrou Nemo. E agora, como sair dessa?
Se colocar um break, somente o laço do k será cortado. O restante continuará sendo executado.
Outra solução seria marcar uma flag como nemoEncontrado = true dentro do if e verificar em cada for, mas não seria lá a solução mais bonita do mundo.
E por que não colocar um goto aqui?
//Procurando Nemo
for ( i = 0; i < 1000; ++i )
{
for ( j = 0; j < 1000; ++j )
{
for ( k = 0; k < 1000; ++k )
{
//faz milhares de coisas aqui
if ( oceano[i][j][k] == NEMO )
{
//Encontrei! Quero cair fora daqui!
goto FimBuscaNemo;
}
}
}
}
}
FimBuscaNemo:
…
Pronto, um bom uso do goto :)
Outro exemplo seria para funções com múltiplos retornos.
É uma boa prática que cada função possua apenas 1 ponto de retorno, mas às vezes isso acarreta em um monte de ifs encadeados. Mais uma vez o goto pode te ajudar:
Exemplo 2:
int func()
{
AlocaUmMonteDeCoisaAqui();
if ( abrirArquivo() == false )
{
{
DesalocaTudoQueAlocou();
return ERRO_ABRIR_ARQUIVO;
}
if ( arquivoVazio() == true )
{
DesalocaTudoQueAlocou();
return ERRO_ARQUIVO_VAZIO;
}
if ( checkSum() == false )
{
DesalocaTudoQueAlocou();
return ERRO_CHECKSUM;
}
if ( tamanhoArquivo() < 100 )
{
DesalocaTudoQueAlocou();
return ERRO_TAMANHO_INVALIDO;
}
//faz um monte de coisa
DesalocaTudoQueAlocou();
return BELEZA;
}
}
Este é um exemplo muito comum no dia a dia. A função aloca um monte de coisa no começo (poderia bloquear semáforos também) e para cada retorno precisa desalocar tudo. É também um erro muito comum um programador ir dar manutenção, colocar um novo return em algum ponto e esquecer de chamar a DesalocaTudoQueAlocou().
Uma função mais elegante e segura poderia ser escrita como:
int func()
{
int retval = BELEZA;
AlocaUmMonteDeCoisaAqui();
if ( abrirArquivo() == false )
{
{
retval = ERRO_ABRIR_ARQUIVO;
goto Fim_Func;
}
if ( arquivoVazio() == true )
{
retval = ERRO_ARQUIVO_VAZIO;
goto Fim_Func;
}
if ( checkSum() == false )
{
retval = ERRO_CHECKSUM;
goto Fim_Func;
}
if ( tamanhoArquivo() < 100 )
{
retval = ERRO_TAMANHO_INVALIDO;
goto Fim_Func;
}
//faz um monte de coisa
Fim_Func:
DesalocaTudoQueAlocou();
return retval;
}
}
Aí está, outro uso saudável do goto!
Ou seja, não precisa ter medo da linguagem. Basta apenas usá-la corretamente.
Duas regras básicas devem ser seguidas quanto ao uso desta função:
1 – Só salte para linhas a frente da atual, e não para trás;
2 – Possua, no máximo, dois pontos de destino em cada função.
Com isso, você pode se divertir a vontade!
É isso!
P.s.: Como somos politicamente incorretos, não poderíamos deixar de falar do COMEFROM, que é basicamente o oposto do GOTO!
O COMEFROM surgiu como uma piada de Assembly:
Não possui nenhuma utilidade a não ser bagunçar seu código, mas se você quiser se exibir para os outros, aqui vai um port do COMEFROM para C:
#define _(A) goto A;
#define COME_FROM(A) A:
int main()
{
int x = 0;
int y = 0;
COME_FROM( fim_loop );
++x;
++y;
_( fim_loop )
return 0;
}
Boa sorte! :)
A regra "só salte para frente" não deve, em minha opinião, ser seguida muito à risca. Loops, em C, comumente são traduzidos como um salto para frente e diversos para trás:
ResponderExcluiri = 0;
while (i != 3)
{
i++;
}
Pode ser traduzida pelo compilador como:
xor eax,eax
jmp test
loop:
inc eax
test:
cmp eax,3
jnz loop
De fato, a especificação dos processadores Intel dizem claramente que existem 2 tipos de branch prediction e que o salto para trás tem a tendência de ter interpretado como acontecido antes que a instrução jnz seja executada...
[]s
Na verdade é essa a ideia...
ResponderExcluirSe você quiser saltar para trás, deve utilizar while ou for, e não goto.
Tem uma regra do misra e do JSF++ que comenta isso.
Abraço