Saikuro, complexidade ciclomática para Ruby

November 12th, 2008 § 0 comments

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:

Saikuro 1

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:

Saikuro 2

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.

Tagged

Leave a Reply

Your email address will not be published. Required fields are marked *

What's this?

You are currently reading Saikuro, complexidade ciclomática para Ruby at Superfície Reflexiva.

meta