Clean Code: Boas práticas para argumentos de função
Os argumentos influenciam muito na qualidade das nossas funções. Eles são a entrada principal das nossas funções e manter eles organizados é importante para deixar o comportamento das funções explícito
Os argumentos de uma função são a melhor forma de comunicar estados externos para as nossas funções. Eles nos ajudam a evitar que modifiquemos diretamente uma variável externas que não pertencem ao escopo da função. Chamamos de Efeitos Colaterais esse ato de modificar estados fora do escopo da função.
Na Ciência da Computação, uma função ou expressão tem um efeito colateral quando ela modifica algum estado fora de seu escopo ou tem uma interação observável com as funções que a chamam ou com o mundo exterior além de retornar um valor.
Fonte: Texto com tradução livre do artigo Side effect feita por Carlos Schults nos comentários abaixo.
Efeitos colaterais podem ser ruins e muitas vezes tornam as intenções da nossa função menos explícita, e isso é algo péssimo. Sempre que possível nós devemos evitar causar efeitos colaterais. Então utilizar argumentos é uma boa prática, não é? Bem, depende da forma como você os utiliza!
Argumentos de função realmente ajudam a evitar os efeitos colaterais, mas uma função que depende de muitos argumentos também é imprevisível. Quanto mais argumentos, mais comportamentos diferentes nossa função pode apresentar - ela se torna mais complexa e imprevisível para quem está mantendo o código.
Então argumentos podem tornar nossas funções imprevisíveis e por isso precisamos tomar cuidado ao utilizá-los, mas quais são as melhores práticas?
Quantidade de Argumentos
Em um mundo perfeito a quantidade ideal de argumentos que uma função deve ter é zero, mas obviamente isso nem sempre é possível. Não queremos acessar ou modificar estados externos nas nossas funções, então muitas vezes nós vamos precisar passar ao menos um argumento para as nossas funções. Observe esse exemplo de função sem argumentos:
conectarAoBancoDeDados();
Funções que não precisam de nenhum argumento normalmente são rotinas, uma sequencia de chamada de funções. Uma ação simples e explícita. Ao não ter argumentos nós não precisamos saber detalhes internos da implementação para entender o que a função faz. Isso é muito bom para a legibilidade do código.
Mas e se nossa função precisasse de um argumento, como ficaria? Observe:
conectarAoBancoDeDados(dadosDoBancoDeDados);
Nossa função continua simples de ser lida, mas não tanto quanto antes pois agora precisamos de mais informações sobre a implementação da função para entender seu funcionamento.
Funções com apenas um argumento na maioria dos casos são utilizadas como perguntas, isUsuarioValido(usuario)
, que retornam true
ou false
, ou então para retornar uma versão modificada do argumento utilizado, converterParaMaiusculo("hello world")
, ou para proceder com uma rotina, um evento, como é o caso do nosso exemplo acima onde o argumento possui informações para conectar com o banco de dados, que não precisa necessariamente retornar algum valor.
Com dois argumentos as coisas começam a ficar mais complicadas. Sempre vão existir situações onde dois argumentos fazem todo sentido, como a posição do eixo X e eixo Y de um plano cartesiano, mas existem casos onde os argumentos não se comunicam tão bem entre si e pode causar confusão, por exemplo:
var iguais = comparaPalavras("Oi", palavra);
A função comparaPalavra
recebe dois argumentos, um com a palavra esperada e o outro com a palavra que será comparada, e então retorna true
ou false
, mas como podemos diferenciar qual é a ordem correta dos argumentos? Primeiro a palavra esperada e depois a palavra atual, ou seria o inverso? Adicionamos uma complexidade a mais para explicitar no nome da nossa função. Poderíamos rescrever o nome da função com um nome mais descritivo para dar mais sintonia ao passar os argumentos, dessa forma:
var iguais = comparaPalavraEsperadaComAtual("Oi", palavra);
Isso deixa muito mais explicito a forma como devemos passar os argumentos para a função. O que ela realmente espera que seja passado.
Algumas linguagens possuem um recurso chamado Named Parameters que permite que você especifique a qual argumento o valor pertence independente da ordem que você os passe nos argumentos da função.
Funções com três argumentos podem ser um pouco mais raras de existir, mas elas continuam adicionando mais complexidade nas nossas funções como nesse exemplo:
desenharCirculo(10, 10, 50);
A função desenhaCirculo
recebe a posição do eixo X, do eixo Y e do raio para desenhar um círculo, mas assim como no exemplo anterior aqui a ordem dos argumentos não fica explicita no nome da função. A função poderia estar implementada de forma invertida, onde você teria que passar o raio e depois o valor dos eixos. Em muitos casos é possível refatorar os parâmetros de uma função para diminuir sua quantidade.
Introduza Objetos como Parâmetros
Quando uma função precisa de mais de dois parâmetros, podemos tentar introduzir objetos como parâmetros contanto que eles façam sentido em um mesmo contexto, por exemplo:
desenharCirculo(10, 10, 50);
Nesse exemplo poderíamos tratar os eixos como sendo um único objeto como Posicao, Ponto ou Centro, então podemos refatorar ele da seguinte forma:
const ponto = {10, 10};
desenharCirculoDoPontoComRaio(ponto, 50);
Caso você encontre uma situação que utilize mais de três argumentos, verifique se você não pode reduzir a quantidade de parâmetros criando objetos que possuam o mesmo contexto, pois dessa forma você quebra a complexidade dos argumentos em partes menores que torna mais fácil o entendimento da função.
Array de argumentos
Se a sua função recebe um número muito grande ou indefinido de argumentos e todos eles são do mesmo tipo e são utilizados da mesma forma, como nesse exemplo:
desenharBandeiraComAsCores('Branco', 'Amarelo', 'Verde', 'Preto', 'Vermelho', 'Azul', ...outrosArgumentos);
Isso é um sinal de que você poderia utilizar apenas um Array, um lista, dessa forma:
const cores = ['Branco', 'Amarelo', 'Verde', 'Preto', 'Vermelho', 'Azul'];
desenharBandeiraComAsCores(cores);
Assim nós fazemos que a função que precisava de uma quantidade indefinida de argumentos passe a precisar de apenas um argumento, que é um array.
Não utilize valores booleanos para argumentos
Essa é uma das piores práticas. Booleanos significa que sua função vai estar desempenhando um papel totalmente diferente dependendo do estado de apenas uma variavel. Isso fará com que nossa função tenha mais de uma responsabilidade, o que significa que qualquer alteração no código da função nós teremos que levar em consideração os outros papéis que a função está desempenhando.
renderizarPagina(isProducao);
Se o argumento de renderizarPagina
for verdadeiro a função ira renderizar a página completa, mas se for falso a função irá desenhar apenas o cabeçalho. Nossa função vai desempenhar dois papéis diferentes dependendo apenas de um argumento booleano. Se nossa função vai desempenhar dois papéis diferentes dependendo apenas de um booleano, o ideal é termos duas funções diferentes com apenas uma responsabilidade, dessa forma:
if (isProducao) {
renderizaPaginaCompleta();
return;
}
renderizaApenasCabecalho();
Dessa forma nós temos o mesmo resultado de antes, mas sem o problema de adicionar mais de uma responsabilidade para a função. Nunca se esqueça de que nossas funções devem fazer apenas uma coisa e devem fazer isso muito bem!
Conclusão
Os argumentos das funções são um recurso importante das linguagens de programação, mas abusar desse recurso pode ser problemático, e essas dicas sobre argumentos apresentada no livro Clean Code do Uncle Bob ajuda-nos a manter a qualidade do nosso código.
E de todos os problemas que apresentei aqui, o pior erro que eu cometia era o de utilizar variáveis booleanas como argumento. Isso é um grande indicador de que sua função está fazendo mais de uma coisa, que vai totalmente contra o que o Tio Curly nos ensina.
E você, comete algum dos erros descritos no artigo? Possui alguma outra dica para melhorar a forma como utilizamos os argumentos das funções? Deixe um comentário com a sua experiência!
You might also be interested in these articles...
Desenvolvedor na TriadWorks - Email
Posted in: agilidadeboas praticasclean codecodigocurly lawjavascriptniveis de abstraçãoone thingprogramaçãorefactoringsistema legadotddtestestestes automatizados