Funções devem ter apenas um ponto de saída? O principio Single-exit point.
Será que sua função com apenas um ponto de saída está correta? Você está fazendo do jeito certo?
Em todas linguagens de programação as pessoas seguem determinadas boas práticas criadas pelas comunidades com a intenção de evitar diversos problemas em seus códigos. Uma dessas boas práticas era ter funções com apenas uma saída (Single-exit point), uma função com apenas um return
em seu bloco como por exemplo:
function maiorNumero(a, b) {
var resultado;
if (a > b) {
resultado = a;
}
else {
resultado = b;
}
return resultado;
}
Perceba que nesse exemplo nós criamos uma variável com o nome resultado
logo no inicio e temos apenas um return
no final da função que irá retornar essa variável (return resultado;
).
Mesmo com apenas um return
essa função pode ter dois possíveis resultados, um onde a função retorna o resultado
com o valor de a
e outro com o valor de b
. Nós alteramos apenas o conteúdo da variável resultado
no corpo da função e o ponto de saída continua sendo a ultima linha dela. Esse é o Single-exit point.
Essa prática era muito comum em linguagens de programação estruturada e de baixo nível que gerenciavam manualmente os recursos do computador, e até hoje as pessoas aplicam essa prática nas mais variadas linguagens!
Mas como seria esse código utilizando múltiplos retornos? Observe:
function maiorNumero(a, b) {
if (a > b) {
return a;
}
return b;
}
Nesse exemplo nós não temos mais a declaração de variável como tínhamos antes e temos mais de um return
. Assim que nós fazemos o processamento necessário nós encerramos a função imediatamente. Nós também omitimos o else
utilizando um return
com o valor caso a condição não seja verdadeira. Esse conceito é chamado de Early Return.
Com um código tão pequeno fica difícil de dizer qual é o melhor. Os ganhos não são tão evidentes pois economizamos apenas algumas linhas omitindo o else
, mas e nesse exemplo abaixo, como fica?:
function sacarPrevidencia {
var resultado;
if (isClienteMorto()) {
resultado = saquePensaoPorMorte();
}
else if (isClienteIncapacitado()) {
resultado = saqueAuxilioAcidente();
}
else if (isClienteDoente()) {
resultado = saqueAuxilioDoenca();
}
else {
resultado = saqueNormal();
}
return resultado;
}
Nesse caso nós estamos aplicando a mesma prática de Single-exit point onde temos apenas um return
e no decorrer do código nós alteramos o valor da nossa variável que será retornada. A quantidade de else
pode confundir um pouco.
Vamos rescrever esse código utilizando o Early Return e ver como fica:
function sacarPrevidencia {
if (isClienteMorto()) {
return saquePensaoPorMorte();
}
if (isClienteIncapacitado()) {
return saqueAuxilioAcidente();
}
if (isClienteDoente()) {
return saqueAuxilioDoenca();
}
return saqueNormal();
}
Utilizando o Early Return nós diminuímos muito a quantidade de código, substituímos todos os else
por if
e toda bloco de if
possui um return
interno. Nós não tivemos nenhum prejuízo de legibilidade.
Apesar do Early Return eliminar a necessidade do
else
, nós ainda temos muitosif
no código, e isso é considerado um Code Smell, um código mal cheiroso. Podemos refatorar esse código removendo as condicionais utilizando Polimorfismo.
Temos então um empate? Não! A grande vantagem de utilizar a técnica de Early Return é que a maioria das pessoas encara ele de forma mais natural. Retornar de uma função assim que concluímos o processamento é muito mais natural e existem estudos que mostram que em determinados algoritmos a utilização do Early Return facilita na escrita dos algoritmos que utilizam estruturas de repetição (for
e while
).
Entendendo o contexto
Mas se a técnica de Early Return possui vantagens, então por quê as funções terem apenas uma saída (Single-exit point) era e ainda é considerado por alguns uma boa prática que todos deviam respeitar? Bem, para isso nós precisamos entender o contexto da época em que o conceito foi criado, ou seja, nos anos 60 quando as linguagens estruturadas dominavam o mercado. Olhe esse exemplo na linguagem C:
int formatarArquivo( FILE *arquivo ) {
void * buffer = malloc(1024); //Pedimos memória para o computador
if (arquivo == NULL) {
return false; //Caso o arquivo seja vazio nós retornamos falso
}
processaArquivo(arquivo, buffer); //Fazemos o processamento
free(buffer); //Liberamos a memória de volta para o computador
return true; //Retornamos true por termos concluído o processamento
}
Nesse código nós utilizamos a técnica de Early Return, mas temos um problema nesse código: Estamos pedindo memória do computador utilizando a função malloc()
e ela pode nunca ser devolvido para o computador por causa do nosso Early Return que impediria de executar a função free(buffer)
antes do return
.
O código iria funcionar normalmente, mas em algum ponto ele poderia quebrar por estar infinitamente pedindo memória e nunca devolvendo ela para o computador. Podemos fazer uma analogia problema de abrir conexões com o Banco de Dados e nunca fechá-las.
Vamos ver como corrigiam o problema:
int formatarArquivo( FILE *arquivo ) {
int resultado = true; // Criamos uma variável que será retornada com seu valor padrão que é true.
void * buffer = malloc(1024); //Pedimos memória para o computador
if (arquivo == NULL) {
resultado = false; //Caso o arquivo atribuímos falso para o resultado.
}
processaArquivo(arquivo, buffer); //Fazemos o processamento
free(buffer); //Liberamos a memória de volta para o computador
return resultado; //Retornamos o resultado
}
No contexto de uma linguagem de programação estruturada e nas linguagens que manuseiam manualmente os recursos do computador, utilizar apenas um retorno, Single-exit point, era uma boa prática pois a função sempre iria percorrer todas as linhas da função e sempre iria fazer a devida limpeza da memória alocada!
Conclusão
Ambas as formas estão corretas, mas a forma utilizando a técnica de Early Return economiza algumas linhas de código, que é algo que eu considero um ponto muito positivo, e também é a forma mais simples de se resolver alguns algoritmos.
Hoje em dia nós não precisamos mais seguir a prática de Single-exit point tão a risca, pois nossas ferramentas evoluiram e temos novas formas de lidar com esse problema de esquecer de limpar os recursos utilizados pela função, como por exemplo o bloco try/finally
.
E por pura ironia nós também não podemos aplicar o principio de Single-exit point, pois a maioria das linguagens atuais possuem alguma forma de Exception, e as exceptions são uma forma de saída da função sem utilizar o return
, que é incompatível com o conceito. Elas ocorrem quando não tomamos cuidado com os fluxos alternativos da nossa aplicação.
Independente da forma que você crie as suas funções, sempre priorize a legibilidade para as pessoas que vão ler seu código. O importante é que você transmita suas intenções da forma mais clara possível para elas.
As tecnologias, ferramentas e o hardware mudam, e com isso a forma como trabalhamos com tudo isso também muda. Por isso é muito importante entender o por quê e o para quê. Não adianta sair aplicando Design Patterns sem entender qual problema ele resolve, não adianta utilizar uma nova tecnologia sem estudar o problema que ela resolve. Tudo possui um motivo e você como programador deve buscar entendê-lo. Precisamos ficar atentos.
Aqui na TriadWorks nós aplicamos muitas boas práticas e deixamos sempre claro o por quê e o para quê em nossos curso, então confere aqui no nosso site os nossos cursos. Lembrando que a próxima turma de Java para Web começa dia 17 desse mês de Julho.
E você, qual forma é a que você mais utiliza? Compartilhe conosco sua experiência. E se tiver qualquer crítica ou sugestão, deixe seu comentário! :)
You might also be interested in these articles...
Desenvolvedor na TriadWorks - Email
Posted in: algoritmoboa praticaboas praticasclean codeearly returnjavascriptlogica de programacaológicoprogramaçãoraciociniorefactoringsingle-entry point