Flog é outro analisador de complexidade para Ruby. Diferentemente do Saikuro, que mede a complexidade ciclomática real do código, o Flog tem o propósito de mostrar padrões de tortuosidade no seu código. É mais uma ferramenta interessante e de fácil uso para analisar a evolução da complexidade do seu código:
Instalação
Para instalar o Flog, basta rodar o comando gem:
~$ sudo gem install flog
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 o mesmo exemplo que mostrarmos no artigo sobre o uso do Saikuro.
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 Flog. O comando é simples:
~$ flog hasher.rb
Resultado inicial
O resultado da execução acima pode ser visto na listagem abaixo:
Total Flog = 57.7 (7.2 +/- 62.8 flog / method)
DocumentHasher#hash: (27.3)
9.0: send
8.6: assignment
5.8: []
5.4: branch
2.8: each
1.9: to_s
1.7: +
1.5: new
1.3: hash
1.3: id
DocumentHasherTest#setup: (8.3)
6.5: sclass
1.3: assignment
1.3: new
0.4: lit_fixnum
Note que o método hash possui um fator de "tortuosidade" de 27 e que os maiores culpados são o envio de mensagens (send) e atribuições. A "tortuosidade" total do programa está em 57.7, que representa o cômputo de todos os métodos do programa.
Alterando o programa
Para fins de exemplo, podemos tentar reduzir a complexidade do método hash como fizemos no artigo anterior usando o exemplo abaixo:
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:
Total Flog = 58.1 (5.3 +/- 10.3 flog / method)
DocumentHasher#hash: (10.7)
3.2: []
3.0: branch
2.6: assignment
1.5: new
1.3: build_extra_fields
1.3: hash
1.3: build_fields
1.3: id
DocumentHasher#build_extra_fields: (9.4)
4.2: send
2.8: assignment
1.8: to_s
1.6: +
1.3: branch
1.3: each
DocumentHasherTest#setup: (8.3)
6.5: sclass
1.3: assignment
1.3: new
0.4: lit_fixnum
DocumentHasherTest#test_hash_fields: (7.0)
3.0: []
2.6: assert_equal
1.3: hash
1.3: assignment
Note que a complexidade dos métodos caiu substancialmente. Os métodos estão mais simples e mais balanceados. A "tortuosidade" geral subiu um pouco com a adição de algumas chamadas, mas, analisando os métodos individualmente é possível observar que a somatória de complexidade dos três métodos envolvidos é menor do que a complexidade original. Mais uma vez, o código está mais legível e mais testável.
Conclusão
O Flog representa uma métrica arbitrária de complexidade que, mesmo assim, pode ser usada para acompanhar o desenvolvimento de um base de código. Um exemplo disso pode ser visto no texto do Carlos Villela mostrando a evolução do código do Rails ao longo dos anos.
Como de usual, sugestões, críticas e dúvidas são bem-vindos. No próximo artigo, esfolando o seu código Ruby com mais ferramentas de análise de complexidade.
