Introduction aux règles de qualité du code avec FindBugs et PMD

1. Vue d’ensemble

Dans cet article, nous soulignerons certaines des règles importantes décrites dans des outils d’analyse de code tels que FindBugs, PMD et CheckStyle.

2. Complexité cyclomatique

2.1. Qu’est-ce que la complexité cyclomatique?

La complexité du code est une mesure importante, mais difficile à mesurer. PMD propose un ensemble solide de règles sous Code section Règles de taille , ces règles sont conçues pour détecter les violations concernant la taille et la complexité des méthodes des méthodes. .

CheckStyle est reconnu pour sa capacité d’analyse du code par rapport aux normes de codage et aux règles de formatage. Cependant, il peut également détecter des problèmes lors de la conception de classes/méthodes en calculant une certaine complexité.

L’une des mesures de complexité les plus pertinentes décrites dans les deux outils est la complexité cyclomatique (CC).

La valeur CC peut être calculée en mesurant le nombre de chemins d’exécution indépendants d’un programme.

Par exemple, la méthode suivante donnera une complexité cyclomatique de 3:

public void callInsurance(Vehicle vehicle) {
    if (vehicle.isValid()) {
        if (vehicle instanceof Car) {
            callCarInsurance();
        } else {
            delegateInsurance();
        }
    }
}

CC prend en compte l’imbrication d’instructions conditionnelles et d’expressions booléennes en plusieurs parties.

De manière générale, un code avec une valeur supérieure à 11 en termes de CC est considéré comme très complexe et difficile à tester et à maintenir.

Certaines valeurs courantes utilisées par les outils d’analyse statique sont présentées ci-dessous:

  • 1-4: faible complexité - facile à tester

  • 5-7: complexité modérée - tolérable

  • 8-10: complexité élevée - la refactorisation doit être envisagée pour faciliter

essai ** 11 + très grande complexité - très difficile à tester

Le niveau de complexité affecte également la testabilité du code , plus le CC est élevé, plus il est difficile d’appliquer des tests pertinents .

En fait, la valeur de complexité cyclomatique indique exactement le nombre de tests requis pour obtenir un score de couverture de 100% des branches.

Le graphe de flux associé à la méthode callInsurance () est:

lien:/uploads/flowgraph__CC-1.png%20324w[]

Les chemins d’exécution possibles sont:

  • 0 ⇒ 3

  • 0 ⇒ 1 ⇒ 3

  • 0 ⇒ 2 ⇒ 3

Mathématiquement parlant, CC peut être calculé en utilisant la formule simple suivante:

CC = E - N + 2P
  • E: nombre total d’arêtes

  • N: nombre total de nœuds

  • P: nombre total de points de sortie

2.2. Comment réduire la complexité cyclomatique?

Pour écrire du code sensiblement moins complexe, les développeurs peuvent avoir tendance à utiliser différentes approches, selon la situation:

  • Évitez d’écrire de longues instructions switch en utilisant des modèles de conception,

par exemple. les modèles de construction et de stratégie peuvent être de bons candidats pour faire face avec des problèmes de taille de code et de complexité ** Ecrire des méthodes réutilisables et extensibles en modularisant le code

structurer et mettre en œuvre le https://en.wikipedia.org/wiki/Single responsibility principle Après autre PMD

code size peuvent avoir un impact direct sur CC ** , par exemple. règle de longueur de méthode excessive, trop de champs dans une seule classe, liste de paramètres excessive dans une seule méthode, etc.

Vous pouvez également envisager les principes et modèles suivants concernant la taille et la complexité du code, par exemple. le principe https://en.wikipedia.org/wiki/KISS principle[ KISS ], et https://en.wikipedia.org/wiki/Don’t repeat__yourself[DRY (Don ' t Répétez vous)].

3. Règles de traitement des exceptions

Les défauts liés aux exceptions peuvent être habituels, mais certains d’entre eux sont extrêmement sous-estimés et doivent être corrigés pour éviter les dysfonctionnements critiques du code de production.

PMD et FindBugs proposent tous deux un ensemble limité de règles relatives aux exceptions.

Voici notre sélection de ce qui peut être considéré comme critique dans un programme Java lors du traitement des exceptions.

3.1. Ne pas lancer d’exception dans Finally

Comme vous le savez peut-être déjà, le bloc finally \ {} en Java est généralement utilisé pour fermer des fichiers et libérer des ressources. Son utilisation à d’autres fins peut être considérée comme un odeur de code .

