Qual parâmetro devo receber em meus métodos?

Um dilema interessante sempre é: qual parâmetro devo receber em meu método? Por exemplo, imagine uma classe NotaFiscal, que é extremamente complexa e difícil de ser criada, pois depende de muitos outros objetos:

class NotaFiscal {
	public NotaFiscal(
		Cliente cliente,
		List<Item> itens,
		List<Desconto> descontos,
		Endereco entrega,
		Endereco cobranca,
		FormaDePagamento pagto,
		double valorTotal
	) { ...}

	// muitos atributos e métodos
}

Agora, imagine um outro método, responsável por calcular o valor do imposto dessa nota fiscal. Mas, para calcular o valor, o algoritmo leva em consideração apenas a lista de itens. Os outros atributos não são necessários para ele. Veja:

class CalculadorDeImposto {
	public double calcula(NotaFiscal nf) {
		double total = 0;
		for(Item item : nf.getItens()) {
			if(item.getValor()>1000)
				total+= item.getValor() * 0.02;
			else
				total+= item.getValor() * 0.01;
		}
		return total;
	}
}

Veja só que o método calcula() recebe uma NotaFiscal. Mas veja que não precisaria. Poderíamos apenas receber uma List. A pergunta é: qual dos dois devemos receber? Essa é uma pergunta profunda, que envolve até uma discussão sobre acoplamento.

Discutimos anteriormente que devemos sempre nos acoplar com dependências mais leves e menos frágeis. Nesse exemplo em particular, NotaFiscal parece ser uma classe pouco estável e bastante complicada; já uma lista de itens parece ser mais simples. Receber essa lista como parâmetro pode deixar o método calcula() mais simples e menos propenso a sofrer modificações. Se, para o cálculo do imposto, o algoritmo precisasse apenas do valor total, por quê não receber um double total apenas? Quanto mais simples, menos problemas.

Mas talvez uma lista de itens ou um double não deixe bem claro o que o método calcula() precisa receber. Um double pode ser qualquer número, uma lista de ítens pode ser qualquer lista de ítens, mas não é isso que ele quer: ele quer um valor de uma nota fiscal, ou uma lista de ítens de uma nota fiscal. Garantias essas que tínhamos quando passávamos uma NotaFiscal como parâmetro.

Para resolver isso de maneira elegante, por quê não criar uma abstração? Poderíamos ter uma interface chamada Tributavel, que seria implementada pela classe NotaFiscal. O método calcula(), por sua vez, receberia essa interface.

tributavel

Assim como no exemplo anterior, acabamos emergindo uma interface leve. Nesse exemplo em particular, a criação da interface Tributavel nos traz vantagens:

  • Diminuímos o risco do acoplamento, pois a interface Tributavel é muito mais estável que a classe NotaFiscal.
  • O método calcula() recebe agora apenas o que realmente precisa. Tributavel é uma interface magra.
  • Temos semântica no parâmetro do método. Não recebemos uma lista ou um double qualquer, e sim algo passível de tributação.

Em código:

interface Tributavel {
	List<Item> itensASeremTributados();
}

class NotaFiscal implements Tributavel {
	public NotaFiscal(...) { ... }

	public List<Item> itensASeremTributados() { ... }
}

class CalculadorDeImposto {
	public double calcula(Tributavel t) {
		double total = 0;
		for(Item item : t.itensASeremTributados()) {
			if(item.getValor()>1000)
				total+= item.getValor() * 0.02;
			else
				total+= item.getValor() * 0.01;
		}
		return total;
	}
}

Portanto, esse é mais um exemplo de como interfaces leves são importantes. Classes que dependem de interfaces leves sofrem menos com mudanças em outros pontos do sistema. Novamente, elas são pequenas, e portanto, tem poucas razões para mudar.

