paulo1205
(usa Ubuntu)
Enviado em 09/02/2016 - 22:05h
Eu sei que o tópico já foi marcado como resolvido, mas eu gostaria de deixar alguns comentários finais.
(1) Longe de mim atacar o grande Dijkstra. O
paper mencionado tinha uma preocupação séria e relevante (e que permanece relevante e séria até os dias de hoje) com a possibilidade de garantir, de um ponto de vista formal, matemático mesmo, que um programa esteja correto. É uma preocupação no campo teórico, mas com consequências práticas muito pertinentes. A pergunta marcante é “como o fluxo de execução chegou até aqui?”, e é fato que o uso indiscriminado de
goto torna mais difícil responder a essa pergunta, tanto para o matemático que tenta aplicar métodos formais de análise quanto para o programador que se esforça para encontrar
bugs num código escrito por ele próprio ou por terceiros. Nesse sentido, o
paper está em pleno contato com a realidade.
O problema é quando, após teorizar sobre a dificuldade, sugerem-se medidas extremas. Se o
aedes eagypti pode se reproduzir em caixas d'água mal instaladas (i.e. sem tampa), vamos proibir todas as caixas d'água? Usando lógica análoga à proposta de Dijkstra a respeito de
goto , provavelmente a resposta seria “sim”. Afinal de contas, é teoricamente possível limpar o corpo, a casa ou qualquer objeto usando apenas lenços umedecidos, certo?
(2) Não sou, por outro lado, um ardoroso defensor de
goto . Ele deve ser usado tão somente quando ele for
A melhor alternativa, considerando-se ao mesmo tempo vários aspectos, incluindo consumo de recursos, desempenho e legibilidade.
(3)
goto não é o único elemento da linguagem que se pode alegar como desvio dos princípios de programação estruturada, do modo como seus proponentes mais ardorosos e puristas costumam propor. Se fosse para rezar pela sua cartilha, todos os blocos, tanto de funções como de laços de repetição e de execução condicional, deveriam ser sempre executados até o final.
Isso implicaria que o C deveria perder imediatamente não apenas o
goto , mas também os comandos
break e
continue , e o comando
return se tornaria redundante pois, como o único ponto de saída da função seria o seu final, seria possível assumir o valor da última expressão avaliada como valor de retorno, como fazem o Shell, Perl e algumas outras linguagens. Além disso, o comando
switch deveria mudar de semântica: em vez de ter
labels , com possibilidade de compartilhamento de código entre diferentes opções (“
fall-through ”), ter-se-ia de fato fluxos de execução completamente separados para cada alternativa.
Com uma linguagem assim capada (fosse por força de design ou pela escolha do programador que deseje seguir de modo purista a maneira de pensar de programação estruturada), todo programa seria obrigado a ter muitos mais testes condicionais e mais variáveis de controle, ou, como alternativa ou como complemento, a criar funções pequeninhas. Provavelmente aumentaria também o uso de ponteiros, ou se criaria passagem por referência, como se fez em Pascal (as referências de C++ existem por outro motivo).
(4) Por curiosidade, por conta deste tópico, eu fiz uma varredura em todos os programas em C, C++ e Perl que escrevi e ainda tenho no meu micro, arquivados desde 1988. O resultado foi o seguinte.
Linguagem | Arquivos | Com goto | Nº Linhas | Nº gotos
-----------+----------+----------+-----------+----------
C | 241 | 10 | 32872 | 42
C++ | 219 | 1 | 38053 | 2
Perl | 134 | 15 | 40345 | 63
-----------+----------+----------+-----------+----------
TOTAL | 594 | 26 | 111270 | 107
Uso em C:
Tipo | Arquivos | % Arqs. | Nº gotos | % Linhas
--------------------------+----------+---------+----------+----------
Tratamento de erro | 8 | 3,1395 | 36 | 0,10952
Parar laços multi-níveis | 1 | 0,4149 | 1 | 0,00304
Parser | 1 | 0,4149 | 5 | 0,01521
--------------------------+----------+---------+----------+----------
TOTAL | 10 | 4,1494 | 42 | 0,12777
Uso em C++:
Tipo | Arquivos | % Arqs. | Nº gotos | % Linhas
--------------------------+----------+---------+----------+----------
Tratamento de erro em | 1 | 0,4566 | 2 | 0,00526
destrutor | | | |
--------------------------+----------+---------+----------+----------
TOTAL | 1 | 0,4566 | 2 | 0,00526
Uso em Perl:
Tipo | Arquivos | % Arqs. | Nº gotos | % Linhas
--------------------------+----------+---------+----------+----------
Tratamento de erro | 12 | 8,9552 | 58 | 0,14376
Parar laços multi-níveis | 1 | 0,7463 | 1 | 0,00248
Sucesso prematuro(*) | 2 | 1,4925 | 4 | 0,00991
--------------------------+----------+---------+----------+----------
TOTAL | 15 | 11,194 | 63 | 0,15615
(*) “Sucesso prematuro” é como eu chamei a situação em que um
processamento relativamente longo pôde ser abreviado porque
o dado desejado calhou de vir num formato adequado antes de
chegar ao fim do processamento. É uma espécie de exceção
às avessas, um dado excepcionalmente bom.
O fato de C++ possuir exceções ajuda a evitar
goto s. De fato, a única ocorrência de
goto num código meu em C++ vem justamente do único lugar em que C++ não permite deixar que exceções apareçam para outros pontos do programa, que é na execução de um destrutor. Eu até poderia ter feito o código desse destrutor lançando uma exceção para si mesmo (a exceção só não poderia escapar para fora do bloco do destrutor), mas seria como usar um canhão para matar um mosquito. Uma vez que é para desviar o fluxo para dentro da mesma função e é muito menos custoso usar
goto do que exceções, foi melhor usar
goto .
C e Perl não possuem exceções (Perl diz que tem, usando a construção “
eval BLOCK”, mas elas não funcionam -- ou não funcionavam -- muito bem), e isso ajuda a explicar o número maior de
goto s nessas linguagens. O caso é ainda um pouco mais grave em Perl por causa do objetivo dos programas: a maioria dos programas que fiz em Perl era para gerar relatórios grandes e voltados à web, às vezes mesmo como CGI. Nesses casos, não dá para simplesmente matar o programa na hora e exibir uma mensagem de erro para o usuário, mas até o erro precisa ser devidamente bem formatado.