Saikuro é uma analisador de complexidade ciclomática para Ruby. A instalação e uso do mesmo são bem simples e serão descritos aqui brevemente.
Instalação
Para instalar o Saikuro, basta rodar o comando gem:
~$ sudo gem install Saikuro
Esse comando instala a gem em si e um executável que pode ser usado na linha de comando.
Um programa de exemplo
Para demonstrar o uso da ferramenta, vamos utilizar um programa de referência. Esse programa será um simples transformador de objetos Ruby em hashes equivalentes e está listado abaixo, com os testes necessários:
require "test/unit"
class DocumentHasher
def initialize(object, options = {})
@object = object
@options = options
end
def hash
hash = { :id => @object.id }
if @options[:fields]
@options[:fields].each do |field|
hash[field] = @object.send(field)
end
end
if @options[:extrafields]
@options[:extrafields].each do |field|
hash[field] = @object.send(field.tos + "field")
end
end
hash
end
def self.hash(object, options = {})
self.new(object, options).hash
end
end
class DocumentHasherTest < Test::Unit::TestCase
Struct.new("HashExample", :id, :title, :author)
TITLEFIELD = "Test"
AUTHORFIELD = "Author"
EXTRA_FIELD = "Extra"
def setup
@object = Struct::HashExample.new(1, TITLEFIELD, AUTHORFIELD)
class << @object
def extra_field
"Extra"
end
end
end
def testhashid
result = DocumentHasher.hash(@object)
assert_equal 1, result[:id]
end
def testhashfields
result = DocumentHasher.hash(@object,
:fields => [:title, :author])
assertequal TITLEFIELD, result[:title]
assertequal AUTHORFIELD, result[:author]
end
def testhashextrafields
result = DocumentHasher.hash(@object,
:fields => [:title, :author],
:extrafields => [:extra])
assertequal EXTRAFIELD, result[:extra]
end
end
Salve esse arquivo para um arquivo chamado hasher.rb e rode-o com o comando abaixo para verificar sua execução:
~$ ruby hasher.rb
Utilização
Com esse programa, podemos rodar o Saikuro. O comando é simples:
~$ saikuro -c -t -y 0 -w 11 -e 16 -i hasher.rb -o report
Esse comando roda o analisador de complexidade com os seguintes parâmetros:
- -t, para analisar tokens também
- -y 0, para exibir a complexidade de todos os métodos
- -w 11, para exibir warnings para métodos de complexidade superior a 11
- -e 16, para exibir erros para métodos de complexidade superior a 16
- -i hasher.rb, o arquivo a ser analisado (pode ser um diretório também)
- -o report, o diretório onde gerar o relatório de saída
Resultado inicial
O resultado da execução acima pode ser visto, parcialmente, na imagem abaixo, que mostra os resultados somente para a classe em que estamos interessados:

Note que o método hash possui complexidade 5 que reflete os 4 comandos decisórios (2 if e 2 each) mais um ponto de saída pelo cálculo mais simples da complexidade. Os demais métodos, puramente lineares, possuem complexidade 1.
No diretório de resultado há também um arquivo que mostra a quantidade de tokens gerados no programa. Este relatório, com os níveis de aviso baixo, tende a reportar erros mesmo em condições consideradas normais.
Alterando o programa
Para fins de exemplo, podemos tentar reduzir a complexidade do método hash. Digamos que façamos a seguinte alteração:
require "test/unit"
class DocumentHasher
def initialize(object, options = {})
@object = object
@options = options
end
def hash
hash = { :id => @object.id }
build_fields(hash, @options[:fields] || {})
build_extra_fields(hash, @options[:extra_fields] || {})
hash
end
def self.hash(object, options = {})
self.new(object, options).hash
end
protected
def build_fields(hash, fields)
fields.each do |field|
hash[field] = @object.send(field)
end
end
def build_extra_fields(hash, fields)
fields.each do |field|
hash[field] = @object.send(field.to_s + "_field")
end
end
end
class DocumentHasherTest < Test::Unit::TestCase
Struct.new("HashExample", :id, :title, :author)
TITLE_FIELD = "Test"
AUTHOR_FIELD = "Author"
EXTRA_FIELD = "Extra"
def setup
@object = Struct::HashExample.new(1, TITLE_FIELD, AUTHOR_FIELD)
class << @object
def extra_field
"Extra"
end
end
end
def test_hash_id
result = DocumentHasher.hash(@object)
assert_equal 1, result[:id]
end
def test_hash_fields
result = DocumentHasher.hash(@object,
:fields => [:title, :author])
assert_equal TITLE_FIELD, result[:title]
assert_equal AUTHOR_FIELD, result[:author]
end
def test_hash_extra_fields
result = DocumentHasher.hash(@object,
:fields => [:title, :author],
:extra_fields => [:extra])
assert_equal EXTRA_FIELD, result[:extra]
end
end
Rodando o comando novamente, teríamos:

Como os testes demonstram, o programa funciona da mesma forma. E como o relatório de complexidade também demonstra, a complexidade do programa permanece a mesma no geral e a dos métodos diminuiu. O resultado é um programa mais legível e mais facilmente testável.
Conclusão
Embora métricas em si não tenham nenhum poder de tornar o código de um programa melhor, acompanhar a evolução das mesmas é uma ferramenta fundamental para garantir a qualidade do código. Para quem usa o Ruby, o Saikuro é uma ferramenta simples e rápida para isso. Para quem não usa, é fácil achar ferramentas que se adequam a outras linguagens.
Como de usual, sugestões, críticas e dúvidas são bem-vindos. No próximo artigo, ainda hoje, esfolando o seu código Ruby com mais ferramentas de análise de complexidade.