24 Comentários

  1. Reinaldo de Carvalho 31/03/2015 at 11:55 #

    Eu vejo dois problemas ainda:

    1) Se eu tiver mais 5 operações com nota fiscal, eu vou enchê-la de interfaces?

    2) A lista de Itens ainda é muito vaga. Item provavelmente tem muitos outros atributos dentro, e quem quiser usar CalculadorDeImposto pode não saber quais atributos de Item preencher.
    Minha proposta é retornar somente as informações que a classe vai usar. Nesse caso, ao invés de uma lista de itens, uma lista de valores.

  2. Marcus 31/03/2015 at 12:41 #

    Este é um post que dá gosto de ler. Parabéns!

  3. Erick Kaneda 31/03/2015 at 15:48 #

    Muito bom artigo!
    Comecei a utilizar apenas dessa maneira por semântica mas pouco tempo depois vi a grande utilidade que é utilizar uma interface, algo que dificilmente é bem explicado por professores e/ou apostilas(Normalmente falam apenas sobre o “problema” com herança única mas quase nunca falam sobre essa estabilidade citada no artigo).

    Um bom assunto para postarem é sobre algo que me deparei outro dia e me deparei com esse tópico no GUJ:

    http://www.guj.com.br/java/134767-probleminha-ooad-ddd-repositorios

  4. Pedro Lira 31/03/2015 at 16:14 #

    Ótima explicação Maurício, fiquei inclusive com a dúvida, se na primeira classe demonstrada, a de NotaFiscal, não existiria algo a ser feito, pois acho deselegante um método receber mais que 3 atributos como parâmetros. Existe neste caso algo tão elegante como o que foi demonstrado, para não ser necessário passar tantos parâmetros?

    No mais, ótima explicação. Parabéns.

  5. Douglas Arantes 31/03/2015 at 18:00 #

    Parabéns Maurício, pelo post. Simples e Claro.

  6. Vinicius Castro 31/03/2015 at 20:51 #

    Gostei do post.
    Aborda uma questão importante de forma simples, objetiva e fácil de entender. Parabéns!

  7. Luis Henrique 31/03/2015 at 21:20 #

    Excelente artigo!

  8. Frederico Maia 02/04/2015 at 11:20 #

    Show o post! Difícil é convencer os desenvolvedores que não manjam de OO de que isso é uma boa prática e que melhora de fato a evolução do código.

  9. Alberto 02/04/2015 at 12:04 #

    Opa Aniche, teve uma coisa que não ficou clara para mim. Eu entendo que você queira diminuir o acoplamento entre as partes do código, mas não saquei como seria a implementação para minimizar o problema. Qual implementação da interface *Tributavel* você vai usar? Nesse caso específico não consegui ver uma desvantagem em passar a List, já que o método poderia calcular o imposto sobre qualquer lista de itens. Caso esse calculo seja específico para uma NotaFiscal, não faria sentido a classe NotaFiscal encapsular o uso do objeto do tipo CalculadorDeImposto?

    Da para ir além na questão do parâmetro e pensar numa situação simples. Em vários DAOs tem o método que busca pelo id e, geralmente, a implementação é *busca(NotaFiscal nota)*enquanto você quer apenas o id. Como você faria?

  10. Macedo 02/04/2015 at 14:58 #

    Excelente artigo, e acho que o link sobre ddd que o @erickkaneda postou é algo que falta nos posts sobre java, pegar um problema real e trabalhar sobre ele. é muito bom ver praticas e ideias, padrões e sistemas, mas normalmente mostra-se apenas uma visão bem focada que muitas vezes esconde diversos outros problemas. Imagino que existam analises assim em cursos, mas fica como dica.
    Postar um problema, e deixar para que inumeras pessoas vão postando suas soluções e com o tempo ir ampliando o problema.

  11. Stélio Moiane 04/04/2015 at 13:22 #

    Oi Aniche,
    Parabens pelo post, muito simples e conciso.

    A ideia de se acoplar com abstrações realmente ajuda muito e deixa o código bem claro.

    Sucessos!

  12. Fernando Vasconcellos 06/04/2015 at 17:46 #

    Fala @Alberto.

    Bem observado, talvez o exemplo escolhido pelo Aniche não tenha sido o melhor mas a discussão é muito válida ainda. Nesse caso simplório, onde em meu contexto parar calcular imposto exista apenas o objeto NotaFiscal, talvez fizesse mais sentido o método calcularImposto ser um comportamento do próprio objeto NotaFiscal, tendo em vista que o imposto é calculado a partir dos itens da NF.

    Veja:

    NotaFiscal.calculaImpostoSobreOsItens();

    Entretanto, imagine um contexto de negócio onde possuo 2 objetos que são tributaveis, sendo eles: NF e Pedidos de compra. Ambos possuem uma lista de itens tributáveis, sendo eles Itens da NF e Insumos/Mercadorias dos pedidos e Parcelas. Nesse caso, faz muito mais sentido pensar em uma abstração Tributável e talvez até uma abstração para os itens que seriam tributados. Desse jeito, CASO A FORMA DE TRIBUTAÇÃO SEJA A MESMA, o código que calcula o imposto não precisaria ser duplicado…

    No caso da forma de tributação variar de acordo com o objeto, a solução se tornaria outra, talvez aplicar um strategy fosse uma boa opção… mas ai a discussão é outra e já devo estar fugindo do tema do post do Aniche hehe

  13. Rodrigo Lima 12/04/2015 at 11:00 #

    Muito bom artigo!

    Tentando ajudar ao pessoal que não entendeu o exemplo por completo, na implementação original, onde é passado NotaFiscal como parâmetro, o método calculaImposto teria acesso a todos os atributos de uma nota fiscal, não somente a lista de itens a serem tributados. Com isso, qualquer mudança na classe NotaFiscal, poderia afetar o método que calcula o imposto.

    Passar somente uma Lista de itens tributáveis, deixa a assinatura do método meio incompreensível já que o método necessita de uma lista de itens tributáveis de uma nota fiscal e não uma lista de itens qualquer.

    Não solução dada, a interface Tributável tem apenas um método (List itensASeremTributados()), e passando essa interface como parâmetro para o método calculaImposto, garante que o método calcula imposto somente terá acesso a lista de itens a ser tributado e não mais a todos os atributos de uma nota fiscal.

    Outro benefício é que qualquer outra mudança na lógica da classe NotaFiscal, que não seja nos itens a serem tributados, nunca afetará o método que calcula o imposto, baixando o nível de acoplamento.

  14. Josimar Silva 13/04/2015 at 08:56 #

    Muito bom o post. Simples e bem explicativo. Parabéns Aniche.

  15. Rogerio J. Gentil 14/04/2015 at 17:55 #

    Por isto que eu gosto das suas publicações Aniche. Sempre tornam algo deixado de lado, esquecido por programados em algo factível. Melhor ainda com os comentários do @Alberto, @Fernando Vasconcellos e @Rodrigo Lima.

  16. Roberto 20/04/2015 at 09:18 #

    Bom dia,
    Nesse eu preferiria uma outra abordagem de interface. a Classe Item implementa a interface Tributável e o método recebe uma lista de Tributáveis.
    Dessa forma a meu ver fica um código mais limpo e trabalhamos com uma parte menor.

  17. Márcio Torres 24/04/2015 at 10:27 #

    Bom exemplo de redução de acoplamento, no entanto a solução como um todo me pareceu procedimental e não orientada a objetos, passando dados de um objeto outro operar sobre eles. Na prática nem precisaria dois objetos, o método calcula, por exemplo, poderia ser estático, já que não utiliza nenhum atributo de CalculadorDeImposto.

    IMHO, este é um exemplo do benefício do baixo acoplamento mas com o efeito colateral de perder coesão.

    Para todo o caso, está de parabéns o post, nem todo mundo tem coragem de trazer a mesa um assunto tão polêmico.

  18. Paulo Silveira 25/04/2015 at 01:10 #

    oi Marcio. Não é apenas porque um método não utiliza nenhum atributo da classe que ele deve ser estático. Certamente é um sinal, mas de forma alguma uma regra. Nesse caso em particular, eu não tornaria esse método estático. Se um dia você for ter mais de um CalculadorDeImposto, está facílimo de fazer um extract interface aí e cada CalculadorDeImposto diferente pode ou não utilizar seus atributos para o cálculos. Ter atributos ou não é um detalhe de implementação. Se você optasse por um método estático em vez de instância, a refatoração simples seria impossível. Invocação de método estático é escrever na pedra, já que não há invocação virtual de método. Pra mim, esse caso o método estático sim é uma solução procedural, exatamente como você pode ler no Effective Java do Joshua Bloch, item 22 use function objects to represent strategies. É por isso que temos tants interfaces com um único método que muitas vezes são implementadas sem atributo algum, como Comparators e basicamente todas as novas interfaces funcionais do Java 8.

  19. João Victor 28/04/2015 at 11:37 #

    Excelente post!!

  20. Márcio Torres 01/05/2015 at 11:29 #

    Salve Paulo!

    Tchê, entendo perfeitamente teu ponto vista e concordo com ele, é excelente. Mas, opinião pessoal, ainda faria estático em vez de tentar de uma instância para torná-lo amigável à uma extração de interface, ou então já faria como dissestes, usaria uma interface. É bacana que tua solução vem ao encontro do meu ponto de vista, na verdade, o estático foi a segunda parte da minha fala no comentário anterior, o ponto que eu me referia é que eu gostaria de ter o método para consultar o imposto na `NotaFiscal`. Na minha humilde opinião, para cumprir isso poderia usar o _Strategy_, passando uma instância (concreta) de `Calculador` à `NotaFiscal` e disponibilizando um método `getImposto()` que delega para o `Calculador`, agradando _gregos_, _troianos_ e _espartanos_: é a _prova de futuro_ como querias, diminui o acoplamento como o Maurício queria, e tem o método que consulta o imposto da nf na própria própria nf como eu queria.

    Pessoalmente, me sinto confortável com os métodos de consulta presentes nos objetos que tem os dados em vez de passá-los para outros objetos (o que, na minha opinião, mesmo sem a _keyword_ `static` tem alma de estático), mas além disso pensei que `NotaFiscal` poderia ir para uma _view_ onde poderia-se acessar `nf.imposto`, sem precisar de uma miscelânea de objetos, um DTO, etc. Inclusive, faria a chamada ao método `Calculo#calcular` apenas no `NotaFiscal#getImposto` para atrasá-lo e fazê-lo só se necessário.

    Sabe, devaneando por aqui, penso: muito se fala de _modelo anêmico_, um _fancy name_ dado e propagado por pessoas influentes para expressar um objeto com atributos (estado) mas sem operações. Como seria chamado um objeto que tem operações mas não tem estado (atributos)? Eu sugeriria _modelo amnésico_, mas não sou influente para meu _fancy name_ pegar hehehehe. Claro, muito é devido a característica do Java, tenho de criar uma instância para passar um método (um objeto de função), o que é uma pena, daria para poupar um pouco mais o _heap_ e o _overhead_ no GC se fosse apenas uma referência a um método.

    Fechando, bem provável que eu tenha falado um monte de bobagens, comecei com um _nitpicking_ sobre o código do Maurício, ou seja, um comentário para encontrar cabelo em ovo. Fiquem a vontade para desconstruir meus argumentos, sempre aprendo muito aqui, tanto as pessoas que postam quanto as que comentam o fazem em alto nível e cordialmente – em outras palavras, é uma honra ser esculachado aqui hahaha :).

    Abraço a todos.

  21. Maurício Aniche 01/05/2015 at 11:57 #

    Oi Márcio,

    Seu ponto é interessante. A troca é complicada. Muitas vezes precisamos separar dado de comportamento, para ganhar em polimorfismo e conseguir mais flexibilidade.

    Em todos exemplos que posto no blog, imagine que a regra não tenha 5 linhas (que faço por questões didáticas), mas sim 50, 100. Talvez ficaria complicado demais ter isso direto na entidade. Meu objetivo aqui era explicar só sobre o impacto que uma interface mais gorda tem ao longo do teu sistema. Não entrei na discussão da coesão pq senão ia me prolongar demais por aqui.

    Sobre ele ser estático, os problemas já foram citados. Você perde em flexibilidade (afinal, nada de polimorfismo), e por consequência nada de testes (pois você não consegue mockar esse cara).

    Um abraço!

  22. Léo Costa 13/05/2015 at 16:16 #

    Muito útil quando usamos microserviços 😉

  23. André 28/05/2015 at 11:28 #

    Em Java eu não sei, mas em C# eu usaria a cláusula “where” para restrição de tipo genérico!

    public bool MyMethod(T t) where T : IMyInterface { }

  24. Reinaldo 19/03/2016 at 11:51 #

    Ainda continua com o mesmo dilema: não precisa do item completo, mas somente dos valores.
    Quem invocar o serviço, como vai saber quais atributos de item ele deve preencher?

Deixe uma resposta