Costumo conversar com programadores que dizem " Não coloque várias declarações de retorno no mesmo método " . Quando peço que eles me digam os motivos, tudo o que recebo é " O padrão de codificação diz isso " ou " É confuso " . Quando eles me mostram soluções com uma única declaração de retorno, o código fica mais feio para mim. Por exemplo:
if (condition)
return 42;
else
return 97;
" Isso é feio, você precisa usar uma variável local! "
int result;
if (condition)
result = 42;
else
result = 97;
return result;
Como esse inchaço de 50% do código facilita a compreensão do programa? Pessoalmente, acho isso mais difícil, porque o espaço de estado acaba de aumentar por outra variável que poderia ser facilmente evitada.
Claro, normalmente eu apenas escreveria:
return (condition) ? 42 : 97;
Mas muitos programadores evitam o operador condicional e preferem a forma longa.
De onde veio essa noção de "apenas um retorno"? Existe uma razão histórica para a criação desta convenção?
fonte
Respostas:
"Entrada única, saída única" foi escrita quando a maior parte da programação era feita em linguagem assembly, FORTRAN ou COBOL. Foi amplamente mal interpretado, porque as linguagens modernas não suportam as práticas contra as quais Dijkstra estava advertindo.
"Entrada única" significava "não cria pontos de entrada alternativos para funções". Na linguagem assembly, é claro, é possível inserir uma função em qualquer instrução. FORTRAN suportou várias entradas para funções com a
ENTRY
instrução:"Saída única" significava que uma função deveria retornar apenas para um local: a instrução imediatamente após a chamada. Isso não significava que uma função deveria retornar apenas de um lugar. Quando a Programação Estruturada foi escrita, era prática comum para uma função indicar um erro retornando para um local alternativo. FORTRAN apoiou isso através de "retorno alternativo":
Ambas as técnicas eram altamente propensas a erros. O uso de entradas alternativas geralmente deixa algumas variáveis não inicializadas. O uso de retornos alternativos apresentava todos os problemas de uma instrução GOTO, com a complicação adicional de que a condição de ramificação não estava adjacente à ramificação, mas em algum lugar da sub-rotina.
fonte
const
existia antes de muitos dos usuários nascerem, portanto, não há mais necessidade de constantes maiúsculas. mesmo em C. Mas Java preservados todos os maus hábitos antigos C .setjmp/longjmp
?)Essa noção de entrada única, saída única (SESE) vem de idiomas com gerenciamento explícito de recursos , como C e assembly. Em C, códigos como este vazarão recursos:
Nesses idiomas, você basicamente tem três opções:
Replicar o código de limpeza.
Ugh. Redundância é sempre ruim.
Use a
goto
para pular para o código de limpeza.Isso requer que o código de limpeza seja a última coisa na função. (E é por isso que alguns argumentam que
goto
tem seu lugar. E de fato - em C.)Introduzir uma variável local e manipular o fluxo de controle através disso.
A desvantagem é que o fluxo de controle manipulados através de sintaxe (pense
break
,return
,if
,while
) é muito mais fácil de seguir do que o fluxo de controle manipulado através do estado de variáveis (porque essas variáveis não têm estado quando você olha para o algoritmo).Na montagem, é ainda mais estranho, porque você pode pular para qualquer endereço de uma função ao chamá-la, o que efetivamente significa que você tem um número quase ilimitado de pontos de entrada para qualquer função. (Às vezes, isso é útil. Esses thunks são uma técnica comum para os compiladores implementarem o
this
ajuste do ponteiro necessário para chamarvirtual
funções em cenários de herança múltipla em C ++.)Quando você precisa gerenciar recursos manualmente, explorar as opções de entrada ou saída de uma função em qualquer lugar leva a um código mais complexo e, portanto, a erros. Portanto, surgiu uma escola de pensamento que propagou o SESE, a fim de obter um código mais limpo e menos erros.
No entanto, quando um idioma apresenta exceções, (quase) qualquer função pode ser encerrada prematuramente (quase) a qualquer momento, portanto, você precisa prever o retorno prematuro de qualquer maneira. (Eu acho que
finally
é usado principalmente para isso em Java eusing
(ao implementarIDisposable
,finally
caso contrário) em C #; o C ++ emprega RAII .) Depois de fazer isso, você não pode deixar de se limpar devido a umareturn
declaração anterior, então o que provavelmente é o argumento mais forte a favor do SESE desapareceu.Isso deixa legibilidade. Obviamente, uma função 200 LoC com meia dúzia de
return
instruções espalhadas aleatoriamente sobre ele não é um bom estilo de programação e não cria código legível. Mas essa função também não seria fácil de entender sem esses retornos prematuros.Nos idiomas em que os recursos não são ou não devem ser gerenciados manualmente, há pouco ou nenhum valor em aderir à antiga convenção SESE. OTOH, como argumentei acima, o SESE geralmente torna o código mais complexo . É um dinossauro que (exceto C) não se encaixa bem na maioria das línguas atuais. Em vez de ajudar a compreensibilidade do código, ele dificulta.
Por que os programadores Java mantêm isso? Eu não sei, mas do meu POV (externo), o Java pegou muitas convenções de C (onde elas fazem sentido) e as aplicou ao seu mundo OO (onde são inúteis ou totalmente ruins), onde agora se apega a eles, não importa quais sejam os custos. (Como a convenção para definir todas as suas variáveis no início do escopo.)
Os programadores aderem a todos os tipos de notações estranhas por razões irracionais. (Declarações estruturais profundamente aninhadas - "pontas de flechas" - eram, em idiomas como Pascal, uma vez vistas como um belo código.) A aplicação de raciocínio lógico puro a isso parece falhar em convencer a maioria deles a se desviar de seus modos estabelecidos. A melhor maneira de mudar esses hábitos é provavelmente ensinando-os desde cedo a fazer o que é melhor, não o que é convencional. Você, sendo professor de programação, tem na sua mão.
:)
fonte
finally
cláusulas em que é executado independentemente dereturn
s ou exceções anteriores.finally
mais do tempo.malloc()
efree()
em um comentário, por exemplo), eu estava falando sobre os recursos em geral. Eu também não estava sugerindo que a GC resolveria esses problemas. (Eu mencionei C ++, que não possui GC pronto para uso.) Pelo que entendi, em Javafinally
é usado para resolver esse problema.Por um lado, as instruções de retorno único facilitam o log, bem como as formas de depuração que dependem do log. Lembro-me de muitas vezes que tive que reduzir a função em retorno único apenas para imprimir o valor de retorno em um único ponto.
Por outro lado, você pode refatorar isso nessas
function()
chamadas_function()
e registrar o resultado.fonte
_function()
, comreturn
s nos locais apropriados, e um invólucro nomeadofunction()
que lida com o log externo, do que ter uma únicafunction()
com lógica contorcida para fazer com que todos os retornos se ajustassem em uma única saída -point apenas para que eu possa inserir uma declaração adicional antes desse ponto."Entrada única, saída única" originou-se com a revolução da programação estruturada do início dos anos 1970, iniciada pela carta de Edsger W. Dijkstra ao editor " Declaração GOTO considerada prejudicial ". Os conceitos por trás da programação estruturada foram detalhados no livro clássico "Structured Programming" de Ole Johan-Dahl, Edsger W. Dijkstra e Charles Anthony Richard Hoare.
A "declaração GOTO considerada prejudicial" é leitura obrigatória, ainda hoje. A "Programação Estruturada" é datada, mas ainda muito, muito gratificante, e deve estar no topo da lista de "Devemos Ler" de qualquer desenvolvedor, muito acima de qualquer coisa, por exemplo, de Steve McConnell. (A seção de Dahl expõe os conceitos básicos de classes no Simula 67, que são a base técnica para classes em C ++ e toda a programação orientada a objetos.)
fonte
goto
podia literalmente ir a qualquer lugar , como em algum ponto aleatório de outra função, ignorando qualquer noção de procedimentos, funções, pilha de chamadas, etc. Nenhuma linguagem sadia permite isso hoje em diagoto
. C'ssetjmp
/longjmp
é o único caso semi-excepcional que conheço, e mesmo isso requer cooperação de ambos os lados. (Semi-irônico que eu usei a palavra "excepcional" lá, considerando que as exceções fazem quase a mesma coisa ...) Basicamente, o artigo desencoraja uma prática que há muito está morta.Sempre é fácil vincular o Fowler.
Um dos principais exemplos contrários ao SESE são as cláusulas de guarda:
Substituir condicional aninhado por cláusulas de guarda
fonte
_isSeparated
e_isRetired
podem ambos ser verdadeiros (e por que isso não seria possível?), Você retorna a quantia errada.Eu escrevi um post sobre este tópico há um tempo.
A conclusão é que essa regra vem da era dos idiomas que não têm coleta de lixo ou manipulação de exceção. Não há estudo formal que mostre que essa regra leva a um melhor código nas linguagens modernas. Fique à vontade para ignorá-lo sempre que isso levar a um código mais curto ou mais legível. O pessoal do Java que insiste nisso é cego e inquestionável, seguindo uma regra desatualizada e sem sentido.
Esta pergunta também foi feita no Stackoverflow
fonte
Um retorno facilita a refatoração. Tente executar o "método de extração" no corpo interno de um loop for que contenha retorno, interrupção ou continuação. Isso falhará quando você interromper seu fluxo de controle.
O ponto é: acho que ninguém está fingindo escrever um código perfeito. Portanto, o código está regularmente sob refatoração para ser "aprimorado" e estendido. Portanto, meu objetivo seria manter meu código o mais amigável possível para a refatoração.
Muitas vezes, enfrento o problema de reformular completamente as funções se elas contiverem interruptores de fluxo de controle e se desejar adicionar apenas pouca funcionalidade. Isso é muito suscetível a erros, pois você altera todo o fluxo de controle em vez de introduzir novos caminhos para aninhamentos isolados. Se você tiver apenas um retorno único no final ou se usar guardas para sair de um loop, é claro que terá mais aninhamento e mais código. Mas você obtém recursos de refatoração suportados pelo compilador e pelo IDE.
fonte
Considere o fato de que várias declarações de retorno são equivalentes a ter GOTOs em uma única declaração de retorno. Este é o mesmo caso com instruções de quebra. Assim, alguns, como eu, os consideram GOTO para todos os efeitos.
No entanto, eu não considero esses tipos de GOTO prejudiciais e não hesitarei em usar um GOTO real no meu código se encontrar uma boa razão para isso.
Minha regra geral é que os GOTO são apenas para controle de fluxo. Eles nunca devem ser usados para loop, e você nunca deve GOTO 'para cima' ou 'para trás'. (que é como as quebras / devoluções funcionam)
Como outros já mencionaram, é necessário ler a declaração GOTO considerada prejudicial.
No entanto, lembre-se de que isso foi escrito em 1970, quando as GOTOs eram muito usadas. Nem todo GOTO é prejudicial e eu não desencorajaria o uso deles, desde que você não os use em vez de construções normais, mas, no caso estranho, o uso de construções normais seria altamente inconveniente.
Acho que usá-los em casos de erro em que você precisa escapar de uma área devido a uma falha que nunca deve ocorrer em casos normais úteis às vezes. Mas você também deve considerar colocar esse código em uma função separada para poder retornar mais cedo, em vez de usar um GOTO ... mas às vezes isso também é inconveniente.
fonte
Complexidade ciclomática
Eu já vi o SonarCube usar várias declarações de retorno para determinar a complexidade ciclomática. Portanto, quanto mais as declarações de retorno, maior a complexidade ciclomática
Alteração de tipo de retorno
Retornos múltiplos significam que precisamos mudar em vários locais da função quando decidimos alterar nosso tipo de retorno.
Saída múltipla
É mais difícil depurar, pois a lógica precisa ser cuidadosamente estudada em conjunto com as instruções condicionais para entender o que causou o valor retornado.
Solução Refatorada
A solução para várias instruções de retorno é substituí-las pelo polimorfismo e ter um único retorno após a resolução do objeto de implementação necessário.
fonte