quarta-feira, 22 de janeiro de 2014

Qual é um bom valor para Complexidade Ciclomática?


Sempre que eu ministro uma palestra, e falo sobre métricas (especialmente sobre C.C), há uma certa "celeuma"... As pessoas não entendem ou não aceitam bem o limite de CC que eu prego. Bem, vou tentar explicar os meus motivos.




O que é Complexidade Ciclomática? 

Se você não sabe, então a melhor fonte são os meus dois livros:

Se você não quer gastar dinheiro, não se preocupe! Tem várias explicações simples. No site do jQana, eu dou algumas delas: 
A própria Wikipedia tem um artigo excelente, que começa assim: 

Complexidade ciclomática (ou complexidade condicional) é uma métrica de software usada para indicar a complexidade de um programa de computador. Desenvolvida por Thomas J. McCabe em 1976, ela mede a quantidade de caminhos de execução independentes a partir de um código fonte.
Essa complexidade é computada através do grafo de fluxo de controle do programa: os nós do grafo correspondem a grupos indivisíveis de comandos, e uma aresta direcionada conecta dois nós se o segundo comando pode ser executado imediatamente após o primeiro. A complexidade ciclomática também pode ser aplicada a funções, módulos, métodos ou classes individuais dum programa.
Uma estratégia de teste de software formulada por McCabe é testar cada caminho independente dum programa, de forma que a quantidade de casos de teste será a complexidade ciclomática do programa.


Basicamente, é a contagem de "nós predicados", ou seja, comandos que criam desvio no fluxo natural do processamento. Por que? Porque tais comandos criam novos caminhos, aumentando a complexidade do código fonte. 

A complexidade pode ser medida por método ou por classe, em linguagens como Java. No caso de classes, a prática é calcular a média das complexidades dos métodos e "inner" classes encontrados em uma classe.

Nada como um exemplo...

Considere o seguinte exemplo de código fonte (não preste atenção nos detalhes, pois é apenas um "loren ipsum"): 

01) public class TesteCiclo {
02) public double calcular (int parametro1, int parametro2) {
03)  double retorno = 0.0d;
04)  if (parametro1 <= 7) {
05)   retorno = parametro1 * 1.5;
06)   if (parametro2 > parametro1) {
07)    retorno = retorno * 0.75;
08)    if (parametro2 == 1) {
09)     retorno = -1.0d;
10)    }
11)   }
12)  }
13)  return retorno;
14) }
15)}


Podemos transformar este trecho de código em um grafo, de maneira simples:


A entrada no método "calcular()" conta como um caminho básico. Depois, para cada nó que cria um desvio de fluxo, somamos mais 1 (nós em preto).

Logo, pela fórmula simplificada de McCabe (*) temos:

V(G) = P + 1

Onde: P = nós predicados.

(*) http://www.mccabe.com/pdf/mccabe-nist235r.pdf

Neste caso, temos o valor 4, que é o limite máximo para criação de casos de teste, necessários para cobrir todo o código.

Mas, qual é o limite? O que é considerado bom? 

Reza a lenda, que o próprio McCabe disse que, caso um "módulo" ultrapassasse o valor de 10, deveria ser refatorado. Logo, muita gente passou a considerar 10 um valor "bom" e limítrofe, com tolerâncias até 15 ou 20.

Porém, pensando um pouco, vemos que o valor 10 é muito alto, pois significa:

  • 10 caminhos por onde o fluxo pode passar;
  • 10 casos de teste para cobrir o código;
  • 10 motivos para alterações de regras.
Também significa que seu código fonte está meio "macarrônico"... Talvez por que você esteja muito focado em prodedimentos, ao invés de reuso, com muitos "loops" e ninhos de "if". Exemplos de implementações que geram alto valor de CC:
  • Análise ("parsing") manual de XML, através de pesquisa de substrings e loops;
  • Leitura e pesquisa sequencial de bancos de dados, através de SQL;
  • Geração manual de relatórios, usando quebras de níveis;
Para essas três coisas, existem componentes e frameworks prontos, que evitam a criação de procedimentos. Sempre que tivermos soluções prontas, devemos substituir procedimentos por configuração. Para os três casos acima, temos:
  • Usar DOM (XPath, XQuery) para análise de XML;
  • Usar um framework O/R, baseado em JPA, como o Hibernate;
  • Usar um componente de relatório, como o JasperReports ou o Apache FOP;

O valor 5 parece ser mais adequado

Vamos pensar em um método com CC = 5. O que significa isso? Que você tem 5 caminhos básicos para teste, e 5 casos de teste para conseguir cobrir o código todo do método. Não é muito nem pouco. E você consegue entender o que o método faz com relativa facilidade. 

Não sou só eu que penso assim... Outros autores também acreditam que 5 é o limite para código simples. Por exemplo, Steve McConnell, que escreveu o livro "Code Complete", pensa da mesma forma. E criou uma tabela com valores limites: 
  • De 0 a 5: Seu código está, provavelmente, ok;
  • Entre 6 e 10: Pense em maneiras de simplificar o código;
  • Maior que 10: Quebre o código em dois e insira uma chamada da primeira parte para a segunda;
Porém, existe uma explicação mais científica para isto, que eu citei em meu artigo "O Nirvana do Bom Código Fonte", oriunda do campo da Psicologia Cognitiva. A segunda corroboração vem da área de Psicologia cognitiva, mas especificamente, de um artigo escrito pelo pesquisador George A. Miller, no qual ele sugere que as pessoas conseguem manter 7+- 2 objetos em sua memória de trabalho. Alguns conseguem lidar com até 9 objetos simultaneamente, mas alguns só conseguem lidar com 5. Logo, faz todo sentido limitar a complexidade a este número, tornando fácil para todas as pessoas compreenderem o nosso código fonte.

O limite "popular" de 10, é considerado acima do que a maioria das pessoas consegue entender ou lidar diretamente. 

Trocando 6 por meia dúzia

Os céticos dirão: "Mas você está apenas trocando 6 por meia-dúzia, afinal de contas, a complexidade vai continuar a existir". É verdade, mas nós a "diluímos". A complexidade não está mais concentrada em uma única classe, mas dividida em classes menores e mais fáceis de testar. Podemos focar melhor nossos testes e construir menos testes por classe. 

Como medir a Complexidade Ciclomática?

Use o jQana! Ele é um plugin Maven, e gera seu relatório no "maven site" do seu projeto.