Como adicionar análise sintática de código ruby em uma aplicação legada?
Tags: ruby, code, staticanalysis, rubocop
Published: 2020-03-04 23:39:27

---
title: Como adicionar análise sintática de código ruby em uma aplicação legada?
published: true
description: Passo a passo de como configurar e corrigir o código ruby usando Rubocop.
tags: ruby, code, static analysis, rubocop
---

Como é conhecido na comunidade ruby, [Rubocop] (https://docs.rubocop.org/) é a ferramenta mais usada para análise estática de código.

Ele já vem com muitas regras padrões, o que é muito útil para novos projetos, dessa forma você pode seguir a maioria dos padrões e apenas personalizar alguns deles.

Mas como proceder em projetos antigos ou maiores, com muitos arquivos e com muitos problemas?

A gem Rubocop vem com um formatador automático que pode corrigir muitos problemas automaticamente em nosso código.

Além disso, ele pode criar um arquivo de configuração TODO que ignora todos os problemas já presentes, arquivo por arquivo. Assim, podemos começar a usar o rubocop apenas para novos arquivos ou novos problemas nos arquivos existentes e corrigir cada problema de forma incremental.

A recomendação é corrigir todos os problemas possíveis automaticamente e manter os outros ignorados, a serem corrigidos em uma etapa futura.

Você pode começar adicionando a gem ao Gemfile (ou simplesmente instalando-a, mas é recomendável mantê-la no Gemfile).

```
$> bundle add rubocop
```
Com isso, você pode simplesmente executar o `rubocop` e todos os problemas serão mostrados. Como este exemplo:

```
$> rubocop
...
test/test_helper.rb:5:7: C: Style/ClassAndModuleChildren: Use nested module/class definitions instead of compact style.
class ActiveSupport::TestCase
      ^^^^^^^^^^^^^^^^^^^^^^^
test/test_helper.rb:6:81: C: Metrics/LineLength: Line is too long. [82/80]
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
                                                                                ^^

135 files inspected, 1659 offenses detected
```

A partir de agora podemos seguir por duas estratégias diferentes:

## 1. Corrigindo todos os problemas em uma única vez

A primeira estratégia é corrigir todos os problemas de uma única vez. Para isso, basta executar `rubocop --auto-correct` e todos os problemas *corrigíveis* são corrigidos e mostrados com o status " [Corrected] ".
```
$> rubocop --auto-correct
...
ENV['RAILS_ENV'] ||= 'test'
^
test/application_system_test_case.rb:1:1: C: [Corrected] Style/FrozenStringLiteralComment: Missing magic comment # frozen_string_literal: true.
require "test_helper"
^

135 files inspected, 1848 offenses detected, 1485 offenses corrected
```
Na próxima execução, apenas os problemas restantes serão mostrados.
```
$> rubocop
...
135 files inspected, 363 offenses detected
```


Agora podemos ignorar todos estes erros rodando o rubocop com a opção `--auto-gen-config`. Isso criará dois arquivos:
* `.rubocop_todo.yml`: Este arquivo contém regras para ignorar todos os problemas atuais.
* `.rubocop.yml`: O principal arquivo de configuração. Inicialmente apenas herdando / incluindo o arquivo TODO.
```
$> rubocop --auto-gen-config
...
135 files inspected, 133 offenses detected
Created .rubocop_todo.yml.
```
A próxima execução não deve retornar problemas, porque todos são ignorados em `.rubocop_todo.yml`:
```
$> rubocop
...
135 files inspected, no offenses detected
```

Agora você precisa apenas commitar todas as alterações e, opcionalmente, adicionar uma etapa no pipeline do CI para executar o rubocop e verificar seus arquivos de origem.


## 2. Corrigindo um problema de cada vez

A outra estratégia é fazer isso de forma incremental, usando um script, para separar cada correção de problema em um commit git diferente. Isso ajudará em uma futura identificação de alterações e o motivo pelo qual ela ocorreu.
Para fazer isso, basta executar este script e ele irá aplicar e commitar cada problema em um commit separado.

É importante criar esse script em um caminho ignorado pelo git, como `tmp/script.rb` por exemplo. Após criar o arquivo de script, basta chamá-lo executando `ruby tmp/script.rb`.

``` ruby
# tmp/script.rb
require "yaml"
require "rubocop"

puts("Criando configuração...")
system("rubocop --auto-gen-config")
system("rm .rubocop.yml") # OR git checkout .rubocop.yml if you want to start with some config

puts("Lendo a configuração de TODO para obter todos os problemas...")
todo = YAML.load_file(".rubocop_todo.yml")
system("rm .rubocop_todo.yml")

todo.each_with_index do |(cop, _cop_settings), index|
  puts("Corrigindo #{cop} (#{index + 1}/#{todo.count})...")
  if eval("RuboCop::Cop::#{cop.gsub("/", "::")}").new.support_autocorrect?
    files = `rubocop --auto-correct --force-default-config --only #{cop} --format files`.split("\n")
    system("git add #{files.join(" ")}")
    system("git commit -m \"Correção automática do Rubocop para #{cop}\" \
    		--author \"Rubocop Auto Correct <rubocop@rubocop>\"")
  else
    puts("  Não suporta auto-correção")
  end
end

puts("Gerando o arquivo TODO final com problemas incorrigíveis...")
system("rubocop --auto-gen-config")

puts("Finalizado")
```

Poderá levar alguns minutos para ser executado, dependendo do número e tamanho dos seus arquivos fonte.

O Rubocop foi projetado para ser seguro, mas algumas correções automáticas podem causar algum comportamento indesejado, por isso é importante ter uma boa cobertura de testes. É possível executar os testes unitários em cada iteração deste script ou apenas executá-los no final do processo. Nesse caso, se ocorrer um erro, você poderá usar `git bisect` para descobrir qual problema introduziu o erro.


No final, o script gera commits como os abaixo:
```
$> git log --oneline
fad06ac (HEAD -> rubocop) Correção automática do Rubocop para Style/WordArray
22aca61 Correção automática do Rubocop para Style/ClassCheck
06b12a2 Correção automática do Rubocop para Style/BracesAroundHashParameters
6dcace8 Correção automática do Rubocop para Style/BlockDelimiters
a504890 Correção automática do Rubocop para Style/BlockComments
1fae73b Correção automática do Rubocop para Naming/RescuedExceptionsVariableName
10802ca Correção automática do Rubocop para Lint/UnusedMethodArgument
bd11ffc Correção automática do Rubocop para Lint/UnusedBlockArgument
b8e098d Correção automática do Rubocop para Layout/TrailingBlankLines
4cabbd7 Correção automática do Rubocop para Layout/Tab
f29e766 Correção automática do Rubocop para Layout/SpaceInsidePercentLiteralDelimiters
b451890 Correção automática do Rubocop para Layout/SpaceInsideHashLiteralBraces
ee9eae6 Correção automática do Rubocop para Layout/SpaceInsideBlockBraces
f6611fd Correção automática do Rubocop para Layout/SpaceInsideArrayLiteralBrackets
f858545 Correção automática do Rubocop para Layout/SpaceInLambdaLiteral
3aeed2e Correção automática do Rubocop para Layout/SpaceBeforeComment
8b40371 Correção automática do Rubocop para Layout/SpaceBeforeBlockBraces
444f7ae Correção automática do Rubocop para Layout/SpaceAroundEqualsInParameterDefault
42d4e69 Correção automática do Rubocop para Layout/SpaceAfterComma
1acc003 Correção automática do Rubocop para Layout/SpaceAfterColon
28ed714 Correção automática do Rubocop para Layout/MultilineOperationIndentation
3e08ad7 Correção automática do Rubocop para Layout/MultilineMethodCallIndentation
f4611e5 Correção automática do Rubocop para Layout/MultilineMethodCallBraceLayout
6cc68b6 Correção automática do Rubocop para Layout/MultilineHashBraceLayout
9f66fe9 Correção automática do Rubocop para Layout/LeadingCommentSpace
...
```

Independentemente da estratégia utilizada, é recomendável resolver todos os problemas que permaneceram no arquivo TODO e, incrementalmente, mover todos os ignorados que realmente precisam ser ignorados para arquivo principal `rubocop.yml`. Isso garantirá que você continue melhorando seu código ou, pelo menos, mantendo sua qualidade.