Comprensione della complessità ciclomatica


11

Di recente mi sono imbattuto nella complessità ciclomatica e vorrei provare a capirla meglio.

Quali sono alcuni esempi pratici di codifica dei diversi fattori che vanno nel calcolo della complessità? In particolare, per l'equazione di Wikipedia di M = E − N + 2P, voglio capire meglio cosa significa ciascuno dei seguenti termini:

  • E = il numero di spigoli del grafico
  • N = il numero di nodi del grafico
  • P = il numero di componenti collegati

Sospetto che E o N possa essere il numero di punti decisionali (se, altrimenti se, per, foreach, ecc.) In un blocco di codice, ma non sono del tutto sicuro di quale sia o cosa significhi l'altro. Suppongo anche che P si riferisca a chiamate di funzione e istanze di classe, ma non c'è una definizione chiara dato che posso vedere. Se qualcuno potesse fare un po 'più di luce con alcuni chiari esempi di codice di ciascuno, sarebbe d'aiuto.

Come follow-up, la complessità ciclomatica è direttamente correlata al numero di test unitari necessari per una copertura del percorso del 100% ? Ad esempio, un metodo con una complessità di 4 indica che sono necessari 4 test unitari per coprire quel metodo?

Infine, le espressioni regolari influenzano la complessità ciclomatica e, in caso affermativo, come?


Ho scoperto che puoi ottenere il documento originale di McCabe da Wikipedia e Google Books produrrà il libro che McCabe ha usato per il suo documento originale. È interessante notare che scoprirai che McCabe ha usato il teorema originale in modo errato (e spiega anche in modo confuso poiché dovrebbe iniziare con un grafico non orientato e non è necessario renderlo fortemente connesso in primo luogo) ma i numeri escono comunque correttamente ( la formula corretta sarebbe M = E + 1-N + P, ma poiché P è sempre 1, si adatta ...) Si pensa che la moderna "gestione delle eccezioni" getti una chiave inglese nelle opere di quella metrica.
David Tonhofer,

... e che dire delle chiamate ricorsive (possibilmente passando attraverso una catena di funzioni). Si fondono i grafici delle funzioni? Che ne dici di cortocircuitare gli operatori booleani come "&&". Operatori sorvegliati come "ref? .X" che restituiscono null se ref è null? Oh bene, è solo un'altra metrica. Ma c'è del lavoro per un piccolo progetto universitario qui.
David Tonhofer,

Risposte:


8

Per quanto riguarda la formula: i nodi rappresentano gli stati, i bordi rappresentano i cambiamenti di stato. In ogni programma, le istruzioni portano cambiamenti nello stato del programma. Ogni istruzione consecutiva è rappresentata da un bordo e lo stato del programma dopo (o prima ...) l'esecuzione dell'istruzione è il nodo.

Se hai un'istruzione di diramazione ( ifad esempio), allora hai due nodi in uscita, perché lo stato può cambiare in due modi.

Un altro modo per calcolare il numero di complessità ciclomatica (CCN) è calcolare quante "regioni" nel grafico di esecuzione sono presenti (dove "regione indipendente" è un cerchio che non contiene altri cerchi). In questo caso il CCN sarà il numero di regioni indipendenti più 1 (che sarebbe esattamente lo stesso numero della formula precedente).

Il CCN viene utilizzato per la copertura delle diramazioni o copertura del percorso , che è lo stesso. Il CCN equivale al numero di diversi percorsi di diramazione teoricamente possibili in una singola applicazione filettata (che può includere rami come " if x < 2 and x > 5 then", ma che dovrebbe essere intercettato da un buon compilatore come codice irraggiungibile). Devi avere almeno quel numero di casi di test diversi (può essere maggiore poiché alcuni casi di test potrebbero ripetere percorsi coperti da quelli precedenti, ma non meno supponendo che ciascun caso copra un singolo percorso). Se non riesci a coprire un percorso con nessun possibile caso di test, hai trovato un codice irraggiungibile (anche se dovrai effettivamente dimostrare a te stesso perché è irraggiungibile, probabilmente qualche annidato in x < 2 and x > 5agguato da qualche parte).

