Eu sempre soube que isso goto
é algo ruim, trancado em um porão em algum lugar para nunca ser visto de vez, mas encontrei hoje um exemplo de código que faz todo sentido goto
.
Eu tenho um IP no qual preciso verificar se está dentro de uma lista de IPs e prosseguir com o código, caso contrário, lance uma exceção.
<?php
$ip = '192.168.1.5';
$ips = [
'192.168.1.3',
'192.168.1.4',
'192.168.1.5',
];
foreach ($ips as $i) {
if ($ip === $i) {
goto allowed;
}
}
throw new Exception('Not allowed');
allowed:
...
Se eu não usar goto
, tenho que usar alguma variável como
$allowed = false;
foreach ($ips as $i) {
if ($ip === $i) {
$allowed = true;
break;
}
}
if (!$allowed) {
throw new Exception('Not allowed');
}
Minha pergunta é o que há de tão ruim goto
quando é usado em casos tão óbvios e imo relevantes?
Respostas:
O GOTO em si não é um problema imediato, são as máquinas de estado implícitas que as pessoas tendem a implementar com ele. No seu caso, você deseja um código que verifique se o endereço IP está na lista de endereços permitidos, portanto
portanto, seu código deseja verificar uma condição. O algoritmo para implementar essa verificação não deve ser motivo de preocupação aqui, no espaço mental do seu programa principal, a verificação é atômica. É assim que deve ser.
Mas se você colocar o código que faz a verificação no seu programa principal, você o perde. Você introduz um estado mutável, explicitamente:
onde
$list_contains_ip
é sua única variável de estado ou implicitamente:Como você vê, há uma variável de estado não declarada na construção GOTO. Isso não é um problema em si, mas essas variáveis de estado são como pedras: carregar uma não é difícil, carregar uma sacola cheia delas fará você suar. Seu código não permanecerá o mesmo: no próximo mês, você será solicitado a diferenciar os endereços público e privado. No mês seguinte, seu código precisará oferecer suporte a intervalos de IP. No próximo ano, alguém solicitará que você suporte endereços IPv6. Em pouco tempo, seu código ficará assim:
E quem precisar depurar esse código amaldiçoará você e seus filhos.
Dijkstra coloca assim:
E é por isso que o GOTO é considerado prejudicial.
fonte
$list_contains_ip = false;
afirmação parece extraviadaExistem alguns casos de uso legítimos para
GOTO
. Por exemplo, para manipulação e limpeza de erros em C ou para implementar algumas formas de máquinas de estado. Mas este não é um desses casos. O segundo exemplo é o IMHO mais legível, mas ainda mais legível seria extrair o loop para uma função separada e retornar quando você encontrar uma correspondência. Ainda melhor seria (no pseudocódigo, eu não sei a sintaxe exata):Então, o que há de tão ruim
GOTO
nisso? A programação estruturada usa funções e estruturas de controle para organizar o código, para que a estrutura sintática reflita a estrutura lógica. Se algo for executado apenas condicionalmente, ele aparecerá em um bloco de instruções condicionais. Se algo for executado em um loop, ele aparecerá em um bloco de loop.GOTO
permite contornar a estrutura sintática saltando arbitrariamente, tornando o código muito mais difícil de seguir.Obviamente, se você não tiver outra escolha
GOTO
, mas se o mesmo efeito puder ser alcançado com funções e estruturas de controle, é preferível.fonte
it's easier to write good optimizing compilers for "structured" languages.
Cuidado para elaborar? Como um contra-exemplo, o pessoal da Rust introduziu o MIR , uma representação intermediária no compilador que substitui especificamente loops, continuações, quebras e outros por gotos, porque é mais fácil verificar e otimizar.goto
com um ninho de ratos de sinalizadores e condicionais (como no segundo exemplo do OP), é muito menos legível do que juts usando agoto
.Como outros já disseram, o problema não está no
goto
próprio; o problema está em como as pessoas usamgoto
e em como isso pode dificultar o entendimento e a manutenção do código.Suponha o seguinte trecho de código:
Para que valor é impresso
i
? Quando é impresso? Até que você responda a todas as instâncias dagoto label
sua função, você não poderá saber. A simples presença desse rótulo destrói sua capacidade de depurar código por meio de uma simples inspeção. Para pequenas funções com uma ou duas ramificações, não há muito problema. Para funções não pequenas ...No início dos anos 90, recebemos uma pilha de código C que dirigia uma tela gráfica em 3D e nos mandava fazê-la funcionar mais rapidamente. Eram apenas cerca de 5000 linhas de código, mas todas estavam dentro
main
, e o autor usou cerca de 15 ou maisgoto
ramificações nas duas direções. Este era um código ruim , para começar, mas a presença delesgoto
tornava tudo muito pior. O meu colega de trabalho levou cerca de duas semanas para descobrir o fluxo de controle. Melhor ainda, essesgoto
resultados resultaram em um código tão fortemente associado a si mesmo que não pudemos fazer nenhuma alteração sem quebrar alguma coisa.Tentamos compilar com a otimização de nível 1, e o compilador consumiu toda a RAM disponível, depois toda a troca disponível e depois entrou em pânico no sistema (que provavelmente não tinha nada a ver com o
goto
próprio s, mas eu gosto de contar essa história).No final, demos ao cliente duas opções - vamos reescrever tudo do zero ou comprar hardware mais rápido.
Eles compraram hardware mais rápido.
Regras do Bode para usar
goto
:if
oufor
ouwhile
declaração);goto
no lugar de uma estrutura de controleHá casos em que a
goto
é a resposta certa, mas eles são raros (romper um loop profundamente aninhado é o único lugar em que eu o uso).EDITAR
Expandindo essa última declaração, aqui está um dos poucos casos de uso válidos para
goto
. Suponha que temos a seguinte função:Agora, temos um problema - e se uma das
malloc
chamadas falhar no meio? Por mais improvável que seja um evento, não queremos retornar uma matriz parcialmente alocada, nem queremos apenas sair da função com um erro; queremos limpar a nós mesmos e desalocar qualquer memória parcialmente alocada. Em um idioma que lança uma exceção em uma alocação incorreta, isso é bastante simples - basta escrever um manipulador de exceções para liberar o que já foi alocado.Em C, você não possui manipulação de exceção estruturada; você deve verificar o valor de retorno de cada
malloc
chamada e executar a ação apropriada.Podemos fazer isso sem usar
goto
? É claro que podemos - isso requer apenas um pouco de contabilidade extra (e, na prática, esse é o caminho que eu seguiria). Mas, se você está procurando lugares onde o uso de umgoto
não é imediatamente um sinal de má prática ou design, esse é um dos poucos.fonte
goto
mesmo em conformidade com estas regras, eu não iria usá-lo em toda a menos que seja a única forma de ramificação disponível no idioma, mas eu posso ver que não seria demasiado de um pesadelo para debuggoto
é se eles foram escritos de acordo com essas regras.GOTO
, incluindo os famosos gotos aritméticos e atribuídos a Fortran, se bem me lembro.return
,break
,continue
Ethrow
/catch
são essencialmente gotos - todos eles de controle de transferência para outro pedaço de código e tudo poderia ser implementado com gotos - na verdade eu fiz isso uma vez em um projeto da escola, um instrutor PASCAL estava dizendo quanto melhor Pascal era de básico por causa da estrutura ... então eu tinha que ser contrário ...A coisa mais importante sobre engenharia de software (vou usar esse termo sobre codificação para se referir a uma situação em que você está sendo pago por alguém para criar uma base de código junto com outros engenheiros que exigem melhoria e manutenção contínuas) é tornar o código legível - - fazer algo é quase secundário. Seu código será escrito apenas uma vez, mas, na maioria dos casos, as pessoas passarão dias e semanas revisitando / reaprendendo, melhorando e corrigindo - e toda vez que elas (ou você) terão que começar do zero e tentar lembrar / descobrir seu código.
A maioria dos recursos que foram adicionados aos idiomas ao longo dos anos é tornar o software mais sustentável, não mais fácil de escrever (embora alguns idiomas sigam nessa direção - geralmente causam problemas de longo prazo ...).
Comparado a declarações de controle de fluxo semelhantes, os GOTOs podem ser quase tão fáceis de seguir da melhor maneira possível (um único pulo usado em um caso como o sugerido) e um pesadelo quando abusados - e são facilmente abusados ...
Então, depois de lidar com pesadelos de espaguete por alguns anos, apenas dissemos "Não", como comunidade, não vamos aceitar isso - muitas pessoas estragam tudo se houver um pouco de espaço - esse é realmente o único problema com eles. Você pode usá-los ... mas, mesmo que seja o caso perfeito, o próximo cara assumirá que você é um péssimo programador, porque você não entende a história da comunidade.
Muitas outras estruturas foram desenvolvidas apenas para tornar seu código mais compreensível: Funções, Objetos, Escopo, Encapsulamento, Comentários (!) ... bem como padrões / processos mais importantes como "DRY" (impedindo a duplicação) e "YAGNI" (Reduzindo a generalização / complicação excessiva de código) - tudo realmente importa apenas para o PRÓXIMO cara ler seu código (quem provavelmente será você - depois que você esqueceu a maior parte do que fez em primeiro lugar!)
fonte
return
break
continue
throw
catch
GOTO
é uma ferramenta. Pode ser usado para o bem ou para o mal.Nos velhos tempos ruins, com FORTRAN e BASIC, era quase a única ferramenta.
Ao analisar o código daqueles dias, quando você vê um,
GOTO
precisa descobrir por que ele está lá. Pode fazer parte de um idioma padrão que você pode entender rapidamente ... ou pode fazer parte de alguma estrutura de controle de pesadelo que nunca deveria ter sido. Você não sabe até olhar, e é fácil estar enganado.As pessoas queriam algo melhor e estruturas de controle mais avançadas foram inventadas. Eles cobriam a maioria dos casos de uso, e as pessoas que foram queimadas por maus
GOTO
queriam bani-los completamente.Ironicamente,
GOTO
não é tão ruim quando é raro. Quando você vê um, sabe que há algo especial acontecendo e é fácil encontrar o rótulo correspondente, pois é o único rótulo por perto.Avanço rápido de hoje. Você é um professor ensinando programação. Você poderia dizer "Na maioria dos casos, você deve usar as novas construções avançadas, mas, em alguns casos, uma simples
GOTO
pode ser mais legível". Os alunos não vão entender isso. Eles vão abusarGOTO
para criar código ilegível.Em vez disso, você diz "
GOTO
ruim.GOTO
Mal.GOTO
Falha no exame". Os alunos entenderão isso !fonte
Com exceção de
goto
, todas as construções de fluxo no PHP (e na maioria das linguagens) têm escopo hierarquicamente.Imagine algum código examinado através dos olhos apertados:
Independentemente do que construção de controlo
foo
é (if
,while
, etc), há apenas certos pedidos permitidos paraa
,b
ec
.Você pode ter
a
-b
-c
, oua
-c
, ou mesmoa
-b
-b
-b
-c
. Mas você nunca poderia terb
-c
oua
-b
-a
-c
.... a menos que você tenha
goto
.goto
(em particular ao contráriogoto
) pode ser problemático o suficiente para deixá-lo em paz e usar construções de fluxo hierárquicas e bloqueadas.goto
s têm um lugar, mas principalmente como micro-otimizações em idiomas de baixo nível. IMO, não há um bom lugar para isso em PHP.Para sua informação, o código de exemplo pode ser escrito ainda melhor do que qualquer uma das suas sugestões.
fonte
Em idiomas de baixo nível, o GOTO é inevitável. Porém, em alto nível, isso deve ser evitado (no caso de o idioma suportar), pois dificulta a leitura dos programas .
Tudo se resume a tornar o código mais difícil de ler. Idiomas de alto nível são supostos para facilitar a leitura do código do que idiomas de baixo nível como, por exemplo, assembler ou C.
O GOTO não causa aquecimento global nem causa pobreza no terceiro mundo. Isso apenas torna o código mais difícil de ler.
A maioria das linguagens modernas possui estruturas de controle que tornam o GOTO desnecessário. Alguns como Java nem sequer o têm.
De fato, o termo código spaguetti vem de causas de código complicadas e difíceis de seguir por estruturas de ramificação não estruturadas.
fonte
goto
pode fazer código ansioso para ler. Quando você criou variáveis extras e ramificações aninhadas versus um único salto, o código é muito mais difícil de ler e entender.goto
surgir não são exemplos de códigos complicados de espaguete, mas coisas bastante simples, muitas vezes simulando padrões de fluxo de controle em idiomas que não tem a sintaxe: os dois exemplos mais comuns são romper com vários loops e o exemplo no OP ondegoto
é usado para implementarfor
-else
.goto
também é ótimo para itens como repetição de exceção, reversão e máquinas de estado.Nada de errado com as
goto
próprias declarações. Os erros estão com algumas das pessoas que usam inadequadamente a declaração.Além do que JacquesB disse (tratamento de erros em C), você está usando
goto
para sair de um loop não aninhado, algo que você pode fazer usandobreak
. Nesse caso, é melhor você usarbreak
.Mas se você tivesse um cenário de loop aninhado, o uso
goto
seria mais elegante / simples. Por exemplo:O exemplo acima não faz sentido no seu problema específico, porque você não precisa de um loop aninhado. Mas espero que você veja apenas parte do loop aninhado.
Ponto bonous: se sua lista de IPs é pequena, seu método está correto . Mas se a lista aumentar, saiba que sua abordagem tem uma pior complexidade assintótica de tempo de execução de O (n) . À medida que sua lista cresce, convém usar um método diferente que atinja O (log n) (como uma estrutura em árvore) ou O (1) (uma tabela de hash sem colisões).
fonte
break
econtinue
com um número (para interromper / continuar tantas camadas de loops) ou um rótulo (para interromper / continuar o loop com esse rótulo), um dos quais geralmente deve ser preferível a usargoto
para loops aninhados.Verdade. Não se importe.
Verdade. Não se importe.
Verdade. Não se importe.
Verdade. No entanto, eu tenho sido esse codificador disciplinado. Eu já vi o que acontece com o código ao longo do tempo.
Goto
começa bem. Então surge o desejo de reutilizar o código. Logo, eu me vejo em um ponto de interrupção sem nenhuma pista do que está acontecendo, mesmo depois de analisar o estado do programa.Goto
torna difícil raciocinar sobre código. Temos trabalhado criação realmente difícilwhile
,do while
,for
,for each
switch
,subroutines
,functions
, e mais tudo porque fazendo essas coisas comif
egoto
é difícil para o cérebro.Então não. Nós não queremos olhar
goto
. Claro que está vivo e bem no binário, mas não precisamos ver isso na fonte. De fato,if
está começando a parecer um pouco instável.fonte
Goto
permite abstrair um problema, tornando-o outro problema de outro código.If
permite escolher essa abstração. Existem maneiras melhores de abstrair e maneiras melhores de escolher a abstração. Polimorfismo para um.As linguagens assembly geralmente têm apenas saltos condicionais / incondicionais (o equivalente a GOTO. Implementações mais antigas do FORTRAN e BASIC não tinham instruções de bloco de controle além de uma iteração contada (o loop DO), deixando todo o outro fluxo de controle para IFs e GOTOs. esses idiomas foram encerrados por uma declaração numerada e, como resultado, o código escrito para esses idiomas poderia ser, e muitas vezes era, difícil de seguir e propenso a erros.
Para enfatizar o argumento, existe a declaração " COME FROM " , que foi inventada com facilidade .
Praticamente não há necessidade de usar o GOTO em linguagens como C, C ++, C #, PASCAL, Java, etc .; podem ser usadas construções alternativas que quase certamente serão igualmente eficientes e muito mais sustentáveis. É verdade que um GOTO em um arquivo de origem não será um problema. O problema é que não é preciso muito para dificultar o acompanhamento de uma unidade de código e manter o erro. É por isso que a sabedoria aceita é evitar o GOTO sempre que possível.
Este artigo da wikipedia sobre a declaração goto pode ser útil
fonte