Eu realmente hesito em perguntar isso, porque não quero "solicitar debate, argumentos, sondagens ou discussão prolongada", mas sou novo em C e quero obter mais informações sobre os padrões comuns usados na linguagem.
Recentemente, ouvi uma certa aversão ao goto
comando, mas também encontrei recentemente um caso de uso decente para ele.
Código como este:
error = function_that_could_fail_1();
if (!error) {
error = function_that_could_fail_2();
if (!error) {
error = function_that_could_fail_3();
...to the n-th tab level!
} else {
// deal with error, clean up, and return error code
}
} else {
// deal with error, clean up, and return error code
}
Se a parte da limpeza for muito parecida, poderia ser escrito um pouco mais bonito (minha opinião?) Assim:
error = function_that_could_fail_1();
if(error) {
goto cleanup;
}
error = function_that_could_fail_2();
if(error) {
goto cleanup;
}
error = function_that_could_fail_3();
if(error) {
goto cleanup;
}
...
cleanup:
// deal with error if it exists, clean up
// return error code
Esse é um caso de uso comum ou aceitável goto
em C? Existe uma maneira diferente / melhor de fazer isso?
c
design-patterns
goto
Robz
fonte
fonte
goto hell;
Respostas:
A
goto
instrução (e seus rótulos correspondentes) são uma primitiva de controle de fluxo (junto com a execução condicional de uma instrução). Com isso, quero dizer que eles estão lá para permitir que você construa redes de controle de fluxo de programas. Você pode pensar neles como modelar as setas entre os nós de um fluxograma.Alguns deles podem ser otimizados imediatamente, onde há um fluxo linear direto (você apenas usa uma sequência de instruções básicas). Outros padrões são melhor substituídos por construções de programação estruturada, quando disponíveis; se parecer um
while
loop, use umwhile
loop , ok? Os padrões de programação estruturada são definitivamente pelo menos potencialmente mais claros de intenção do que uma bagunça degoto
declarações.No entanto, C não inclui todas as possíveis construções de programação estruturada. (Não está claro para mim que todos os relevantes já foram descobertos; a taxa de descoberta é lenta agora, mas eu hesitaria em dizer que todos foram encontrados.) Dos que conhecemos, C definitivamente não tem o
try
/catch
/finally
estrutura (e também excepções). Também não possui vários níveisbreak
de loop. Esses são os tipos de coisas quegoto
podem ser usados para implementar. É possível usar outros esquemas para fazer isso também - sabemos que C possui um conjunto suficiente degoto
primitivas - mas geralmente envolvem a criação de variáveis de flag e condições de loop ou guarda muito mais complexas; aumentar o emaranhado da análise de controle com a análise de dados dificulta a compreensão geral do programa. Isso também dificulta a otimização do compilador e a execução rápida da CPU (a maioria das construções de controle de fluxo - e definitivamentegoto
- é muito barata).Portanto, embora você não deva usar a
goto
menos que seja necessário, deve estar ciente de que ela existe e que pode ser necessária e, se você precisar, não deve se sentir muito mal. Um exemplo de caso em que é necessário é a desalocação de recursos quando uma função chamada retorna uma condição de erro. (Ou seja,try
/finally
.) É possível escrever que, semgoto
fazer isso, pode haver desvantagens próprias, como os problemas de manutenção. Um exemplo do caso:O código pode ser ainda mais curto, mas é suficiente para demonstrar o ponto.
fonte
try/catch/finally
quegoto
apesar da (como ele pode se espalhar por todo múltiplas funções / módulos) forma semelhante, ainda mais difundida de código espaguete isso é possível usandotry/catch/finally
?Sim.
É usado, por exemplo, no kernel do linux. Aqui está um email do final de um tópico de quase uma década atrás , destacando o meu:
Dito isso, requer muita disciplina para não criar código espaguete depois que você se acostumar a usar o goto; portanto, a menos que você esteja escrevendo algo que exija velocidade e pouco espaço na memória (como um kernel ou sistema incorporado), você realmente deve pense nisso antes de escrever o primeiro passo.
fonte
Na minha opinião, o código que você postou é um exemplo de uso válido
goto
, porque você apenas pula para baixo e o usa como um manipulador de exceção primitivo.No entanto , por causa do velho debate sobre o goto, os programadores evitam
goto
há cerca de 40 anos e, portanto, não são usados para ler código com o goto. Esta é uma razão válida para evitar ir para lá: simplesmente não é o padrão.Eu teria reescrito o código como algo mais facilmente lido pelos programadores em C:
Vantagens deste projeto:
fonte
Um artigo famoso que descreve o caso de uso válido de foi o Structured Programming with GOTO Statement, de Donald E. Knuth (Universidade de Stanford). O artigo apareceu nos dias em que o uso do GOTO era considerado pecado por alguns e quando o movimento para a Programação Estruturada estava no auge. Você pode dar uma olhada no GoTo Considerado Prejudicial.
fonte
Em Java, você faria assim:
Eu uso muito isso. Por mais que eu não
goto
goste, na maioria das outras linguagens de estilo C, uso seu código; não há outra boa maneira de fazer isso. (Saltar de loops aninhados é um caso semelhante; em Java, uso um rotuladobreak
e em qualquer outro lugar em que uso umgoto
.)fonte
Eu acho que é um caso de uso decente, mas no caso de "erro" nada mais é que um valor booleano, há uma maneira diferente de realizar o que você deseja:
Isso faz uso da avaliação de curto-circuito de operadores booleanos. Se isso "melhor", depende do seu gosto pessoal e de como você está acostumado a esse idioma.
fonte
error
o valor desse valor pode se tornar sem sentido com todos os ORs.O guia de estilo linux fornece razões específicas para o uso de
goto
acordo com o seu exemplo:https://www.kernel.org/doc/Documentation/process/coding-style.rst
Isenção de responsabilidade Não devo compartilhar meu trabalho. Os exemplos aqui são um pouco inventados, então tenha paciência comigo.
Isso é bom para o gerenciamento de memória. Recentemente, trabalhei em códigos que alocavam memória dinamicamente (por exemplo, um
char *
retornado por uma função). Uma função que analisa um caminho e verifica se o caminho é válido analisando os tokens do caminho:Agora, para mim, o código a seguir é muito melhor e mais fácil de manter, se você precisar adicionar um
varNplus1
:Agora, o código tinha todos os tipos de problemas com ele, ou seja, N estava acima de 10, e a função tinha mais de 450 linhas, com 10 níveis de aninhamento em alguns lugares.
Mas eu ofereci ao meu supervisor para refatorá-lo, o que eu fiz e agora são várias funções curtas, e todas elas têm o estilo linux
Se considerarmos o equivalente sem
goto
s:Para mim, no primeiro caso, é óbvio para mim que, se a primeira função retornar
NULL
, estaremos fora daqui e voltaremos0
. No segundo caso, eu tenho que rolar para baixo para ver que o if contém toda a função. Concedido o primeiro indica isso estilisticamente (o nome "out
") e o segundo faz sintaticamente. O primeiro é ainda mais óbvio.Além disso, eu prefiro ter
free()
instruções no final de uma função. Isso ocorre em parte porque, na minha experiência, asfree()
declarações no meio de funções cheiram mal e indicam para mim que eu deveria criar uma sub-rotina. Nesse caso, eu crieivar1
em minha função e nãofree()
consegui em uma sub-rotina, mas é por isso que ogoto out_free
estilo de sair é tão prático.Eu acho que os programadores precisam ser educados acreditando que
goto
são maus. Então, quando estiverem maduros o suficiente, devem procurar o código fonte do Linux e ler o guia de estilo do Linux.Devo acrescentar que uso esse estilo de maneira muito consistente, toda função tem um int
retval
, umout_free
label e um out label. Devido à consistência estilística, a legibilidade é aprimorada.Bônus: quebra e continua
Digamos que você tenha um loop while
Há outras coisas erradas nesse código, mas uma coisa é a instrução continue. Eu gostaria de reescrever a coisa toda, mas fui encarregado de modificá-la em um pequeno caminho. Levaria dias para refatorá-lo de uma maneira que me satisfizesse, mas a mudança real foi de cerca de meio dia de trabalho. O problema é que, mesmo se nós '
continue
' ainda precisamos libertarvar1
evar2
. Eu tive que adicionar umvar3
, e isso me fez querer vomitar ter que espelhar as declarações free ().Eu era um estagiário relativamente novo na época, mas estava olhando o código-fonte do Linux há algum tempo, então perguntei ao meu supervisor se eu poderia usar uma declaração goto. Ele disse que sim, e eu fiz isso:
Eu acho que continua bom, na melhor das hipóteses, mas para mim eles são como um goto com uma etiqueta invisível. O mesmo vale para pausas. Eu ainda preferiria continuar ou interromper, a menos que, como foi o caso aqui, o force a espelhar modificações em vários lugares.
E devo acrescentar que esse uso
goto next;
e onext:
rótulo são insatisfatórios para mim. Eles são meramente melhores do que espelhar ofree()
's e ascount++
declarações.goto
Quase sempre estão errados, mas é preciso saber quando eles são bons de usar.Uma coisa que não discuti é o tratamento de erros, coberto por outras respostas.
atuação
Pode-se observar a implementação de strtok () http://opensource.apple.com//source/Libc/Libc-167/string.subproj/strtok.c
Por favor, corrija-me se estiver errado, mas acredito que o
cont:
rótulo e agoto cont;
declaração existem para desempenho (eles certamente não tornam o código mais legível). Eles podem ser substituídos por código legível fazendopular delimitadores. Mas, para ser o mais rápido possível e evitar chamadas de função desnecessárias, elas fazem dessa maneira.
Eu li o jornal de Dijkstra e acho bastante esotérico.
google "dijkstra goto declaração considerada prejudicial" porque não tenho reputação suficiente para postar mais de 2 links.
Eu o vi citado como uma razão para não usar o goto e a leitura dele não mudou nada, na medida em que meus usos do goto são considerados.
Adendo :
Eu criei uma regra clara enquanto pensava em tudo isso sobre continuações e interrupções.
Nem sempre é possível devido a problemas de escopo, mas descobri que isso facilita muito o raciocínio sobre meu código. Eu havia notado que sempre que um loop de tempo interrompia ou continuava, isso me dava um mau pressentimento.
fonte
Pessoalmente, eu refatoraria isso mais ou menos assim:
Isso seria mais motivado ao evitar o aninhamento profundo do que evitar o goto (no entanto, o IMO é um problema pior com o primeiro exemplo de código) e, é claro, dependeria da possibilidade de CleanupAfterError estar fora do escopo (neste caso, "parâmetros" seja uma estrutura contendo alguma memória alocada que você precisa liberar, um ARQUIVO * que você precisa fechar ou o que for).
Uma grande vantagem que vejo com essa abordagem é que é mais fácil e mais limpo dividir uma etapa extra hipotética futura entre, digamos, FTCF2 e FTCF3 (ou remover uma etapa atual existente), para que ela se melhore melhor com a manutenção (e a pessoa que herda meu código, não querendo me linchar!) - vá para o lado, a versão aninhada não tem isso.
fonte
Dê uma olhada nas diretrizes de codificação C da MISRA (Motor Industry Software Reliability Association) que permitem ir sob critérios rígidos (que seu exemplo atende)
Onde eu trabalho, o mesmo código seria escrito - não é preciso ir - Evitar um debate religioso desnecessário sobre eles é uma grande vantagem em qualquer software house.
ou para "goto in drag" - algo ainda mais desonesto do que goto, mas contorna o "No goto ever !!!" acampamento) "Certamente deve estar OK, não usa Goto" ....
Se as funções tiverem o mesmo tipo de parâmetro, coloque-as em uma tabela e use um loop -
fonte
if(flag)
cópia copie o ramo "if" e que as instruções correspondentes na outra cópia utilizem o " outro". Ações que definem e limpam a bandeira são realmente "gotos" que saltam entre essas duas versões do código. Há momentos em que o uso de sinalizadores é mais limpo do que qualquer alternativa, mas adicionar um sinalizador para salvar umgoto
alvo não é uma boa opção.Também uso
goto
se odo/while/continue/break
hackery alternativo seria menos legível.goto
s têm a vantagem de seus alvos terem um nome e leremgoto something;
. Isso pode ser mais legível do quebreak
oucontinue
se você não está realmente parando ou continuando alguma coisa.fonte
do ... while(0)
ou outra construção que não seja um loop real, mas uma tentativa contida de impedir o uso de agoto
.fonte
break
funciona exatamente comogoto
, embora não tenha estigma.Sempre haverá campos que dizem que uma maneira é aceitável e outra que não é. As empresas para as quais trabalhei desaprovaram ou fortemente desencorajaram o uso. Pessoalmente, não consigo pensar em nenhum momento em que usei um, mas isso não significa que eles sejam ruins , apenas outra maneira de fazer as coisas.
Em C, normalmente faço o seguinte:
Para o processamento, usando seu exemplo goto, eu faria o seguinte:
error = function_that_could_fail_1 (); if (! error) {error = function_that_could_fail_2 (); } if (! error) {error = function_that_could_fail_3 (); }
Não há aninhamento e, dentro das cláusulas if, você pode fazer qualquer relatório de erros, se a etapa gerar um erro. Portanto, não precisa ser "pior" do que um método usando gotos.
Eu ainda tenho que me deparar com um caso em que alguém tem gotos que não podem ser feitos com outro método e é tão legível / compreensível e essa é a chave, IMHO.
fonte