Classes não Coesas em Sistemas Orientados a Objetos
A coesão é uma palavra bastante utilizada quando trabalhamos com linguagens orientadas a objetos. Mas poucos programadores sabem realmente aplicar seus conceitos de forma efetiva. Seu significado também é bastante conhecido: uma classe coesa é aquela que possui uma única responsabilidade. Logo, ela não toma conta de mais de um…
A coesão é uma palavra bastante utilizada quando trabalhamos com linguagens orientadas a objetos. Mas poucos programadores sabem realmente aplicar seus conceitos de forma efetiva. Seu significado também é bastante conhecido: uma classe coesa é aquela que possui uma única responsabilidade. Logo, ela não toma conta de mais de um conceito no sistema. Exemplo, se uma classe é responsável por implementar uma Nota Fiscal, ela representa apenas isso. As responsabilidades de um Boleto, por exemplo, estarão em uma outra classe.
Uma classe coesa é vital para um sistema orientado a objetos. Ela é mais simples de ser mantida e eventualmente precisarão de manutenção, pois possuem menos códigos e seu reúso é maior. Entretanto, facilmente encontramos por aí classes gigantes, com dezenas de métodos e dificílimas de serem mantidas.
Neste pequeno artigo vamos falar um pouco sobre classes não coesas e como identificar o que as tornas não coesas.
Exemplo de classes não coesas:
class CalculadoraDeSalario
def initialize(funcionario:)
@funcionario = funcionario
end
def calcula
case @funcionario.cargo
when 'DESENVOLVEDOR'
dez_ou_vinte_porcento(@funcionario)
when 'DBA' || 'TESTER'
quinze_ou_vinte_porcento(@funcionario)
else
raise FuncionarioInvalidoError, 'Funcionario não encontrado'
end
end
private
def dez_ou_vinte_porcento(funcionario)
return funcionario.salario * 0.8 if funcionario.salario > 4000
funcionario.salario * 0.9
end
def quinze_ou_vinte_porcento(funcionario)
...
end
end
Observe que, como o nome já diz, essa classe é responsável por calcular os salários dos funcionários. A regra é um tanto quanto simples, de acordo com o cargo e o salário do funcionário, o desconto é aplicado de forma diferente.
Códigos como esses são bastantes comuns, entretanto nesta classe existem diversos problemas de coesão, se reparamos, existem apenas três cargos diferentes (desenvolvedor, dba e tester) com regras bem simples e similares, entretanto em um sistema real, essa quantidade seria grande. Logo teríamos uma daquelas classes gigantes, cheias de condicionalismos que são muito comuns de serem encontradas. Elas não tem nada de coesas.
Para entendermos melhor o que não é uma classe coesa, é olhar para a classe e descobrir o que faria o programador digitar mais código nela. Toda classe não coesa não para de crescer nunca.
A classe ‘ClaculadoraDeSalario’ cresce indefinidamente por dois motivos: sempre que um cargo novo é adicionado e quando surgir uma nova regra de cálculo. Vamos ver como podemos resolver um desses problemas.
Há várias formas de se resolver este problema, esta é apenas uma delas.
Então vamos trabalhar os métodos privados ‘dez_ou_vinte_porcento’ e ‘quinze_ou_vinte_porcento’, apesar de terem implementações diferentes, eles possuem uma mesma estrutura de código. Ambos recebem um objeto funcionário e nos retornam o salário calculado.
A ideia é colocar cada uma dessas regras em uma classe diferente e todas passam a herdar de uma classe mãe (classe base) que conterá todas as informações que têm em comum. Dessa forma, repare que cada regra de cálculo agora está isolada e contém apenas uma regra, deixando-as muito mais coesas, além que cada classe terá pouco código. Assim, toda nova regra deve ser colocada em uma classe separada.
class Base
def initialize(funcionario:)
@funcionario = funcionario
end
def aplica_regra(regra)
funcionario * regra
end
end
class DezOuVintePorcento < Base
def initialize(funcionario:)
super
salario = funcionario.salario
end
def calcula
regra = salario > 3000 ? 0.8 : 0.9
aplica_regra(regra)
end
end
E o mesmo se aplicaria ao método privado quinze_ou_vinte_porcento, e com isso teriamos o seguinte código:
class CalculadoraDeSalario
def initialize(funcionario:)
@funcionario = funcionario
end
def calcula
case @funcionario.cargo
when 'DESENVOLVEDOR'
DezOuVintePorcento.new(funcionario: @funcionario).calcula
when 'DBA' || 'TESTER'
QuinzeOuVintePorcento.new(funcionario: @funcionario).calcula
else
raise FuncionarioInvalidoError, 'Funcionario não encontrado'
end
end
end
Responsabilidades separadas em classes menores, mais fáceis de serem mantidas, reutilizadas etc. Métodos privados são excelentes para melhorar a legibilidade de um método maior ou uma classe, entretanto, se perceber que existem duas diferentes responsabilidades em uma mesma classe, separe-os.
Há uma extensa lista de problemas que podemos encontrar em códigos que os tornam não coesos, entretanto aqui a ideia era apenas fazer uma pequena abordagem e esclarecer o conceito de classe coesas.
Alisson Araújo
💥
👏👏👏
Julia Guerreiro
Parabéns André! Amei o artigo!!!