Funções estão espalhadas por todo o nosso código, elas nos ajudam a organizar e diminuir as repetições.
Mas, como tudo na programação, elas podem nos trazer vantagens e desvantagens, podendo deixar o código mais fácil ou mais difícil de entender.
Elas encapsulam diversas linhas de código, sob um nome e um conjunto de parâmetros, que se não forem bem descritos e escolhidos, ou usados em grandes quantidades, podem gerar confusão e obscurecer o código.
O ideal é que a função tenha menos parâmetros possíveis, se possível nenhum, claro que é algo bem difícil, muitas vezes impossível, mas, existem diversas formas e técnicas para nos ajudar com isso.
Quantidade e a dificuldade no entendimento
Do ponto de vista da leitura do código, uma função com muitos parâmetros, é mais difícil de entender do que outra com uma quantidade menor.
Olhe os exemplos:
// Nenhum parâmetro
public boolean isPago() {
//…
}
// Um parâmetro
public void setDataPagamento(Data dataPagamento) {
//…
}
// Dois parâmetros
public void pagar(Data dataPagamento,
Usuario usuarioPagamento) {
//…
}
// Três parâmetros
public void transferir(Conta contaOrigem,
Conta contaDestino, BigDecimal valor) {
//…
}
// Quatro parâmetros
public void pagar(BigDecimal valorCompra,
BigDecimal valorPago, FormaPagamento forma,
boolean temDescontoVip) {
//…
}
Qual delas é a mais difícil, ou exige mais esforço? Certamente, a com quatro parâmetros.
Do ponto de vista dos testes, quanto maior a quantidade de parâmetros, mais combinações diferentes são possíveis, o que torna essa função mais difícil de ser testada.
Exemplo:
// Quatro parâmetros
public void agendarTransferencia(Data dataTransferencia,
Conta contaOrigem, Conta contaDestino,
BigDecimal valor, boolean isMesmoDestinatario) {
//...
}
Quantos testes precisam ser feitos para cobrir todas as combinações possíveis, desta quantidade de parâmetros?
Muitos.
Qual quantidade ideal?
Como vimos no exemplo acima, uma função com nenhum ou apenas um parâmetro é o ideal. São mais fáceis de entender e testar. Conforme a quantidade aumenta, a complexidade também.
Um problema comum em funções com mais de um parâmetro, é que se eles forem do mesmo tipo, se torna possível invertê-los acidentalmente:
// Método de transferência
public void transferir(Conta contaOrigem,
Conta contaDestino, BigDecimal valor) {
//…
}
//Utilizando método
transferir(contaDestino, contaOrigem, valor);
Se invertêssemos os parâmetros das contas, iríamos transferir o valor para a conta errada.
O mesmo pode ocorrer de uma forma sutil, sem um efeito colateral grave:
// Código-fonte do método de asserção
public static void assertEquals(Long expected, Long actual) {
//...
}
// Utilizando o método de asserção
Long resultado = calcular();
assertEquals(resultado, 10L);
Observe o código-fonte do método e note que ao utilizá-lo, os parâmetros foram invertidos, ou seja, colocamos o resultado no lugar do valor esperado.
Isso não causa nenhum efeito grave, o teste irá passar, mas, se falhar a sua mensagem estará errada.
Logo, funções com quantidades de parâmetros ainda maiores, serão mais afetadas sobremaneira, em função dos problemas vistos acima.
Elas devem ser evitadas, ou ao menos devem ter uma razão muito especial para existirem.
Como descrever os parâmetros?
Não só a quantidade é importante, mas seus nomes também. Os parâmetros são variáveis, seus nomes devem estar bem explícitos e autoexplicativos.
Imagine, se o nosso método de transferir fosse escrito desta forma:
public void transferir(Conta c1, Conta c2, BigDecimal v) {
//...
}
Fica impossível de saber a função utilitária de cada um deles, para isso teríamos que abrir a implementação do método, para entender como cada um dos parâmetros é utilizado.
Usando objetos como parâmetros
Já vimos ser possível inverter e nos confundir na hora de passar os parâmetros. Pode ocorrer tanto com objetos quanto com tipos primitivos. Exemplo de função:
public void criarCirculo(int x, int y, int area) {
//...
}
Podemos criar um objeto, diminuir o número de parâmetros e evitar que uma inversão aconteça:
Ponto centro = new Ponto();
centro.setX(20);
centro.setY(20);
public void criarCirculo(Ponto centro, int area)
Criamos o objeto Ponto, que receberá os valores de x e y. Diminuímos a quantidade de parâmetros, além de impossibilitar que os parâmetros x e y sejam invertidos.
Nome dos parâmetros no nome da função
É possível adicionar o nome dos parâmetros ao nome da função, como nos exemplos a seguir:
// Um simples setter
public void setPagamento(Pagamento pagamento)
// Um método de busca comum
public Produto buscarPorNomeEValor(String nome, BigDecmial valor)
Quando usado em pequena quantidade pode ajudar na leitura e entendimento do código.
Mas devemos evitar colocar muitos parâmetros no nome da função, da mesma forma que devemos evitar colocar muitos parâmetros na função, por exemplo:
public Produto buscarPorNomeEValorEQuantidadeEmEstoqueENaoExcluido(
String nome, BigDecmial valor,
Integer quantidade, boolean excluido) {
//...
}
Nesses casos o melhor é utilizar um único objeto como parâmetro, como vimos no tópico anterior:
public Produto buscar(ProdutoFiltro filtro) {
//...
}
filtro.setNome(“Produto X”);
produtoRepository.buscar(filtro);
Parâmetros lógicos
Parâmetros lógicos em uma função é sinal que ela está realizando mais de uma tarefa. Observe o exemplo:
public void salvar(Usuario usuario,
Long usuarioId, boolean isNovo) {
if (isNovo) {
//...
} else {
//...
}
}
O ideal seria remover o parâmetro boolean e realizar esta lógica em duas funções distintas.
public void criar(Usuario usuarioNovo) {
//…
}
public void editar(Long usuarioId, Usuario usuarioEditado) {
//…
}
Parâmetros de saída
Vimos que os parâmetros são a entrada de dados de uma função, mas existem casos em que um parâmetro de entrada, é também de saída:
public void pagar(Pagamento pagamento) {
pagamento.setPago(true);
}
Neste exemplo o parâmetro é alterado na função, sendo assim o resultado sai da função, ele se torna um parâmetro de saída.
Só é possível saber isso se olharmos na implementação, o que pode deixar a função mais difícil de entender.
Se uma função precisa alterar o estado de algo, o ideal é que o próprio objeto, o qual o estado precisa ser alterado, faça isso. Como no exemplo:
public class Pagamento {
public void pagar() {
this.setPago(true);
}
}
//Usando a função
pagamento.pagar();
Conclusão
Mostramos diversos bons e maus exemplos, sobre como utilizar parâmetros nas funções.
Entendemos que funções com muitos parâmetros possuem muitas desvantagens e devem ser evitadas.
No dia a dia pode ser difícil evitar que isso não aconteça, uma recomendação é cobrir esta função com testes, para pelo menos garantir o seu funcionamento correto.
Existem muito mais tópicos sobre a construção de código limpo, em especial sobre funções, como o nome ideal, o tamanho ideal, o nível de abstração ideal e o famoso “uma função deve fazer apenas uma coisa”.
Referências
Martin, Robert Cecil, Clean Code, 2011, Edit: Alta Books, Rio de Janeiro
Top comments (0)