2. Sobre esta Apresentação
2/68
1. Conteúdo: Complexidade Ciclomática
2. Área/Foco: Qualidade de Software, Manutenção e Testabilidade
3. Público alvo: Desenvolvedores de Software e Gestores
4. Conteúdo relacionado: Cobertura de Testes, Entregas Contínuas
Organização (+- 60 minutos)
Parte 1 – Introdução: Conceito e Regra de Cálculo
Parte 2 – Aplicação: Padrão de Qualidade, Importância e Utilidade
Parte 3 – Prática: Refatoração e Redução
Parte 4 – Conclusão:
3. Parte 1 - Introdução
3/68
Conceito e Regra de Cálculo
4. Conceito
4/68
Complexidade Ciclomática é uma métrica de software que
indica a complexidade de um programa* (McCabe, 1976).
* Programa, método, rotina etc
Ela mede a quantidade máxima de caminhos
linearmente independentes no código fonte.
5. Conceito
5/68
Caminho Linearmente Independente é qualquer
caminho do programa que introduz pelo menos um
novo conjunto de comandos de processamento ou
uma nova condição. Quando definido em termos de
grafo de fluxo, um caminho independente deve incluir
pelo menos uma aresta que não tenha sido
atravessada antes de o caminho ser definido
(Pressman).
1
B X
2
C Y
A
Independentes: A-1-B-2-C-D A-1-X-2-C-D A-1-B-2-Y-D
Não Independente: A-1-X-2-Y-D
D
6. Exemplo 1
6/68
public int sum(int a, int b) {
int result = a + b;
System.out.println(result);
return result;
}
1
result = a + b
println(result)
return result
O código apresenta três
linhas e um único caminho.
O código apresenta três
linhas e um único caminho.
7. Teste de Unidade do Exemplo 1
7/68
1
result = a + b
println(result)
return result
O teste de unidade precisa
somente de um cenário de teste
( qualquer a + qualquer b)
O teste de unidade precisa
somente de um cenário de teste
( qualquer a + qualquer b)
@Test
public void testSum() {
assertEquals(2, obj.sum(1, 1));
}
8. Exemplo 2 – Soma Somente Diferentes
8/68
public int sum(int a, int b) {
int result= 0;
if (a != b) {
result = a + b;
}
System.out.println(result);
return result;
}
1
result = 0
println(result)
Não
Dois caminhos são possíveis:
1. Onde ‘a’ e ‘b’ são iguais
2. Onde ‘a’ e ‘b’ são diferentes
Dois caminhos são possíveis:
1. Onde ‘a’ e ‘b’ são iguais
2. Onde ‘a’ e ‘b’ são diferentes
a != b
return result
Sim
2
result = a + b
9. Teste de Unidade do Exemplo 2
9/68
1
result = 0
println(result)
Não
a != b
return result
Sim
2
result = a + b
Um cenário pode cobrir todas
as linhas, mas é prudente
escrever mais de um
Um cenário pode cobrir todas
as linhas, mas é prudente
escrever mais de um
@Test
public void test() {
// [1] cobre um caminho a==b
// cobre 80% das linhas
assertEquals(0, obj.sum(1, 1));
// [2] cobre outro caminho a!=b
// cobre 100% das linhas
assertEquals(3, obj.sum(1, 2));
}
10. Exemplo 3 – Ano Bissexto
10/68
public boolean isBissexto(int ano){
if (ano % 400 == 0){
return true;
}
if (ano % 4 == 0 && ano % 100 != 0){
return true;
}
return false;
}
return false% 400 == 0 return true
2
Sim
% 4 == 0
Não
Sim
1
Não
return false
% 100 == 0
Não
Sim
return true
3
4
Há 4 caminhos possíveis:
1. Divisível por 400 : true
2. Nem por 400 nem 4: false
3. Divisível somente por 4: true
4. Divisível por 4 E 100 : false
Há 4 caminhos possíveis:
1. Divisível por 400 : true
2. Nem por 400 nem 4: false
3. Divisível somente por 4: true
4. Divisível por 4 E 100 : false
11. Cenários do Exemplo 3
11/68
return false% 400 == 0 return true
2
Sim
% 4 == 0
Não
Sim
1
Não
return false
% 100 == 0
Não
Sim
return true
3
4
Há 4 caminhos possíveis:
1. Divisível por 400 : true
2. Nem por 400 nem 4: false
3. Divisível somente por 4: true
4. Divisível por 4 E 100 : false
Há 4 caminhos possíveis:
1. Divisível por 400 : true
2. Nem por 400 nem 4: false
3. Divisível somente por 4: true
4. Divisível por 4 E 100 : false
// 4 caminhos independentes
assertTrue(obj.isBissexto(1600));
assertFalse(obj.isBissexto(2001));
assertTrue(obj.isBissexto(2004));
assertFalse(obj.isBissexto(1900));
12. Exemplo 4 – Sorveteria (Tipo/Pote/Cobertura)
12/68
public int precoSorvete(boolean premium,
boolean casquinha, int coberturas) {
int preco = 0;
if (premium) {
preco = 20;
} else {
preco = 15;
}
if (casquinha) {
preco = preco + 2;
} else {
preco = preco + 1;
}
if (coberturas > 1){
preco = preco + 2;
} else {
preco = preco + 1;
}
return preco;
}
if
15 20
if
+1 +2
if
+1 +2
P
0
Quantos
Caminhos?
Quantos
Caminhos?
13. Exemplo 4 – Sorveteria (8 Caminhos Possíveis)
13/68
if
15 20
if
+1 +2
if
+1 +2
17
0
if
15 20
if
+1 +2
if
+1 +2
18
0
if
15 20
if
+1 +2
if
+1 +2
18
0
if
15 20
if
+1 +2
if
+1 +2
19
0
if
15 20
if
+1 +2
if
+1 +2
22
0
if
15 20
if
+1 +2
if
+1 +2
23
0
if
15 20
if
+1 +2
if
+1 +2
23
0
if
15 20
if
+1 +2
if
+1 +2
24
0Premium
Copinho
1 Cobertura
Comum
Copinho
1 Cobertura
Comum
Copinho
2 Coberturas
Comum
Casquinha
1 Cobertura
Comum
Casquinha
2 Coberturas
Premium
Copinho
2 Coberturas
Premium
Casquinha
1 Cobertura
Premium
Casquinha
2 Coberturas
14. Questão 1
14/68
É razoável criar testes de unidade com um cenário
para cada caminho possível de um método?
assertEquals(17, sorveteria.precoSorvete(false, false, 1)); // Comum-Copinho-1Cob
assertEquals(18, sorveteria.precoSorvete(false, false, 2)); // Comum-Copinho-2Cob
assertEquals(18, sorveteria.precoSorvete(false, true, 1)); // Comum-Casquinha-1Cob
assertEquals(19, sorveteria.precoSorvete(false, true, 2)); // Comum-Casquinha-2Cob
assertEquals(22, sorveteria.precoSorvete(true, false, 1)); // Premium-Copinho-1Cob
assertEquals(23, sorveteria.precoSorvete(true, false, 2)); // Premium-Copinho-2Cob
assertEquals(23, sorveteria.precoSorvete(true, true, 1)); // Premium-Casquinha-1Cob
assertEquals(24, sorveteria.precoSorvete(true, true, 2)); // Premium-Casquinha-2Cob
Mais um IF-ELSE aumenta os cenários para 16!
15. Exemplo 4 – Sorveteria (Complexidade Ciclomática = 4)
15/68
if
15 20
if
+1 +2
if
+1 +2
17
0
if
15 20
if
+1 +2
if
+1 +2
22
0
if
15 20
if
+1 +2
if
+1 +2
18
0
if
15 20
if
+1 +2
if
+1 +2
18
0A B C D
16. Exemplo 4 – Sorveteria1 (2 Cenários Cobrem 100%)
16/68
assertEquals(17,
sorveteria.precoSorvete
(false, false, 1));
assertEquals(24,
sorveteria.precoSorvete
(true, true, 2));
if
15 20
if
+1 +2
if
+1 +2
17
0
if
15 20
if
+1 +2
if
+1 +2
22
0A B
17. Exemplo 4 – Resultado da Cobertura
17/68
Complexidade Ciclomática 4
sugere que 4 cenários (bem feitos)
são suficientes para boa cobertura
de todas as linhas e condições
(apenas 2 são necessários)
Complexidade Ciclomática 4
sugere que 4 cenários (bem feitos)
são suficientes para boa cobertura
de todas as linhas e condições
(apenas 2 são necessários)
18. Exemplo 5 – Sorveteria 2 (Ifs sem Else)
18/68
if
+5
+1
+1
if
+1
P
15
+1
if
Quantos caminhos?Quantos caminhos?
public int precoSorvete(boolean premium,
boolean casquinha, int coberturas) {
int preco = 15;
if (premium) {
preco = preco + 5;
}
preco = preco + 1; // pote
if (casquinha) {
preco = preco + 1;
}
preco = preco + 1; // cobertura
if (coberturas > 1){
preco = preco + 1;
}
return preco;
}
19. Exemplo 5 – Sorveteria 2 (8 Caminhos)
19/68
if
+5
+1
+1
if
+1
18
15
+1
if
if
+5
+1
+1
if
+1
17
15
+1
if
if
+5
+1
+1
if
+1
18
15
+1
if
if
+5
+1
+1
if
+1
19
15
+1
if
if
+5
+1
+1
if
+1
23
15
+1
if
if
+5
+1
+1
if
+1
22
15
+1
if
if
+5
+1
+1
if
+1
23
15
+1
if
if
+5
+1
+1
if
+1
24
15
+1
if
Com
Copo
1 Cob
Prem
Copo
1 Cob
Com
Copo
2 Cob
Com
Casc
1 Cob
Com
Casc
2 Cob
Prem
Copo
2 Cob
Prem
CAsc
2 Cob
Prem
Casc
2 Cob
20. Exemplo 5 – Sorveteria 2 (Complexidade Ciclomática = 4)
20/68
B
if
+5
+1
+1
if
+1
P
15
+1
if
A
if
+5
+1
+1
if
+1
P
15
+1
if
C
if
+5
+1
+1
if
+1
P
15
+1
if
D
if
+5
+1
+1
if
+1
P
15
+1
if
4 Caminhos linearmente independentes
21. Exemplo 5 – Sorveteria 2 (1 Cenário cobre 100%)
21/68
if
+5
+1
+1
if
+1
P
15
+1
if
assertEquals(24,
sorveteria.precoSorvete
(true, true, 2));
22. Exemplo 5 – Resultado da Cobertura
22/68
Complexidade Ciclomática 4
sugere que 4 cenários (bem feitos)
são suficientes para boa cobertura
de todas as linhas e condições
(apenas 1 é necessário)
Complexidade Ciclomática 4
sugere que 4 cenários (bem feitos)
são suficientes para boa cobertura
de todas as linhas e condições
(apenas 1 é necessário)
23. Exemplo 6 – Sorveteria 3 (Ifs Aninhados)
23/68
Quantos caminhos?Quantos caminhos?
if17 20
if
+1
+2
If
+1 +2
P
0
public int precoSorvete(boolean premium, boolean casquinha, int coberturas) {
int preco = 0;
if (premium) { // só premiun tem casquinha
preco = 20;
if (casquinha) { // só casq tem cobe
preco = preco + 2;
if (coberturas > 1){
preco = preco + 2;
} else {
preco = preco + 1;
}
} else {
preco = preco + 1;
}
} else {
preco = 15 + 1 + 1; // copo + 1 cob
}
return preco;
}
24. Exemplo 6 – Sorveteria 3 (Complexidade Ciclomática = 4)
24/68
if17 20
if
+1
+2
If
+1 +2
P
0A
if17 20
if
+1
+2
If
+1 +2
P
0B
if17 20
if
+1
+2
If
+1 +2
P
0
D C
Similar ao exemplo “Ano Bissexto”. Cada “preco=” poderia ser um “return”
4 Caminhos
linearmente
independentes
25. Exemplo 6 – Sorveteria 3 (2 Cenários cobrem 77,6%)
25/68
assertEquals(17,
sorveteria.precoSorvete
(false, false, 1));
assertEquals(24,
sorveteria.precoSorvete
(true, true, 2));
if17 20
if
+1
+2
If
+1 +2
0A
if17 20
if
+1
+2
If
+1 24
0
C
27. Exemplo 6 – Sorveteria 3 (4 Cenários Cobrem 100%)
27/68
17 = (false, false, 1) // 1
24 = (true, true, 2)) // 2
23 = (true, true, 1)) // 3
21 = (true, false, 2)) // 4
if17 20
if
+1
+2
If
+1 +2
0A
if17 20
if
21
+2
If
23 24
0
CD
B
28. Exemplo 6 – Resultado da Cobertura
28/68
Complexidade Ciclomática 4
sugere que 4 cenários (bem feitos)
são suficientes para boa cobertura
de todas as linhas e condições
(4 são necessários)
Complexidade Ciclomática 4
sugere que 4 cenários (bem feitos)
são suficientes para boa cobertura
de todas as linhas e condições
(4 são necessários)
29. Exemplos 4, 5 e 6 – Sorveteria 1 x 2 x 3 (CC = 4 em Todos)
29/68
if
15 20
if
+1 +2
if
+1 +2
P
0
if
+5
+1
+1
if
+1
P
15
+1
if
1 2
Sorveterias 1 e 2
8 Caminhos
4 Circuitos
1 = 2 Cenários
2 = 1 Cenário
Sorveteria 3
4 Caminhos
4 Circuitos
4 Cenários
if17 20
if
+1
+2
If
+1 +2
P
03
30. Exemplos 4, 5 e 6 – NPATH x Complexidade Ciclomática
30/68
@Test
public void testAllPaths() { // Baseado em NPATH
assertEquals(17, sorveteria.precoSorvete(false, false, 1)); // Comum-Copinho-1Cob
assertEquals(18, sorveteria.precoSorvete(false, false, 2)); // Comum-Copinho-2Cob
assertEquals(18, sorveteria.precoSorvete(false, true, 1)); // Comum-Casquinha-1Cob
assertEquals(19, sorveteria.precoSorvete(false, true, 2)); // Comum-Casquinha-2Cob
assertEquals(22, sorveteria.precoSorvete(true, false, 1)); // Premium-Copinho-1Cob
assertEquals(23, sorveteria.precoSorvete(true, false, 2)); // Premium-Copinho-2Cob
assertEquals(23, sorveteria.precoSorvete(true, true, 1)); // Premium-Casquinha-1Cob
assertEquals(24, sorveteria.precoSorvete(true, true, 2)); // Premium-Casquinha-2Cob
}
@Test
public void testIndependentPaths() { // Baseado em CC
assertEquals(17, sorveteria.precoSorvete(false, false, 1)); // Comum-Copinho-1Cob
assertEquals(23, sorveteria.precoSorvete(true, true, 1)); // Premium-Casquinha-1Cob
assertEquals(21, sorveteria.precoSorvete(true, false, 2)); // Premium-Copinho-2Cob
assertEquals(24, sorveteria.precoSorvete(true, true, 2)); // Premium-Casquinha-2Cob
}
A Sorveteria com 9 IF-ELSE teria 512 caminhos, mas CC = 10 (linhas do teste)
31. Limite Máximo de Cenários
31/68
Dependendo do algoritmo, a quantidade de
caminhos únicos pode variar, mas o valor de
complexidade ciclomática é o limite superior do
número de cenários necessários para cobrir 100%
das linhas e condições, se definidos corretamente
A Sorveteria com 9 IF-ELSE teria 512 caminhos, mas CC = 10
32. Correlação 1 - Testabilidade
32/68
Se não há impacto é porque não há teste de unidade
Quanto maior a complexidade ciclomática em
um método, maior tende a ser o número de
cenários necessários em um teste deste
mesmo método.
33. Correlação 2 - Manutenção
33/68
Quanto maior a complexidade ciclomática em um
método, maior será a dificuldade de entendimento
e o risco de inserção de novos defeitos neste
método, aumentando o esforço de manutenção.
Durante a construção inicial o impacto é mais baixo. O autor ainda entende o próprio código
34. Cálculo da Complexidade Ciclomática
34/68
Nó
Aresta
CC = Arestas – Nós + 1
if17 20
if
+1
+2
If
+1 +2
P
0
CC = 14 – 11 + 1 = 4
Preciso desenhar um
grafo para cada método
para calcular?
Preciso desenhar um
grafo para cada método
para calcular?
35. Regra de Cálculo Baseada em Keywords (Sonar)
35/68
1. Inicia em “Um” para o método (com ou sem retorno)
2. Adiciona “Um” para cada elemento de fluxo abaixo:
2.1 Seleção if, case
2.2 Loops for, while, do-while, break, e
continue.
2.3 Operadores &&, ||, ?
2.4 Exceções catch, finally, throw, ou throws
2.5 Fluxo return que não seja o último
(else, default, finally, : e return não incrementam o valor)
37. Exercício de Contagem (Soma Diferentes – Exemplo 2)
37/68
?
public int sum(int a, int b) {
int result= 0;
if (a != b) {
result = a + b;
}
System.out.println(result);
return result;
}
38. Resultado
38/68
2
public int sum(int a, int b) {
int result= 0;
if (a != b) {
result = a + b;
}
System.out.println(result);
return result;
}
+1
+1
39. Exercício de Contagem (Ano Bissexto – Exemplo 3)
39/68
?
public boolean isBissexto(int ano){
if (ano % 400 == 0){
return true;
}
if (ano % 4 == 0 && ano % 100 != 0){
return true;
}
return false;
}
41. Resultado para Implementação sem “Return”
41/68
4
public boolean isBissexto(int ano){
boolean result = false;
if (ano % 400 == 0){
result = true;
}
if (ano % 4 == 0 && ano % 100 != 0){
result = true;
}
return result;
}
+1
+1
+1
+1
42. Resultado para Implementação mais Enxuta
42/68
3
public boolean isBissexto(int ano){
return ano % 400 == 0 || (ano % 4 == 0 && ano % 100 != 0);
}
+1+1
+1
assertTrue(obj.isBissexto(1600));
assertFalse(obj.isBissexto(2001));
assertTrue(obj.isBissexto(2004));
assertFalse(obj.isBissexto(1900));
43. Modo de Uso
43/68
O número exato não é a coisa mais importante!
Complexidade Ciclomática serve para acompanhamento
da evolução do software durante a construção
Premissa: Ferramenta de Análise Estática de Código
46. Parte 2 – Aplicação
46/68
Padrão de Qualidade
Importância
Utilidade
47. Padrão de Qualidade sobre Complexidade Ciclomática (CC)
47/68
10
Quanto menor, melhor
Mais fácil de testar e manter
Valor limite do Sonar: até 10
(Acima de 10 gera uma violação)
Relação direta com casos de testes
Quanto mais alta, mais complexo
Mais responsabilidades por método
> 50 = quase impossível de manter
(até para o desenvolvedor original)
Alta CC sugere pouco OO
(+ procedural)
48. Padrão de Qualidade sobre Complexidade Ciclomática (CC)
48/68
<10!
10 é o limite, não o ideal (é alto)
CC alta é um ponto quente
- Maior necessidade de testes
- Maior probabilidade de erro
- Maior necessidade de
manutenção
A média deve tender a 1
49. Padrão de Qualidade sobre Complexidade Ciclomática (CC)
49/68
10
Segundo Thomas J. McCabe (1976):
01-10 Método Simples. Baixo Risco
11-20 Método razoavelmente complexo. Risco
moderado.
21-50 Método muito complexo. Risco elevado.
51-N Método de altíssimo risco e instável
50. Padrão de Qualidade sobre Complexidade Ciclomática (CC)
50/68
5
Segundo Steve McConnell (Code Complete 1993) :
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.
51. Padrão de Qualidade sobre Complexidade Ciclomática (CC)
51/68
Segundo SonarSource (sonarsource.com):
Gerencie como “Vazamento de Água” (Water Leak)
“Fix Issues Before They Exist” (Resolva antes que exista)
Utilização do Plugin SonarLint
A melhor abordagem sobre
complexidade é mantê-la baixa
57. Quebra do Ciclo Vicioso
57/68
Prazo
Curto
KEEP
CALM
AND ...
Dívida
Técnica
Perda de
Qualidade
CC
Baixa
Teste de
Unidade
Confiança
Fluidez
Refatoração
58. Parte 3 – Prática
58/68
Refatoração e Redução
da Complexidade Ciclomática
59. Refatoração
59/68
Refatoração é uma técnica para reestruturar um
código existente, alterando seu estado interno
sem alterar seu comportamento externo.
Ela consiste em uma série de pequenas transformações com a preservação do
comportamento original. Cada transformação faz pouco, mas uma sequência de
transformações pode produzir uma reestruturação significativa. Uma vez que cada
refatoração é pequena, é menos provável que dê errado.
60. Vazamento de Água: Refatoração como Atividade Corriqueira
60/68
ConstruçãoConstrução
Análise EstáticaAnálise Estática
CC Alta?CC Alta?Não
RefatoraçãoRefatoração
Sim
Tratar?Tratar? Não
Dívida TécnicaDívida Técnica
Sim
61. Exercício de Refatoração
61/68
Necessidade: A partir de uma classe qualquer retorne a quantidade de
constantes (final static) inteiras* presentes nessa classe incluindo as
constantes herdadas das superclasses.
* Inteiros: Integer, int, Long, long, Short, short, Byte, byte e BigInteger
** Se a classe é nula, Enum ou abstrata deve ser retornado “-1”.
62. Solução Inicial
62/68
O que o método faz?
Como reduzir a
Complexidade Ciclomática
de 21 para menos de 10?
Porque deu 21?
Está muito procedural?
Pode ser desmembrado?
63. Análise do Método
63/68
2
1
3
O método faz quatro coisas:
1 – Pré-condição
2 – Obtém os campos
3 – Classifica Int Const
4 – Contabiliza
Decomposição funcional
Possivelmente recursivo
4
64. Primeira Refatoração
64/68
1
1
2
2
3
3
4
- Criação de 3 métodos
- CC do método “countIntConst”
desceu de 21 para 5
- Soma das CC dá 23 (4
métodos)
- método “isIntConst” deu 11
Apesar de totalizar 23, os
métodos ficaram muito mais
testáveis e com
responsabilidades únicas
65. Refatoração Final
65/68
- Troca de “Ous” por um conjunto
com os tipos inteiros
- Soma das CC dá 14 (4 métodos)
- “isIntConst” desceu de 11 para 3
3
3
5
3
66. Resultado da Refatoração
66/68
Um método (CC = 21) deu lugar a quatro métodos (CC = 5,3,3,3)
Os métodos extraídos fazem só uma coisa e são facilmente reusáveis
O esforço de construção e teste de unidade é mais ou menos o mesmo,
mas a manutenção será muito mais simples no futuro
O método principal ficou com a leitura mais fácil, pois ele orquestra a
funcionalidade invocando outras três, adicionando semântica
67. Dicas Finais
67/68
Assim como no exemplo, Complexidade Ciclomática alta é um indício de
uma provável necessidade e/ou oportunidade de refatoração
Complexidade Ciclomática alta é indício de baixa ou má cobertura de
testes de unidade. É difícil testar e cobrir um código complexo
Uso frequente de análise estática com refatoração ajuda a manter a
complexidade baixa. Trate o vazamento de água continuamente
68. Muito Obrigado!
Dúvidas?
Douglas Siviotti de Alcantara
douglas.siviotti@gmail.com
github.com/siviotti
www.artesoftware.com.br
Código fonte usado nesta apresentação:
https://github.com/siviotti/ops/tree/master/src/main/java/siviotti/ops/example/cc