Inveja é bom? Claro que não! Inveja é um dos sete pecados capitais! Agora, imagine se houver inveja entre as classes de um programa? Ops, isso significa que, neste caso, o desenvolvedor está cometendo um pecado capital no seu código? Faz sentido.
Acompanhe o artigo e entenda melhor o que é uma Feature Envy!
Introdução
Bom, já sabemos que o Clean Code nos ensinar a praticar a expressividade e legibilidade no nosso código, certo? Porém, além disso, é importante considerar que a prática do Clean Code também evita que o nosso código “cheire mal”. Este termo, “Code Smells”, é mencionado por Martin Fowler no seu livro “Refactoring: Improving the Design of Existing Code“. Segundo o autor, há porções de código que, só de olhar, o desenvolvedor já faz careta, como se estivesse sentindo um cheiro ruim. Você deve ter se identificado com uma situação dessas, não? 🙂
Feature Envy é um dos elementos que causam esse “cheiro” e ocorre quando uma classe começa a utilizar, em excesso, as propriedades internas de outra classe. Em outras palavras, a classe não se limita a utilizar as propriedades que possui e acaba por utilizar propriedades externas. Na definição de Uncle Bob, é como se uma classe “desejasse” ser outra.
Exemplo de Feature Envy
Para explicar melhor, um exemplo prático! No código abaixo, provavelmente você já vai identificar os pontos em que há inveja:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
procedure ProcessarProduto(const Produto: TItemVenda); var Descricao: string; Qtde: integer; Preco, Desconto, Total: real; begin // obtém os dados do item que vem como parâmetro Descricao := Trim(UpperCase(Produto.Descricao)); Qtde := Produto.Qtde; Preco := Produto.Preco; Desconto := Produto.Desconto; // calcula o total com o desconto Total := Qtde * Preco; Total := Total - (Total * Desconto / 100); // incrementa o número de vendas do produto Produto.NumVendas := Produto.NumVendas + 1; // exibe os dados na tela para o usuário Edit1.Text := Descricao; Edit2.Text := FloatToStr(Total); end; |
Embora seja um exemplo bem básico, você notou a quantidade de vezes que as propriedades do objeto Produto
são acessadas? O método ProcessarProduto
obtém a descrição, quantidade, preço e desconto do item, além de, ainda por cima, incrementar o número de vezes que aquele produto foi vendido. Agora você fez careta, não é? 🙂
O código acima é um exemplo tÃpico de Feature Envy. O método ProcessarProduto
inveja o escopo da classe TItemVenda
, como se ele desejasse estar dentro dela. Além da má impressão que o código apresenta, a Feature Envy também pode elevar o acoplamento entre classes e causar dependências desnecessárias, portanto, é muito provável que outros problemas possam surgir a partir dessa “inveja”.
Para evitar o Feature Envy, aliás, para educar o código e ensiná-lo a não ter inveja (haha), basta trabalhar adequadamente com encapsulamento.
Na solução abaixo, observe como o código fica mais limpo e, claro, menos invejoso:
1 2 3 4 5 6 7 8 9 10 11 12 |
procedure ProcessarProduto(const Produto: TItemVenda); var Descricao: string; Total: real; begin Descricao := Produto.ObterDescricaoItem; Total := Produto.ObterTotalItem; Produto.IncrementarNumeroDeVendas; Edit1.Text := Descricao; Edit2.Text := FloatToStr(Total); end; |
Na classe TItemVenda
, essa seria a implementação dos métodos de acesso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function TItemVenda.ObterDescricaoItem: string; begin result := Trim(UpperCase(FDescricao)); end; function TItemVenda.ObterTotalItem: real; var Total: real; begin Total := FQtde * FPreco; Total := Total - (Total * FDesconto / 100); result := Total; end; procedure TItemVenda.IncrementarNumeroDeVendas; begin Inc(FNumVendas); end; |
Mas, no método ProcessarProduto, você continua acessando a classe TItemVenda!
Sim, mas, ao invés de acessar diretamente as propriedades, estou acessando métodos encapsulados públicos, que são disponibilizados justamente para retornar valores internos. Na verdade, esse é um dos propósitos do encapsulamento da Orientação a Objetos. Não conhecemos as propriedades e nem os cálculos que ocorrem dentro do método ObterTotalItem
, mas sabemos que ele irá nos retornar o total do item da venda.
Só mais uma observação: o incremento do número de vendas poderia ser mais confiável. Ao invés de um método, uma alternativa é criar uma Trigger no banco de dados para automatizar essa operação. Dessa forma, o número de vendas seria incrementado somente quando o item fosse efetivamente gravado no banco de dados, aumentando a confiabilidade e eliminando algumas linhas de código na aplicação.
Fico por aqui, pessoal.
Até a próxima semana!