Une routine typique générant des erreurs génère une exception à l’intérieur du bloc finally \ {} :

String content = null;
try {
    String lowerCaseString = content.toLowerCase();
} finally {
    throw new IOException();
}

Cette méthode est supposée lancer une NullPointerException , mais étonnamment, elle lance une IOException , ce qui peut induire en erreur la méthode appelante pour gérer la mauvaise exception.

3.2. Retour dans le finally Block

L’utilisation de l’instruction return dans un bloc finally \ {} peut être déroutante. La raison pour laquelle cette règle est si importante, c’est que chaque fois qu’un code lève une exception, il est rejeté par l’instruction return .

Par exemple, le code suivant s’exécute sans aucune erreur:

String content = null;
try {
    String lowerCaseString = content.toLowerCase();
} finally {
    return;
}

Une exception NullPointerException n’a pas encore été interceptée par l’instruction return du bloc finally

3.3. Échec de la fermeture d’un flux à une exception

La fermeture des flux est l’une des principales raisons pour lesquelles nous utilisons un bloc finally , mais ce n’est pas une tâche anodine, comme cela semble être le cas.

Le code suivant tente de fermer deux flux dans un bloc finally :

OutputStream outStream = null;
OutputStream outStream2 = null;
try {
    outStream = new FileOutputStream("test1.txt");
    outStream2  = new FileOutputStream("test2.txt");
    outStream.write(bytes);
    outStream2.write(bytes);
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        outStream.close();
        outStream2.close();
    } catch (IOException e) {
       //Handling IOException
    }
}

Si l’instruction outStream.close () lève une IOException , la outStream2.close () sera ignorée.

Une solution rapide consisterait à utiliser un bloc try/catch séparé pour fermer le second flux:

finally {
    try {
        outStream.close();
    } catch (IOException e) {
       //Handling IOException
    }
    try {
        outStream2.close();
    } catch (IOException e) {
       //Handling IOException
    }
}

Si vous voulez éviter les blocs try/catch consécutifs, consultez la page Méthode IOUtils.closeQuiety de Apache commons, elle simplifie la gestion des flux sans fermer une exception IOException .

5. Mauvaises pratiques

5.1. La classe définit compareTo () et utilise Object.equals ()

Chaque fois que vous implémentez la méthode compareTo () , n’oubliez pas de faire de même avec la méthode equals () , sinon les résultats renvoyés par ce code risquent de prêter à confusion:

Car car = new Car();
Car car2 = new Car();
if(car.equals(car2)) {
    logger.info("They're equal");
} else {
    logger.info("They're not equal");
}
if(car.compareTo(car2) == 0) {
    logger.info("They're equal");
} else {
    logger.info("They're not equal");
}

Résultat:

They're not equal
They're equal

Pour dissiper les confusions, il est recommandé de vous assurer que Object.equals () n’est jamais appelé lors de l’implémentation de Comparable, , vous devriez essayer de le remplacer par quelque chose comme:

boolean equals(Object o) {
    return compareTo(o) == 0;
}

5.2. Déréférence de pointeur nul possible

NullPointerException (NPE) est considéré comme l’exception la plus rencontrée en programmation Java, et FindBugs se plaint du déréférencement Null PointeD pour éviter de le lancer.

Voici l’exemple le plus élémentaire de lancer un NPE:

Car car = null;
car.doSomething();

Le moyen le plus simple d’éviter les NPE consiste à effectuer une vérification NULL:

Car car = null;
if (car != null) {
    car.doSomething();
}

Les contrôles nuls peuvent éviter les NPE, mais lorsqu’ils sont largement utilisés, ils affectent certainement la lisibilité du code.

Voici donc quelques techniques utilisées pour éviter les NPE sans contrôle de nullité:

  • Évitez le mot clé null pendant le codage _: _ Cette règle est simple,

évitez d’utiliser le mot clé null lors de l’initialisation des variables ou lors du renvoi valeurs Utilisation

Modèle d’objet ** ]

6. Conclusion

Dans cet article, nous avons passé en revue certains des problèmes critiques détectés par les outils d’analyse statique, avec des instructions de base pour traiter correctement les problèmes détectés.

Vous pouvez parcourir l’ensemble complet de règles pour chacune d’entre elles en visitant les liens suivants: