Identificando Code Smells em seu código Ruby

Publicado por Luiz Cezer Marrone Filho no dia dev

Desenvolvimento de Software

A refatoração de código é uma prática quase diária no desenvolvimento de software e seria ideal se todo desenvolvedor tivesse o hábito de aplicar a regra do escoteiro em todo código ruim que ele encontrasse.

Porém nem sempre é fácil saber o que é um “código ruim”, ou um “código que está fedendo” e para isso é ideal que desenvolvedores conheçam os padrões que possam identificar um código que está “fedendo”, ou seja, encontrar um code smell em seu código.

Existem vários code smells que podem ajudar a detectar quando um código está aprodrecendo e poderá prejudicar sua aplicação.

Aqui na Resultados Digitais trabalhamos em um time com cerca de 60 desenvolvedorese fazemos uso do Reek para realizar análises do nosso código. Mas além de confiar na análise do Reek, também é preciso conhecer os smells mais comuns que podem ser encontrados em seu código Ruby:

Long Method

Esse é um dos mais fáceis de ser identificado por seu nome ser bem descritivo e ser de fácil visualização. Como o próprio nome diz, esse smell visa identificar métodos que sejam grandes.

Mas afinal, o que pode ser considerado um método grande? Se você conhece as Regras de Sandi Metz para desenvolvedores, já sabe que um método pode ter no máximo 5 linhas, isso também inclui linhas de condicionais como if e else. Então para evitar esse smell é importante que todos seus métodos obedecam essa condição.

Métodos grandes são um smell e podem indicar problemas. Já os métodos menores são mais propensos a obedecer o Princípio de Responsabilidade Única. Dessa forma, são mais fáceis de testar e têm uma melhor manuteabilidade.

Large Class

Outro smell facilmente encontrado em vários códigos. O Large Class avalia basicamente o mesmo que o Long Method, porém, verificando o tamanho de suas classes, que nesse caso, não devem ter mais do que 100 linhas de código

A principal intenção desse smell também é detectar classes que quebram o Princípio de Responsabilidade Única, ou seja, classes que têm mais do que apenas uma responsabilidade

Classes maiores, assim como métodos, tendem a ser mais complexas de testar e manter. Uma pequena alteração pode acabar desencadeando dezenas de outras alterações e uma pode acabar impactando pontos distintos de software, devido a classe estar com muitas responsabilidades e podendo está muito acoplada a outras classes.

O tratamento mais comum para esse tipo de smell é quebrar as classes em mais classes, garantindo que cada uma seja responsável por apenas um contexto dentro da aplicação.

Refused Bequest

O uso de herança em linguagens orientadas a objeto é muito comum. Geralmente ela é utilizada para criar compartilhamento de código entre as classes.

Essa aplicação de herança deve ser usada com cautela, já que ela pode acabar gerando um alto acomplamento entre as classes. Herança deve ser utilizada com foco em especialização e não compartilhamento de código, como já visto no post Composição e Herança no Ruby.

Além disso, fazer o uso dessa hierarquia irá criar um smell que pode prejudicar seu código. Esse smell é chamado de Refused Bequest e diz que não há sentido em fazer subclasses herdarem atributos e/ou métodos apenas para satisfazer a herança, sendo que essas subclasses nem fazem uso desses comportamentos.

Nesse tipo de situação o uso de herança deve ser repensado e talvez quebrado em mais classes/módulos de modo que as subclasses herdem só o que for necessário.

Seguir essa orientação também irá fazer com que seu código não quebre o Princípio de Segregação de Interface dentro dos princípios SOLID.

Divergent Changes

Imagine uma situação onde é preciso alterar um método de uma determinada classe. Você possui um método para criação de um novo objeto e precisa mudar a assinatura ou então a implementação desse metódo. Porém, você percebe que para que essa alteração tenha efeito você também precisa alterar métodos de busca, edição, ordenação e exibição.

É dessa forma que esse smell é identificado, ou seja, quando você nota que para alterar um método é preciso alterar outros métodos da mesma classe, métodos que não deveriam ter um acoplamento tão alto com o método ao qual a alteração deveria ter sido feita.

É provável que essa classe esteja sofrendo de outros problemas como uma fraca estruturação e também duplicação de código.

Algumas técnicas que podem ajudar a resolver esse smell são aplicar Extract Method para que eles fiquem independentes ou em outros casos extração de comportamentos para novas classes.

Shotgun Surgery

Enquanto o Divergent Changes se refere ao problema de que para se aplicar uma alteração em um determinado trecho de uma classe, vários outros pontos dessa mesma classe devem ser alterados, o Shotgun Surgery é identificado quando para se aplicar uma alteração em um método de uma determinada classe, é preciso aplicar uma ou várias alterações em outras classes além dessa.

Quando essa situação ocorre, a manutenção de código fica mais complicada. Ficando clara a falta do Princípio de Responsabilidade Única e também torna fácil alterar algum comportamento de modo errado, podendo assim causar erros na aplicação.

Nesse tipo de situação é preciso aplicar técnicas como Move Method e Move Field para agrupar comportamentos similares em classes que tenham o mesmo contexto dentro do domínio da aplicação. Dessa forma é possível ter uma melhor organização de código, minimizar duplicação e tornar a manuteção mais simples.

Feature Envy

Imagine a situação onde se está trabalhando em um método Invoice#details porém na implementação desse método você nota que está referenciando objeto customer muitas vezes para obter detalhes desse objeto. Como no exemplo abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Invoice
  attr_reader :customer, :order

  def initialize(customer, order)
    @customer = customer
    @order = order
  end

  def details
    return "Sem informações sobre cliente" unless customer.active?
    "#{customer.name} - #{customer.address} - fez o pedido #{order.id}"
  end
end

Nesse tipo de situação fica claro que para o objeto Invoice o método details precisa conhecer muitos detalhes que são pertinentes ao objeto customer, isso aumenta muito o acoplamento entre os objetos e deve ser evitado. Quando esse smell é detectado pode ser um indício de problemas mais graves na arquitetura e design do código.

Para resolver essa problema é preciso que a própria classe Customer saiba como expor os detalhes que serão necessários para o método Invoice#details. Isso ajuda a reduzir o acoplamento, faz com que as classes continuem tendo apenas uma responsabilidade e melhora a organização de código.

Um exemplo de como o código acima poderia ser melhorado:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Customer
  # ...

  def to_s
    "#{name} - #{address}"
  end

  #...
end

class Invoice
  attr_reader :customer, :order

  def initialize(customer, order)
    @customer = customer
    @order = order
  end

  def details
    return "Sem informações sobre cliente" unless customer.active?
    "#{customer} - fez o pedido #{order.id}"
  end
end

Uma mudança simples que traz melhorias já que agora a class Invoice não sabe detalhes ou atributos internos da classe Customer.

Conclusão

Conhecer os smells mais comuns dentro do seu código é a melhor maneira de entender onde ele poderá impactar e como aplicar a melhor a melhor ténica de refatoração para o problema.

E você, já conhecia esses problemas? Já encontrou eles vagando pelo seu código? Comente conosco.

Luiz Cezer Marrone Filho

Luiz Cezer Marrone Filho

Full Stack Developer

Comentários