quinta-feira, 1 de setembro de 2011

Vamos usar goto?


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! :)

2 comentários:

  1. 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:

    i = 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

    ResponderExcluir
  2. Na verdade é essa a ideia...
    Se 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

    ResponderExcluir