Recentemente, deparei-me com a Complexidade Ciclomática e gostaria de tentar entendê-la melhor.
Quais são alguns exemplos práticos de codificação dos diferentes fatores envolvidos no cálculo da complexidade? Especificamente, para a equação da Wikipedia de M = E − N + 2P
, quero entender melhor o que cada um dos seguintes termos significa:
- E = o número de arestas do gráfico
- N = o número de nós do gráfico
- P = o número de componentes conectados
Suspeito que E ou N possa ser o número de pontos de decisão (se, senão se, for foreach etc.) em um bloco de código, mas não tenho muita certeza de qual é qual ou o que o outro significa. Também estou supondo que P se refira a chamadas de função e instanciações de classe, mas não há uma definição clara, dado que eu possa ver. Se alguém pudesse esclarecer um pouco mais alguns exemplos claros de código de cada um, isso ajudaria.
Como acompanhamento, a Complexidade Ciclomática se correlaciona diretamente com o número de testes de unidade necessários para 100% de cobertura do caminho ? Como exemplo, um método com uma complexidade de 4 indica que são necessários 4 testes de unidade para cobrir esse método?
Finalmente, expressões regulares afetam a complexidade ciclomática e, se sim, como?
fonte
Respostas:
Em relação à fórmula: nós representam estados, arestas representam alterações de estado. Em todos os programas, as instruções trazem alterações no estado do programa. Cada instrução consecutiva é representada por uma borda, e o estado do programa após (ou antes de ...) a execução da instrução é o nó.
Se você tem uma instrução de ramificação (
if
por exemplo) - então você tem dois nós saindo, porque o estado pode mudar de duas maneiras.Outra maneira de calcular o número de complexidade ciclomática (CCN) é calcular quantas "regiões" no gráfico de execução você possui (onde "região independente" é um círculo que não contém outros círculos). Nesse caso, o CCN será o número de regiões independentes mais 1 (que seria exatamente o mesmo número que a fórmula anterior fornece).
O CCN é usado para cobertura de ramificação ou cobertura de caminho , que é a mesma. O CCN é igual ao número de caminhos de ramificação diferentes teoricamente possíveis em um único aplicativo encadeado (que pode incluir ramificações como "
if x < 2 and x > 5 then
", mas que deve ser capturado por um bom compilador como um código inacessível). É necessário ter pelo menos esse número de casos de teste diferentes (pode ser mais, pois alguns casos de teste podem estar repetindo caminhos cobertos pelos anteriores, mas não menos assumindo que cada caso cobre um único caminho). Se você não pode cobrir um caminho com qualquer caso de teste possível - encontrou um código inacessível (embora precise provar para si mesmo porque é inacessível, provavelmente alguns aninhados àx < 2 and x > 5
espreita em algum lugar).Quanto às expressões regulares - é claro que elas afetam, como qualquer outro pedaço de código. No entanto, o CCN da construção regex provavelmente é alto demais para ser coberto em um único teste de unidade, e você pode assumir que o mecanismo regex foi testado e ignorar o potencial de ramificação das expressões para suas necessidades de teste (a menos que você esteja testando seu mecanismo regex, é claro).
fonte
Algumas observações sobre isso que eu ociosamente escrevo ...
Especificamente, para a equação da Wikipedia de M = E - N + 2P
Essa equação está muito errada .
Por alguma razão, McCabe realmente o usa em seu artigo original ("Uma Medida de Complexidade", IEEE Transactions on Software Engineering, Vo .. SE-2, No.4, dezembro de 1976), mas sem justificá-lo e depois de realmente citar o correto fórmula na primeira página, que é
(Aqui, os elementos da fórmula foram rotulados novamente)
Especificamente, McCabe faz referência ao livro C.Berge, Graphs and Hypergraphs (abreviado abaixo para G&HG). Diretamente desse livro :
Definição (página 27 na parte inferior da G&HG):
Teorema (página 29 no topo de G&HG) (não usado por McCabe):
Um ciclo é uma sequência de vértices iniciando e terminando no mesmo vértice, com cada dois vértices consecutivos na sequência adjacentes um ao outro no gráfico.
Intuitivamente, um conjunto de ciclos é independente se nenhum dos ciclos puder ser construído dos outros, sobrepondo as caminhadas.
Teorema (página 29 no meio de G&HG) (usado por McCabe):
Um circuito é um ciclo sem repetições de vértices e arestas permitidas.
Diz-se que um gráfico direcionado está fortemente conectado se todos os vértices puderem ser alcançados a partir de qualquer outro vértice passando pelas bordas na direção designada.
Observe que aqui passamos de gráficos não direcionados para gráficos fortemente conectados (que são direcionados ... Berge não deixa isso totalmente claro)
McCabe agora aplica o teorema acima para derivar uma maneira simples de calcular um "Número de complexidade ciclomática de McCabe" (CCN) da seguinte forma:
Dado um gráfico direcionado representando a "topologia de salto" de um procedimento (o gráfico de fluxo de instruções), com um vértice designado representando o ponto de entrada exclusivo e um vértice designado representando o ponto de saída exclusivo (o vértice do ponto de saída pode precisar ser "construído" adicionando-o no caso de retornos múltiplos), crie um gráfico fortemente conectado adicionando uma aresta direcionada do vértice do ponto de saída ao vértice do ponto de entrada, tornando assim o vértice do ponto de entrada acessível a partir de qualquer outro vértice.
McCabe agora postula (de maneira bastante confusa) que o número ciclomático do gráfico de fluxo de instruções modificado "está de acordo com nossa noção intuitiva de 'número mínimo de caminhos'" e, portanto, devemos usar esse número como medida de complexidade.
Legal, então:
O número de complexidade ciclomática do gráfico de fluxo de instruções modificado pode ser determinado contando os "menores" circuitos no gráfico não direcionado. Isso não é particularmente difícil de ser feito pelo homem ou pela máquina, mas a aplicação do teorema acima nos fornece uma maneira ainda mais fácil de determiná-lo:
v (G) = e - v + p
se alguém desconsiderar a direcionalidade das arestas.
Em todos os casos, consideramos apenas um único procedimento, portanto, há apenas um componente conectado no gráfico inteiro e, portanto:
v (G) = e - v + 1.
Caso se considere o gráfico original sem a borda "saída a entrada" adicionada , obtém-se simplesmente:
ṽ (G) = ẽ - v + 2
como ẽ = e - 1
Vamos ilustrar usando o exemplo de McCabe de seu artigo:
Aqui temos:
A fórmula para o número ciclomático diz:
v (G) = e - v + p
que produz 5 = 10 - 6 + 1 e, portanto, correto!
O "número de complexidade ciclomática de McCabe", conforme indicado em seu artigo, é
5 = 9 - 6 + 2 (nenhuma explicação adicional é fornecida no artigo sobre como)
que está correto (resulta em v (G)), mas pelas razões erradas, ou seja, usamos:
ṽ (G) = ẽ - v + 2
e assim ṽ (G) = v (G) ... ufa!
Mas essa medida é boa?
Em duas palavras: não muito
for
loops ewhile
loops são tratados da mesma maneira (observe que em C, pode-se abusar dafor
expressão de umawhile
de outra maneira; aqui estou falando sobre ofor (int i=0;i<const_val;i++)
loop estrito ). Sabemos pela ciência da computação teórica que essas duas construções produzem poderes computacionais totalmente diferentes: funções primitivas-recursivas se você estiver equipado apenasfor
, funções μ-recursivas parciais se você estiver equipadowhile
.fonte
Sim basicamente. Também é uma boa idéia usar a complexidade ciclomática como um indicador de quando refatorar. Na minha experiência, a testabilidade e a reutilização aumentam muito para um CC mais baixo (embora você deva ser prático - não refatorar demais, e alguns métodos terão um CC alto devido à sua natureza - nem sempre faz sentido tentar forçá-lo mais baixo).
Sim, se você quiser ser exato, embora a maioria das ferramentas de análise de código não pareça levá-las em consideração dessa maneira. Expressões regulares são apenas máquinas de estado finito, então acho que o CC delas pode ser calculado a partir do gráfico FSM, mas seria um número bastante grande.
fonte