Existem boas razões pelas quais é uma prática melhor ter apenas uma declaração de retorno em uma função?
Ou está certo retornar de uma função assim que estiver logicamente correto, significando que pode haver muitas instruções de retorno na função?
language-agnostic
coding-style
Tim Post
fonte
fonte
Respostas:
Costumo ter várias instruções no início de um método para retornar para situações "fáceis". Por exemplo, isto:
... pode ficar mais legível (IMHO) assim:
Então, sim, acho que é bom ter vários "pontos de saída" de uma função / método.
fonte
DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
Ninguém mencionou ou citou Code Complete, então eu o farei.
17.1 retorno
Minimize o número de retornos em cada rotina . É mais difícil entender uma rotina se, lendo-a na parte inferior, você não tem consciência da possibilidade de que ela retorne a algum lugar acima.
Use um retorno quando melhorar a legibilidade . Em certas rotinas, depois de saber a resposta, você deseja devolvê-la à rotina de chamadas imediatamente. Se a rotina for definida de tal forma que não exija nenhuma limpeza, não retornar imediatamente significa que você precisa escrever mais código.
fonte
Eu diria que seria incrivelmente imprudente decidir arbitrariamente contra vários pontos de saída, pois eu achei a técnica útil na prática repetidamente , na verdade, muitas vezes refatorei o código existente para vários pontos de saída para maior clareza. Podemos comparar as duas abordagens assim:
Compare isso com o código em que vários pontos de saída estão permitidos: -
Eu acho que o último é consideravelmente mais claro. Tanto quanto posso dizer, a crítica a vários pontos de saída é um ponto de vista bastante arcaico atualmente.
fonte
Atualmente, estou trabalhando em uma base de código onde duas das pessoas que trabalham nela assinam cegamente a teoria do "ponto único de saída" e posso dizer que, por experiência, é uma prática horrível. Isso torna o código extremamente difícil de manter e eu mostrarei o porquê.
Com a teoria do "ponto único de saída", você inevitavelmente acaba com um código parecido com este:
Isso não apenas torna o código muito difícil de seguir, mas agora, digamos mais tarde, você precisa voltar e adicionar uma operação entre 1 e 2. Você precisa recuar quase toda a função, e boa sorte para garantir que todos suas condições e chaves if / else são correspondidas corretamente.
Esse método torna a manutenção do código extremamente difícil e propensa a erros.
fonte
A programação estruturada diz que você só deve ter uma instrução de retorno por função. Isso é para limitar a complexidade. Muitas pessoas, como Martin Fowler, argumentam que é mais simples escrever funções com várias instruções de retorno. Ele apresenta esse argumento no livro clássico de refatoração que ele escreveu. Isso funciona bem se você seguir os outros conselhos e escrever pequenas funções. Eu concordo com esse ponto de vista e apenas puristas estritos de programação estruturada aderem a declarações de retorno únicas por função.
fonte
GOTO
para mover o fluxo de controle, mesmo quando a função existia. Nunca diz "nunca useGOTO
".Como Kent Beck observa ao discutir as cláusulas de guarda nos Padrões de implementação que fazem uma rotina ter um único ponto de entrada e saída ...
Acho uma função escrita com cláusulas de guarda muito mais fácil de seguir do que um longo monte de
if then else
instruções aninhadas .fonte
Em uma função que não tem efeitos colaterais, não há boas razões para obter mais de um retorno e você deve escrevê-las em um estilo funcional. Em um método com efeitos colaterais, as coisas são mais seqüenciais (indexadas por tempo), então você escreve em um estilo imperativo, usando a instrução de retorno como um comando para interromper a execução.
Em outras palavras, quando possível, favorece esse estilo
por cima disto
Se você se encontra escrevendo várias camadas de condições aninhadas, provavelmente há uma maneira de refatorar isso, usando a lista de predicados, por exemplo. Se você achar que seus ifs e elses estão distantes sintaticamente, convém dividir isso em funções menores. É difícil ler um bloco condicional que abrange mais de uma tela cheia de texto.
Não existe uma regra rígida que se aplique a todos os idiomas. Algo como ter uma única declaração de retorno não tornará seu código bom. Mas um bom código tenderá a permitir que você escreva suas funções dessa maneira.
fonte
Eu já vi isso nos padrões de codificação para C ++ que eram uma ressaca do C, como se você não tivesse RAII ou outro gerenciamento automático de memória, então seria necessário limpar para cada retorno, o que significa recortar e colar da limpeza ou do goto (logicamente o mesmo que 'finalmente' nos idiomas gerenciados), ambos considerados ruins. Se suas práticas são usar ponteiros e coleções inteligentes em C ++ ou outro sistema de memória automática, não há um motivo forte para isso, e tudo se resume à legibilidade e mais uma decisão de julgamento.
fonte
auto_ptr
, você pode usar ponteiros simples em paralelo. Embora fosse estranho escrever código 'otimizado' com um compilador não otimizador em primeiro lugar.try
...finally
em Java) e você precisar fazer a manutenção de recursos, você poderia fazer com um único retornar no final de um método. Antes de fazer isso, considere seriamente refatorar o código para se livrar da situação.Eu me apóio à ideia de que declarações de retorno no meio da função são ruins. Você pode usar retornos para criar algumas cláusulas de guarda na parte superior da função e, é claro, informar ao compilador o que retornar no final da função sem problemas, mas é possível perder e retornar no meio da função. tornar a função mais difícil de interpretar.
fonte
Sim , existem:
A questão geralmente é colocada como uma falsa dicotomia entre retornos múltiplos ou declarações if profundamente aninhadas. Quase sempre há uma terceira solução que é muito linear (sem aninhamento profundo) com apenas um único ponto de saída.
Atualização : Aparentemente, as diretrizes MISRA também promovem uma saída única .
Para ser claro, não estou dizendo que é sempre errado ter vários retornos. Mas, dadas as soluções equivalentes, existem muitas boas razões para preferir a que tem um único retorno.
fonte
Contract.Ensures
com vários pontos de retorno.goto
para obter um código de limpeza comum, provavelmente simplificou a função para que exista umreturn
no final do código de limpeza. Então, você poderia dizer que resolveu o problemagoto
, mas eu diria que resolveu simplificando para um únicoreturn
.Ter um único ponto de saída fornece uma vantagem na depuração, porque permite definir um único ponto de interrupção no final de uma função para ver qual valor será realmente retornado.
fonte
Em geral, tento ter apenas um único ponto de saída de uma função. Porém, há momentos em que isso acaba criando um corpo de função mais complexo do que o necessário; nesse caso, é melhor ter vários pontos de saída. Realmente precisa ser uma "decisão de julgamento" com base na complexidade resultante, mas o objetivo deve ser o menor número possível de pontos de saída, sem sacrificar a complexidade e a compreensibilidade.
fonte
Não, porque não vivemos mais na década de 1970 . Se sua função é longa o suficiente para que vários retornos sejam um problema, é muito longo.
(Além do fato de que qualquer função de várias linhas em um idioma com exceções terá vários pontos de saída de qualquer maneira.)
fonte
Minha preferência seria pela saída única, a menos que isso realmente complique as coisas. Descobri que, em alguns casos, vários pontos existentes podem mascarar outros problemas de design mais significativos:
Ao ver esse código, eu perguntaria imediatamente:
Dependendo das respostas a essas perguntas, pode ser que
Nos dois casos acima, o código provavelmente pode ser reformulado com uma asserção para garantir que 'foo' nunca seja nulo e que os chamadores relevantes sejam alterados.
Há duas outras razões (acho que o código C ++ específico) em que múltiplas existem realmente podem ter um efeito negativo . Eles são tamanho do código e otimizações do compilador.
Um objeto que não seja POD C ++ no escopo na saída de uma função terá seu destruidor chamado. Onde existem várias instruções de retorno, pode ser que haja objetos diferentes no escopo e, portanto, a lista de destruidores a serem chamados será diferente. O compilador, portanto, precisa gerar código para cada instrução de retorno:
Se o tamanho do código for um problema - isso pode ser algo que vale a pena evitar.
A outra questão diz respeito à "Otimização do valor de retorno nomeado" (também conhecida como Copy Elision, ISO C ++ '03 12.8 / 15). O C ++ permite que uma implementação pule a chamada do construtor de cópia, se puder:
Tomando o código como está, o objeto 'a1' é construído em 'foo' e, em seguida, sua construção de cópia será chamada para construir 'a2'. No entanto, a cópia elision permite que o compilador construa 'a1' no mesmo local na pilha que 'a2'. Portanto, não há necessidade de "copiar" o objeto quando a função retornar.
Vários pontos de saída complicam o trabalho do compilador na tentativa de detectar isso e, pelo menos para uma versão relativamente recente do VC ++, a otimização não ocorreu onde o corpo da função teve vários retornos. Consulte Otimização de valor de retorno nomeado no Visual C ++ 2005 para obter mais detalhes.
fonte
throw new ArgumentNullException()
em C # neste caso), gostei muito de suas outras considerações, todas são válidas para mim e podem ser críticas em alguns casos. contextos de nicho.foo
está sendo testado não tem nada a ver com o assunto, que é se fazerif (foo == NULL) return; dowork;
ouif (foo != NULL) { dowork; }
Ter um único ponto de saída reduz a Complexidade Ciclomática e, portanto, em teoria , reduz a probabilidade de que você introduza erros no seu código ao alterá-lo. A prática, no entanto, tende a sugerir que é necessária uma abordagem mais pragmática. Portanto, tenho a tendência de ter um único ponto de saída, mas permiti que meu código tivesse vários, se isso for mais legível.
fonte
Eu me forço a usar apenas uma
return
declaração, pois, de certa forma, gerará cheiro de código. Deixe-me explicar:(As condições são arbritárias ...)
Quanto mais condições, maior a função, mais difícil é a leitura. Portanto, se você estiver sintonizado com o cheiro do código, perceberá e desejará refatorar o código. Duas soluções possíveis são:
Devoluções múltiplas
Funções separadas
Concedido, é mais longo e um pouco confuso, mas no processo de refatorar a função dessa maneira, temos
fonte
Eu diria que você deve ter quantos forem necessários ou qualquer um que torne o código mais limpo (como cláusulas de guarda ).
Eu pessoalmente nunca ouvi / vi nenhuma "prática recomendada" dizer que você deve ter apenas uma declaração de retorno.
Na maioria das vezes, costumo sair de uma função o mais rápido possível com base em um caminho lógico (as cláusulas de guarda são um excelente exemplo disso).
fonte
Acredito que vários retornos geralmente são bons (no código que escrevo em C #). O estilo de retorno único é uma reserva de C. Mas você provavelmente não está codificando em C.
Não há lei exigindo apenas um ponto de saída para um método em todas as linguagens de programação . Algumas pessoas insistem na superioridade desse estilo, e às vezes o elevam a uma "regra" ou "lei", mas essa crença não é apoiada por nenhuma evidência ou pesquisa.
Mais de um estilo de retorno pode ser um mau hábito no código C, onde os recursos precisam ser desalocados explicitamente, mas linguagens como Java, C #, Python ou JavaScript que têm construções como coleta automática de lixo e
try..finally
blocos (eusing
blocos em C # ) e esse argumento não se aplica - nesses idiomas, é muito incomum precisar da desalocação manual centralizada de recursos.Há casos em que um único retorno é mais legível e casos em que não é. Veja se isso reduz o número de linhas de código, torna a lógica mais clara ou reduz o número de chaves e recuos ou variáveis temporárias.
Portanto, use quantos retornos forem adequados às suas sensibilidades artísticas, pois é um problema de layout e legibilidade, não técnico.
Eu falei sobre isso mais detalhadamente no meu blog .
fonte
Há coisas boas a dizer sobre ter um único ponto de saída, assim como há coisas ruins a dizer sobre a inevitável programação "flecha" resultante.
Se estiver usando vários pontos de saída durante a validação de entrada ou alocação de recursos, tento colocar todas as 'saídas de erro' muito visivelmente na parte superior da função.
O artigo Spartan Programming do "SSDSLPedia" e o ponto de saída de função única artigo de do "Wiki do Portland Pattern Repository" têm alguns argumentos interessantes sobre isso. Além disso, é claro, existe este post a considerar.
Se você realmente deseja um único ponto de saída (em qualquer idioma habilitado para não-exceção), por exemplo, para liberar recursos em um único local, acho que a aplicação cuidadosa de goto é boa; veja, por exemplo, este exemplo bastante artificial (compactado para salvar o espaço da tela):
Pessoalmente, em geral, não gosto mais de programar flechas do que de vários pontos de saída, embora ambos sejam úteis quando aplicados corretamente. O melhor, é claro, é estruturar seu programa para não exigir nenhum. Dividir sua função em vários pedaços geralmente ajuda :)
Embora, ao fazer isso, descubra que acabe com vários pontos de saída de qualquer maneira, como neste exemplo, onde alguma função maior foi dividida em várias funções menores:
Dependendo do projeto ou das diretrizes de codificação, a maior parte do código da placa da caldeira pode ser substituída por macros. Como observação lateral, dividi-la dessa maneira torna as funções g0, g1, g2 muito fáceis de serem testadas individualmente.
Obviamente, em uma linguagem OO e habilitada para exceção, eu não usaria declarações if como essa (ou de todo, se eu pudesse fazer isso com pouco esforço), e o código seria muito mais claro. E não-flecha. E a maioria dos retornos não finais provavelmente seriam exceções.
Em resumo;
fonte
Você conhece o ditado - a beleza está nos olhos de quem vê .
Algumas pessoas juram pelo NetBeans e outras pelo IntelliJ IDEA , outras pelo Python e outras pelo PHP .
Em algumas lojas, você pode perder o emprego se insistir em fazer isso:
A questão é toda sobre visibilidade e manutenção.
Sou viciado em usar álgebra booleana para reduzir e simplificar a lógica e o uso de máquinas de estado. No entanto, havia colegas do passado que acreditavam que meu emprego de "técnicas matemáticas" na codificação é inadequado, porque não seria visível e sustentável. E isso seria uma má prática. Desculpe pessoal, as técnicas que emprego são muito visíveis e sustentáveis para mim - porque, quando voltasse ao código seis meses depois, entenderia claramente o código, ao invés de ver uma bagunça de espaguete proverbial.
Ei, amigo (como costumava dizer um cliente anterior), faça o que quiser, desde que saiba como consertar quando eu precisar que você conserte.
Lembro-me de 20 anos atrás, um colega meu foi demitido por empregar o que hoje seria chamado de estratégia de desenvolvimento ágil . Ele tinha um plano incremental meticuloso. Mas o gerente dele estava gritando: "Você não pode liberar recursos progressivamente para os usuários! Você deve ficar com a cascata ". Sua resposta ao gerente foi que o desenvolvimento incremental seria mais preciso para as necessidades do cliente. Ele acreditava no desenvolvimento para as necessidades dos clientes, mas o gerente acreditava na codificação da "exigência do cliente".
Somos frequentemente culpados por quebrar a normalização de dados, os limites de MVP e MVC . Nós alinhamos em vez de construir uma função. Tomamos atalhos.
Pessoalmente, acredito que o PHP é uma má prática, mas o que eu sei. Todos os argumentos teóricos se resumem a tentar cumprir um conjunto de regras
Todas as outras regras desaparecem em segundo plano. E é claro que essa regra nunca desaparece:
fonte
Eu me inclino a usar cláusulas de guarda para retornar cedo e sair no final de um método. A regra de entrada e saída única tem importância histórica e foi particularmente útil ao lidar com código legado que rodava em 10 páginas A4 para um único método C ++ com vários retornos (e muitos defeitos). Mais recentemente, a boa prática aceita é manter os métodos pequenos, o que torna as saídas múltiplas menos uma impedância ao entendimento. No exemplo a seguir do Kronoz copiado de cima, a questão é o que ocorre em // Restante do código ... ?:
Sei que o exemplo é um tanto artificial, mas ficaria tentado a refatorar o loop foreach em uma instrução LINQ que poderia ser considerada uma cláusula de guarda. Mais uma vez, em um exemplo artificial a intenção do código não é aparente e someFunction () pode ter qualquer outro efeito colateral ou o resultado pode ser utilizado no // Resto do código ... .
Fornecendo a seguinte função refatorada:
fonte
null
vez de lançar uma exceção indicando que o argumento não foi aceito?Um bom motivo para pensar é na manutenção do código: você tem um único ponto de saída. Se você deseja alterar o formato do resultado, ..., é muito mais simples de implementar. Além disso, para depuração, você pode simplesmente colocar um ponto de interrupção lá :)
Dito isto, certa vez tive que trabalhar em uma biblioteca onde os padrões de codificação impunham 'uma declaração de retorno por função' e achei muito difícil. Eu escrevo muitos códigos de cálculos numéricos, e geralmente existem 'casos especiais', então o código acabou sendo muito difícil de seguir ...
fonte
Vários pontos de saída são adequados para funções pequenas o suficiente - ou seja, uma função que pode ser visualizada em uma tela inteira. Se uma função longa também inclui vários pontos de saída, é um sinal de que a função pode ser dividida ainda mais.
Dito isto, evito funções de saída múltipla, a menos que seja absolutamente necessário . Senti dor de bugs devido a algum retorno perdido em alguma linha obscura em funções mais complexas.
fonte
Eu trabalhei com terríveis padrões de codificação que forçaram um único caminho de saída para você, e o resultado quase sempre é espaguete não estruturado, se a função for qualquer coisa menos trivial - você acaba com muitas pausas e continua que apenas atrapalham.
fonte
if
declaração na frente de cada chamada de método que retorna sucesso ou não :(Ponto de saída único - todas as outras coisas iguais - torna o código significativamente mais legível. Mas há um problema: construção popular
é falso, "res =" não é muito melhor que "retorno". Ele possui uma declaração de retorno único, mas vários pontos em que a função realmente termina.
Se você possui uma função com vários retornos (ou "res =" s), geralmente é uma boa ideia dividi-la em várias funções menores com um único ponto de saída.
fonte
Minha política usual é ter apenas uma declaração de retorno no final de uma função, a menos que a complexidade do código seja bastante reduzida ao adicionar mais. Na verdade, sou um fã de Eiffel, que aplica a única regra de retorno por não ter uma declaração de retorno (há apenas uma variável 'resultado' criada automaticamente para colocar o resultado).
Certamente há casos em que o código pode ser esclarecido com retornos múltiplos do que a versão óbvia sem eles. Alguém poderia argumentar que é necessário mais retrabalho se você tiver uma função muito complexa para ser compreensível sem várias declarações de retorno, mas às vezes é bom ser pragmático sobre essas coisas.
fonte
Se você terminar com mais de alguns retornos, pode haver algo errado com seu código. Caso contrário, eu concordaria que, às vezes, é bom poder retornar de vários locais em uma sub-rotina, especialmente quando torna o código mais limpo.
Perl 6: Exemplo ruim
seria melhor escrito assim
Perl 6: bom exemplo
Observe que este é apenas um exemplo rápido
fonte
Eu voto no retorno único no final como uma diretriz. Isso ajuda na manipulação comum de limpeza de código ... Por exemplo, veja o código a seguir ...
fonte
Essa é provavelmente uma perspectiva incomum, mas acho que quem acredita que várias declarações de retorno devem ser favorecidas nunca precisou usar um depurador em um microprocessador que suporta apenas quatro pontos de interrupção de hardware. ;-)
Embora os problemas do "código de seta" estejam completamente corretos, um problema que parece desaparecer ao usar várias instruções de retorno está na situação em que você está usando um depurador. Você não tem uma posição conveniente para colocar um ponto de interrupção para garantir que verá a saída e, portanto, a condição de retorno.
fonte
Quanto mais instruções de retorno você tiver em uma função, maior será a complexidade desse método. Se você se perguntar se possui muitas instruções de retorno, pode se perguntar se possui muitas linhas de código nessa função.
Mas, não, não há nada errado com uma / muitas declarações de retorno. Em algumas línguas, é uma prática melhor (C ++) do que em outras (C).
fonte