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.

2 Comentários

Alisson Araújo

💥
👏👏👏

29 jun, 2023 Responder

Julia Guerreiro

Parabéns André! Amei o artigo!!!

30 jun, 2023 Responder