Complexité cyclomatique lors de plusieurs appels de la même méthode

12

Grâce à une question sur Code Review, j'ai eu un petit désaccord (qui est essentiellement une opportunité d'apprendre quelque chose) sur ce qu'est exactement la complexité cyclomatique pour le code ci-dessous.

public static void main(String[] args) {
    try {
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
        thro();
    }
    catch (NullPointerException e) {
    }
}

private static Random random = new Random();

public static void thro() throws NullPointerException {
    if (random.nextBoolean())
        throw new NullPointerException();
    System.out.println("No crash this time");
}

En écrivant ce code dans Eclipse et en utilisant le plugin de métriques Eclipse , cela me dit que la complexité cyclomatique McCabe pour la méthode principale est 2, et pour la throméthode, elle dit 2.

Cependant, quelqu'un d'autre me dit que la complexité d'appeler throplusieurs fois est number of calls * method complexity, et affirme donc que la complexité de la méthode principale est 7 * 2 = 14.

Sommes-nous en train de mesurer des choses différentes? Peut-on avoir raison tous les deux? Ou quelle est la complexité cyclomatique réelle ici?

Simon Forsberg
la source
5
Le CC de la fonction est de deux, car il n'y a que deux chemins à travers. Le CC du programme est plus élevé. Il s'agit d'un coup de couteau complet dans l'obscurité, mais je suppose que le logiciel d'analyse de code prend chaque fonction comme une boîte noire distincte en raison de l'impossibilité de calculer le CC d'une application complexe entière en une seule fois.
Phoshi
@Phoshi Si vous écrivez cela comme une réponse et (si possible) fournissez des liens qui montrent qu'il y a une séparation des deux, je serais heureux d'accepter cette réponse.
Simon Forsberg
Si vous comptez tous les chemins causés par des exceptions possibles dans les mesures CC, Dieu aide le gars qui a posé la question sur la refactorisation d'un code trivial pour obtenir le nombre inférieur à 10.
mattnz

Réponses:

9

Quand j'ai bien compris, la complexité cyclomatique de mainest 8 - c'est le nombre de chemins linéairement indépendants à travers le code. Vous obtenez soit une exception sur l'une des sept lignes, soit aucune, mais jamais plus d'une. Chacun de ces "points d'exception" possibles correspond exactement à un chemin différent dans le code.

Je suppose que lorsque McCabe a inventé cette métrique, il n'avait pas de langages de programmation avec une gestion des exceptions à l'esprit.

Doc Brown
la source
Mais importe-t-il vraiment laquelle des lignes qui lève l'exception?
Simon Forsberg
5
@ SimonAndréForsberg: oui, c'est le cas. Pensez à "thro" ayant un effet secondaire où il incrémente un compteur global quand il est appelé (cela ne changerait pas les chemins possibles à travers le code). Les résultats possibles de ce compteur sont alors de 0 à 7, ce qui prouve que le CC est au moins 8.
Doc Brown
Diriez-vous que le plug-in de métriques que j'utilise signale une valeur incorrecte pour la mainméthode?
Simon Forsberg
@ SimonAndréForsberg: eh bien, je ne connais pas votre plugin de métriques, mais 2 n'est évidemment pas 8.
Doc Brown
Il y a un lien vers le plugin de métriques dans ma question ....
Simon Forsberg
6

Étant «l'autre gars», je vais répondre ici, et être précis sur ce que je dis (ce que je n'étais pas particulièrement précis avec sur d'autres formums).

En utilisant l'exemple de code ci-dessus, je calcule la complexité cyclomatique comme 8, et j'ai des commentaires dans le code pour montrer comment je calcule cela. Pour décrire les chemins, je considérerai une boucle réussie à travers tous les thro()appels comme le chemin de code «principal» (ou «CP = 1»):

public static void main(String[] args) {
  try {
             // This is the 'main' Code Path: CP = 1
    thro();  // this has a branch, can succeed CP=1 or throw CP=2
    thro();  // this has a branch, can succeed CP=1 or throw CP=3
    thro();  // this has a branch, can succeed CP=1 or throw CP=4
    thro();  // this has a branch, can succeed CP=1 or throw CP=5
    thro();  // this has a branch, can succeed CP=1 or throw CP=6
    thro();  // this has a branch, can succeed CP=1 or throw CP=7
    thro();  // this has a branch, can succeed CP=1 or throw CP=8
  }
  catch (NullPointerException e) {
  }
}

Donc, je compte 8 chemins de code dans cette méthode principale, qui, pour moi, est une complexité cyclomatique de 8.

En termes Java, chaque mécanisme pour quitter une fonction compte pour sa complexité, donc, une méthode qui a un état de réussite, et, par exemple, peut contenir jusqu'à 3 exceptions, a 4 chemins de sortie documentés.

La complexité d'une méthode qui appelle une telle fonction est:

CC(method) = 1 + sum (methodCallComplexity - 1)

Je pense que d'autres choses à considérer, c'est que, à mon avis, la catchclause ne contribue pas à la complexité de la méthode, catchest simplement la cible d'une throwsbranche, et donc un bloc de capture qui est la cible de plusieurs throwcomptes s 1 fois pour chacun throw, et pas seulement une fois pour tout.

rolfl
la source
Comptez-vous également les branches possibles pour OutOfMemoryExceptions? Je veux dire pédantiquement, ils peuvent provoquer des branches de code, mais personne ne les compte car ils diluent l'utilité de la métrique.
Telastyn
Non, je ne le suis pas ... et vous avez raison, mais, dans le contexte de cet argument, je ne compte que les exceptions que la méthode est déclarée lever. De plus, si une méthode déclare trois exceptions, mais que le code callinch le fait, catch (Throwable t) {...je pense que peu importe le nombre d'exceptions qu'elle déclare lever.
rolfl