Per quanto riguarda le espressioni regolari - ovviamente influenzano, come qualsiasi altra parte di codice. Tuttavia, il CCN del costrutto regex è probabilmente troppo alto per essere coperto in un singolo test unitario e puoi supporre che il motore regex sia stato testato e ignorare il potenziale di ramificazione delle espressioni per le tue esigenze di test (a meno che tu non stia testando il tuo motore regex, ovviamente).


2
+1: in realtà, devi fidarti che il motore regex è stato testato. Se non ci si fida, ottenere uno che si fa la fiducia.
S.Lott

"Il CCN equivale al numero di diversi percorsi di esecuzione possibili in una singola applicazione con thread" Questo è sbagliato in quanto il CCN si basa solo sulla topologia del codice e non sul suo significato . Una buona percentuale di questi percorsi può essere impossibile da esercitare in quanto richiedono uno stato di input che non può essere impostato (alcuni x sono 5 e anche meno di 2 ad esempio). Francamente, penso che usare il CCN per decidere sui casi di test da eseguire sia perverso. CCN è un numero per dire allo sviluppatore "potresti essere andato in mare qui, ti preghiamo di considerare il refactoring". E anche allora, potrebbero esserci buone ragioni per un alto CCN.
David Tonhofer,

1
@David ha aggiunto una frase per risolverlo. CCN è una copertura di succursale e non ci sono mai buone ragioni per un CCN elevato a un livello inferiore (in genere suggerisco di applicare per singola funzione).
Littleadv,

La copertura delle filiali e la copertura del percorso non sono le stesse. La copertura delle filiali mira a coprire tutte le filiali mentre la copertura del percorso mira a coprire tutte le combinazioni di filiali.
mouviciel,

13

Alcune osservazioni su questo che scrivo pigramente ...

In particolare, per l'equazione di Wikipedia di M = E - N + 2P

Quell'equazione è molto sbagliata .

Per qualche ragione, McCabe lo utilizza davvero nel suo documento originale ("A Complexity Measure", IEEE Transactions on Software Engineering, Vo .. SE-2, No.4, December 1976), ma senza giustificarlo e dopo aver effettivamente citato il corretto formula sulla prima pagina, che è

v (G) = e - v + p

(Qui, gli elementi della formula sono stati rietichettati)

In particolare, McCabe fa riferimento al libro C.Berge, Graphs and Hypergraphs (abbreviato di seguito in G&HG). Direttamente da quel libro :

Definizione (pagina 27 in fondo a G&HG):

Il numero ciclomatico v (G) di un grafico (non orientato) G (che può avere diversi componenti disconnessi) è definito come:

v (G) = e - v + p

dove e = numero di spigoli, v = numero di vertici, p = numero di componenti collegati

Teorema (pagina 29 in cima a G&HG) (non usato da McCabe):

Il numero ciclomatico v (G) di un grafico G è uguale al numero massimo di cicli indipendenti

Un ciclo è una sequenza di vertici che iniziano e finiscono con lo stesso vertice, con ogni due vertici consecutivi nella sequenza adiacenti l'uno all'altro nel grafico.

Intuitivamente, una serie di cicli è indipendente se nessuno dei cicli può essere costruito dagli altri sovrapponendo le camminate.

Teorema (pagina 29 al centro di G&HG) (usato da McCabe):

In un grafico G fortemente connesso, il numero ciclomatico è uguale al numero massimo di circuiti linearmente indipendenti.

Un circuito è un ciclo senza ripetizioni di vertici e bordi consentiti.

Si dice che un grafico diretto è fortemente connesso se ogni vertice è raggiungibile da ogni altro vertice passando attraverso i bordi nella direzione designata.

Si noti che qui si è passati da grafi non orientati a grafici fortemente connesse (che sono diretti ... Berge non rende questo tutto chiaro)

McCabe ora applica il suddetto teorema per derivare un modo semplice per calcolare un "McCabe Cyclomatic Complexity Number" (CCN) in questo modo:

Dato un grafico diretto che rappresenta la "topologia di salto" di una procedura (il diagramma di flusso dell'istruzione), con un vertice designato che rappresenta il punto di ingresso univoco e un vertice designato che rappresenta il punto di uscita univoco (potrebbe essere necessario "costruire" il vertice del punto di uscita aggiungendolo in caso di più ritorni), creare un grafico fortemente connesso aggiungendo un bordo diretto dal vertice del punto di uscita al vertice del punto di entrata, rendendo così il vertice del punto di entrata raggiungibile da qualsiasi altro vertice.

McCabe ora ipotizza (piuttosto confondentemente direi) che il numero ciclomatico del diagramma di flusso delle istruzioni modificato "sia conforme alla nostra nozione intuitiva di" numero minimo di percorsi "", e quindi useremo quel numero come misura di complessità.

Bene, quindi:

Il numero di complessità ciclomatica del diagramma di flusso delle istruzioni modificato può essere determinato contando i circuiti "più piccoli" nel grafico non diretto. Questo non è particolarmente difficile da fare da parte dell'uomo o della macchina, ma l'applicazione del teorema sopra ci dà un modo ancora più semplice per determinarlo:

v (G) = e - v + p

se si ignora la direzionalità dei bordi.

In tutti i casi, consideriamo solo una singola procedura, quindi c'è un solo componente collegato nell'intero grafico e quindi:

v (G) = e - v + 1.

Nel caso in cui si consideri il grafico originale senza il bordo "exit-to-entry" aggiunto , si ottiene semplicemente:

ṽ (G) = ẽ - v + 2

come ẽ = e - 1

Illustriamo usando l'esempio di McCabe dal suo articolo:

L'esempio di McCabe

Qui abbiamo:

  • e = 10
  • v = 6
  • p = 1 (un componente)
  • v (G) = 5 (contiamo chiaramente 5 cicli)

La formula per il numero ciclomatico dice:

v (G) = e - v + p

che produce 5 = 10 - 6 + 1 e quindi corretto!

Il "numero di complessità ciclomatica di McCabe" riportato nel suo documento è

5 = 9 - 6 + 2 (non sono fornite ulteriori spiegazioni nel documento su come)

che sembra essere corretto (produce v (G)) ma per ragioni sbagliate, cioè usiamo:

ṽ (G) = ẽ - v + 2

e quindi ṽ (G) = v (G) ... eh!

Ma questa misura è buona?

In due parole: non molto

  • Non è del tutto chiaro come stabilire il "diagramma di flusso delle istruzioni" di una procedura, specialmente se la gestione delle eccezioni e la ricorsione entrano nell'immagine. Si noti che McCabe ha applicato la sua idea al codice scritto in FORTRAN 66 , una lingua senza ricorsione, senza eccezioni e una struttura di esecuzione semplice.
  • Il fatto che una procedura con una decisione e una procedura con un ciclo producano lo stesso CCN non è un buon segno.

inserisci qui la descrizione dell'immagine


1
@JayElston Buona cattura. Anzi, lo faccio. Fisso!
David Tonhofer,

1
Grande +1 per il collegamento al documento originale. Molti dei documenti scritti all'epoca sono abbastanza leggibili per qualsiasi programmatore di medio livello e dovrebbero essere letti.
Daniel T.

1

Come follow-up, la complessità ciclomatica è direttamente correlata al numero di test unitari necessari per una copertura del percorso del 100%?

Sì, in pratica. È anche una buona idea fare uso della complessità ciclomatica come indicatore di quando refactoring. Nella mia esperienza, la testabilità e la riusabilità aumentano notevolmente per CC inferiori (anche se dovresti essere pratico - non sovra-refactoring e alcuni metodi avranno CC elevati a causa della loro natura - non ha sempre senso cercare di forzarlo inferiore).

Infine, le espressioni regolari influenzano la complessità ciclomatica e, in caso affermativo, come?

Sì, se vuoi essere esatto, anche se la maggior parte degli strumenti di analisi del codice non sembrano prenderli in considerazione in questo modo. Le espressioni regolari sono solo macchine a stati finiti, quindi immagino che il loro CC possa essere calcolato dal grafico FSM, ma sarebbe un numero piuttosto grande.


+1 - Suppongo che il calcolo del CC per RegExes non sia un compito divertente.
VirtuosiMedia,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.