Ce n’est pas un code secret est une chose compliquée à écrire, à déboguer et à maintenir, ce qui est nécessaire pour une qualité logicielle élevée. De plus, la complexité élevée du code entraîne un niveau plus élevé de défauts de code, ce qui rend le code plus coûteux à maintenir.
Ainsi, en réduisant la complexité du code, nous pouvons réduire le nombre de bogues et de défauts, ainsi que son coût à vie. Qu’est-ce qu’un code complexe exactement ? Comment pouvons-nous évaluer objectivement la complexité d’un morceau de code, qu’il s’agisse d’une base de code entière ou d’une petite fonction ?
Dans cet article, je vais parcourir trois métriques de complexité pour évaluer la complexité du code. Ce sont:
- Complexité cyclomatique
- Complexité des instructions de commutation et des conditions logiques
- Compétence du développeur
Je vais également passer en revue certains des avantages de l’évaluation et de la compréhension de la complexité du code.
- Complexité cyclomatique
- Exemple Un
- Exemple Deux
- Exemple Trois
- Complexité de l’instruction Switch et de la condition logique
- Niveau de compétence Développeur logiciel
- Les avantages de la mesure de la complexité logicielle
- Meilleurs tests
- Risque réduit
- Coûts réduits
- Une plus grande prévisibilité
- Aide les développeurs à apprendre
- C’est un enveloppement
Complexité cyclomatique
En 1976, Thomas McCabe Snr a proposé une métrique pour calculer la complexité du code, appelée Complexité cyclomatique. Il est défini comme:
Mesure quantitative du nombre de chemins linéairement indépendants à travers le code source d’un programmecomp calculée à l’aide du graphique de flux de contrôle du programme.
Si vous n’êtes pas familier avec un graphique de flux de contrôle:
Il s’agit d’une représentation, en notation graphique, de tous les chemins qui pourraient être parcourus par un programme lors de son exécution.
Dit plus simplement, moins il y a de chemins à travers un morceau de code, et moins ces chemins sont complexes, plus la complexité cyclomatique est faible. En conséquence, le code est moins compliqué. Pour démontrer la métrique, utilisons trois exemples de code Go, quelque peu arbitraires.
Exemple Un
func main() { fmt.Println("1 + 1 =", 1+1)}
Comme il n’y a qu’un seul chemin à travers la fonction, elle a un score de complexité cyclomatique de 1, que nous pouvons trouver en exécutant gocyclo dessus.
Exemple Deux
func main() { year, month, day := time.Now().Date() if month == time.November && day == 10 && year == 2018 { fmt.Println("Happy Go day!") } else { fmt.Println("The current month is", month) }}
Dans cet exemple, nous récupérons l’année, le mois et le jour en cours. Avec ces informations, nous vérifions ensuite si la date actuelle est le 10 novembre 2018 avec une condition if / else.
Si c’est le cas, le code affiche « Bonne journée! » à la console. Si ce n’est pas le cas, il affiche « Le mois en cours est » et le nom du mois en cours. L’exemple de code est rendu plus compliqué car si la condition est composée de trois sous-conditions. Étant donné que, il a un score de complexité plus élevé de 4.
Exemple Trois
func main() { _, month, _ := time.Now().Date() switch month { case time.January: fmt.Println("The current month is January.") case time.February: fmt.Println("The current month is February.") case time.March: fmt.Println("The current month is March.") case time.April: fmt.Println("The current month is April.") case time.May: fmt.Println("The current month is May.") default: fmt.Println("The current month is unknown.") }}
Dans cet exemple, nous imprimons le mois en cours, basé sur la valeur de month
, récupérée de l’appel à time.Now().Date()
. Il y a sept chemins à travers la fonction, un pour chacune des instructions case et un pour la valeur par défaut.
En conséquence, sa complexité cyclomatique est de 7. Si nous avions comptabilisé tous les mois de l’année, avec un défaut, cependant, son score serait de quatorze. Cela se produit parce que Gocyclo utilise les règles de calcul suivantes :
1 est la complexité de base d’une fonction
+1 pour chaque cas ‘if’, ‘for’, », ‘&&’ ou ‘||’
En utilisant ces trois exemples, nous pouvons voir qu’en ayant une métrique standard pour calculer la complexité du code, nous pouvons rapidement évaluer la complexité d’un morceau de code.
Nous pouvons également voir à quel point différentes sections de code complexes sont comparées les unes aux autres. Cependant, la complexité cyclomatique ne suffit pas à elle seule.
Complexité de l’instruction Switch et de la condition logique
L’évaluateur suivant de la complexité du code est l’instruction switch et la complexité de la condition logique. Dans l’exemple de code ci-dessous, j’ai pris le deuxième exemple Go et divisé la condition compound if en trois conditions imbriquées; une pour chacune des conditions d’origine.
func main() { year, month, day := time.Now().Date() output := fmt.Sprintf("The current month is %s", month) if month == time.November { if day == 13 { if year == 2018 { output = fmt.Sprintf("Happy Go day!") } } } fmt.Println(output)}
Qui est plus facile à comprendre (ou moins compliqué), l’original, ou celui-ci? Allons maintenant nous appuyer sur cela, en examinant les trois questions suivantes.
- Et si nous avions, comme nous le faisons ci-dessus, plusieurs conditions if et que chacune était assez complexe?
- Et si nous avions plusieurs conditions if et que le code dans le corps de chacune était assez complexe?
- Le code serait-il plus facile ou plus difficile à comprendre?
Il est juste de dire que plus le nombre de conditions imbriquées est élevé et plus le niveau de complexité de ces conditions est élevé, plus la complexité du code est élevée.
Niveau de compétence Développeur logiciel
Qu’en est-il du niveau de compétence du développeur? Jetez un œil à la version C du deuxième exemple de Go ci-dessous.
#include <stdio.h>#include <time.h>#include <string.h>int main(){ time_t t = time(NULL); struct tm tm = *localtime(&t); const char * months = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; if (tm.tm_year == 2018 && strncmp(months, "November", strlen(months)) == 0 && tm.tm_mday == 10) { printf("Happy C Day!.\n"); } else { printf("The current month is %s.\n", months); }}
Techniquement, il fait ce que font les autres exemples. Cependant, il faut plus de code pour atteindre le même résultat. Pour être juste, si j’avais une plus grande familiarité avec C, le code pourrait ne pas être plus long que l’exemple Go.
Cependant, disons que c’est le minimum requis pour atteindre le même résultat. Si vous comparez les deux, étant donné la nature plus verbeuse de la syntaxe de C par rapport à Go, c’est plus difficile à comprendre.
De plus, si vous n’aviez aucune expérience préalable avec C, malgré un score de complexité cyclomatique relativement similaire, quelle serait votre perception?
Considérez-vous que le code est moins ou plus compliqué? C’est donc un autre facteur essentiel pour comprendre la complexité du code.
Les avantages de la mesure de la complexité logicielle
Il y a quatre avantages principaux de la mesure de la complexité du code, plus un supplémentaire.
Meilleurs tests
En sachant combien de chemins indépendants il y a à travers un morceau de code, nous savons combien de chemins il y a à tester.
Au fait, je ne plaide pas pour une couverture de code à 100% — c’est souvent une mesure logicielle dénuée de sens. Cependant, je plaide toujours pour un niveau de couverture du code aussi élevé que possible et pratique.
Ainsi, en sachant combien de chemins de code il y a, nous pouvons savoir combien de chemins nous devons tester. Par conséquent, vous avez une mesure du nombre de tests requis, au minimum, pour vous assurer que le code est couvert.
Risque réduit
Comme le dit le vieil adage:
Il est plus difficile de lire du code que de l’écrire.
Quoi de plus:
- Le code est lu beaucoup plus qu’il n’est écrit
- Un bon développeur de logiciels ne devrait jamais être évalué par les lignes de code qu’il a écrites (ou modifiées), mais par la qualité du code qu’il a maintenu.
Étant donné qu’en réduisant la complexité du code, vous réduisez le risque d’introduction de défauts, qu’ils soient petits ou grands, légèrement embarrassants ou induisant une faillite.
Coûts réduits
Lorsque le risque de défauts potentiels est réduit, il y a moins de défauts à trouver — et à éliminer. En conséquence, le coût de maintenance diminue également.
Nous avons tous vu et connaissons les coûts associés à la recherche de défauts aux différentes étapes de la vie d’un logiciel, comme illustré dans le tableau ci-dessous.
Il est donc logique que, si nous comprenons la complexité de notre code et quelles sections sont plus compliquées que d’autres, nous soyons bien mieux placés pour réduire cette complexité.
Donc, en réduisant cette complexité, nous réduisons la probabilité d’introduire des défauts. Cela se déroule à toutes les étapes de la vie d’un logiciel.
Une plus grande prévisibilité
En réduisant la complexité logicielle, nous pouvons développer avec une plus grande prévisibilité. Ce que je veux dire par là, c’est que nous sommes mieux en mesure de dire — avec confiance — combien de temps une section de code prend pour se terminer. En sachant cela, nous sommes mieux en mesure de prédire le temps nécessaire à l’expédition d’une version.
Sur la base de ces connaissances, l’entreprise ou l’organisation est mieux à même de fixer ses objectifs et ses attentes, en particulier celles qui dépendent directement dudit logiciel. Lorsque cela se produit, il est plus facile de définir des budgets réalistes, des prévisions, etc.
Aide les développeurs à apprendre
Aider les développeurs à apprendre et à grandir est le dernier avantage de comprendre pourquoi leur code est considéré comme complexe. Les outils que j’ai utilisés pour évaluer la complexité jusqu’à présent ne le font pas.
Ce qu’ils font est de fournir un score de complexité global ou granulaire. Cependant, un outil de complexité de code complet, tel que Codacy, le fait.
Dans la capture d’écran ci-dessus, nous pouvons voir que, sur les six fichiers répertoriés, l’un a une complexité de 30, un score généralement considéré comme assez élevé.
La complexité cyclomatique est un excellent indicateur pour comprendre si la qualité du code se détériore pour un changement donné. La complexité cyclomatique peut être plus difficile à raisonner en la regardant ou en comparant des modules entiers étant donné son échelle infinie et n’étant pas liée à la taille du module. Cependant, quelque chose que vous pourriez trouver utile est de regarder la liste de fichiers de Codacy triée par priorité, ce qui vous aidera à comprendre quels fichiers sont candidats de mauvaise qualité de code, puis par conséquent leurs modules.
C’est un enveloppement
De plus, il s’agit d’une discussion approfondie sur la complexité du code, la façon dont il est évalué, ainsi que les avantages significatifs de le réduire. Bien qu’il y ait plus à comprendre la complexité du code que ce que j’ai couvert ici, nous avons parcouru un long chemin pour la comprendre.
Si c’est la première fois que vous entendez parler du terme ou que vous en apprenez sur l’un des outils, je vous encourage à explorer les articles et outils liés, afin que vous en appreniez davantage. Si vous ne codez pas en Go ou en C, alors Google « outil de complexité du code » plus votre (vos) langue(s) logicielle(s). Vous êtes sûr de trouver de nombreux outils disponibles.
Pour plus de conseils pour améliorer la qualité du code, consultez d’autres articles de blog de Codacy.
Enfin, si vous voulez un outil complet pour évaluer la qualité du code et qui aide vos développeurs à apprendre et à grandir, essayez Codacy.
Codacy est utilisé par des milliers de développeurs pour analyser des milliards de lignes de code chaque jour !
La mise en route est facile – et gratuite! Utilisez simplement votre compte GitHub, Bitbucket ou Google pour vous inscrire.
COMMENCER