Comment puis-je définir et mesurer la simplicité dans le code?

13

Il y a beaucoup de réponses dans ma question précédente sur la simplicité concernant la lisibilité qui m'ont aidé à voir que ma définition et ma compréhension de la simplicité dans le code étaient, très probablement, incorrectes.

Comment puis-je définir la simplicité dans le code? Quelles mesures et mesures logicielles sont disponibles pour mesurer la simplicité du code?

Richard
la source
2
@MarkTrapp Il existe d'autres façons de discuter de la simplicité du code sans les sujets de génie logiciel empirique, sujets que je connais beaucoup moins. Par exemple, discuter de la simplicité en termes de capacité à écrire des tests automatisés. Mes compétences et mes connaissances me permettent de répondre à cette question du point de vue d'un ingénieur logiciel emprical, tandis que d'autres peuvent répondre sous d'autres angles. L'ajout de cette déclaration à la question limite considérablement le nombre de réponses utiles, ce qui la rend (OMI) trop localisée. Si vous voulez l'ajouter, vous pouvez, mais c'est une bonne question telle quelle.
Thomas Owens
2
@ThomasOwens Les vraies questions ont des réponses , pas des idées ou des opinions. Rétrécir la portée pour que tout le monde interprète comment répondre à la question de la même manière est exactement de quoi parle Stack Exchange. Il peut y avoir plus d'une approche pour résoudre le problème, mais il n'y a qu'un seul problème sans ambiguïté.
Dans son état actuel, il y a très peu de réponses à cette question (ma réponse porte sur le point de vue du génie logiciel empirique, avec les métriques communes - il y en a probablement d'autres). Cela n'a aucun sens d'exclure les réponses fournissant des alternatives valables sous d'autres perspectives, ce que fait le libellé de cette question. Je suis entièrement en désaccord avec ces modifications et la question devrait être ramenée à sa forme originale.
Thomas Owens
@MarkTrapp Le problème est sans ambiguïté: comment déterminer la simplicité du code? Il y a plusieurs bonnes réponses. La mienne consiste à utiliser des techniques de génie logiciel empiriques pour mesurer la complexité. Un autre pourrait être d'écrire des tests automatisés et s'il est difficile d'écrire de bons tests, le code est complexe - une réponse parfaitement valide. Il y en a peut-être d'autres que je ne connais pas. Si vous devez mesurer la complexité / simplicité d'une base de code, la question doit être formulée de manière à permettre à toutes les alternatives d'être présentées afin que le demandeur puisse choisir la meilleure solution pour son cas particulier.
Thomas Owens

Réponses:

16

Les métriques les plus courantes pour mesurer la complexité (ou la simplicité, si vous considérez la simplicité comme l'opposé de la complexité) sont la complexité cyclomatique de McCabe et les métriques de complexité de Halstead .

La complexité cyclomatique mesure le nombre de chemins distincts à travers une unité donnée, généralement une méthode ou une fonction, bien qu'il puisse également être calculé sur une classe. À mesure que le nombre de chemins augmente, il devient plus difficile de se souvenir du flux de données à travers un module donné, qui est lié au concept de mémoire de travail . Une complexité cyclomatique élevée tend à indiquer une difficulté dans la capacité de tester un module - davantage de cas de test sont nécessaires pour couvrir les différents chemins à travers le système. Des études ont également établi un lien entre une complexité cyclomatique élevée et des taux de défauts élevés. Typiquement, une complexité cyclomatique de 10 indique qu'une unité doit être revue et éventuellement refactorisée.

Les mesures de complexité Halstead utilisent les entrées d'opérateurs et d'opérandes totaux et distincts pour calculer le volume, la difficulté et l'effort d'un morceau de code. La difficulté, qui est le (nombre d'opérateurs uniques / 2) * (nombre total d'opérandes / nombre d'opérandes uniques), est liée à la capacité de lire et de comprendre le code pour des tâches telles que l'apprentissage du système ou l'exécution d'une révision de code. Encore une fois, vous pouvez compter cela au niveau du système, au niveau de la classe ou au niveau de la méthode / fonction. Il y a quelques articles sur le calcul de ces mesures ici et ici .

Le simple fait de compter des lignes de code peut également vous donner une idée de la complexité. Plus de lignes de code signifie qu'il y a plus à lire et à comprendre dans un module. J'hésiterais à utiliser cela comme une mesure autonome. Au lieu de cela, je l'utiliserais avec d'autres mesures, telles que le nombre de défauts dans un module donné pour obtenir la densité des défauts. Une densité de défauts élevée peut indiquer des problèmes lors de l'écriture de tests et de la réalisation de révisions de code, qui peuvent ou non être causés par un code complexe.

L'entrée et la sortie sont deux autres mesures liées au flux de données. Comme défini ici , le fan in est la somme des procédures appelées, les paramètres lus et les variables globales lues et fan out est la somme des procédures qui appellent une procédure donnée, les paramètres écrits (exposés à des utilisateurs externes, transmis par référence), et les variables globales écrites dans. Encore une fois, une entrée et une sortie élevées peuvent indiquer un module qui peut être difficile à comprendre.

Dans des paradigmes spécifiques, il peut y avoir d'autres mesures ou paramètres qui sont également utiles. Par exemple, dans le monde orienté objet, la surveillance du couplage (désir faible), de la cohésion (désir élevé) et de la profondeur d'héritage (désir faible) peut être utilisée pour évaluer la simplicité ou la complexité d'un système.

Bien sûr, il est important de réaliser que de nombreuses mesures et métriques ne sont que des indicateurs. Vous devez utiliser votre jugement pour déterminer s'il est nécessaire de refactoriser pour augmenter la simplicité ou si cela ne vaut pas la peine de le faire. Vous pouvez effectuer les mesures, calculer les métriques et en savoir plus sur votre code, mais vous ne voulez pas concevoir votre système en fonction des chiffres. En fin de compte, faites ce qui a du sens.

Thomas Owens
la source
5
Je sais que vous l'avez mentionné, mais il est important de souligner que la complexité cyclomatique n'est vraiment utile qu'au niveau de la fonction / méthode et devient beaucoup plus subjective / inutile aux niveaux supérieurs.
Ryathal
Le problème est que, bien que ces mesures soient bonnes, elles constituent un guide général. Il existe plusieurs cas où de "mauvais" programmes obtiennent de bons résultats, par exemple, ayant une douzaine de fonctions, où une seule fonction avec deux paramètres supplémentaires suffirait, et, inversement, de nombreux "bons" programmes qui sont des solutions bien écrites à un problème complexe peuvent obtenir des résultats mal.
James Anderson
@James, je l'ai explicitement souligné. Toute mesure ou métrique doit être prise dans son contexte comme indicateur que quelque chose doit être examiné. Il faut le jugement d'un ingénieur pour déterminer si une action corrective est nécessaire et quelle est cette action. Cependant, à moins que vous ne collectiez activement des données, il n'existe aucun moyen empirique de connaître les problèmes potentiels.
Thomas Owens
7

Au lieu de regarder un mode formel de définition de la simplicité, je voudrais plutôt définir la simplicité comme un attribut de la qualité de l'écriture de code.

Je ne mets pas une certaine mesure de simplicité mais quand appelez-vous quelque chose de simple ou non.

1. Traversée du code:
est-il facile de naviguer dans le code? Est-il facile de repérer où les fonctions API sont écrites? Est-il facile de comprendre les flux d'appels, par exemple quelles méthodes appellent les autres (et pourquoi) - y a-t-il de bonnes machines à états implémentées ou des algorithmes clairement identifiés?

Lorsque la traversée du code est facile, le code est simple à suivre.

2. Attribution de noms
Bien que d'autres normes de codage aident à rendre le code plus propre - la chose la plus importante est la dénomination des classes / instances-objet / Variables / méthodes. L' utilisation de noms clairs et sans ambiguïté a clairement un grand impact sur la simplicité du code. Lorsqu'il est difficile d'identifier un nom simple, c'est un signe que vous voudrez peut-être repenser l'idée étant cette variable / méthode.

3. Interprétation et références
Est-ce que chacune de vos méthodes a un rôle clair à jouer? Est-ce que chaque variable / attribut est facile à déterminer le rôle qu'ils jouent? Lorsqu'un morceau de code fait quelque chose qui implique des hypothèses ou affecte un ensemble de variables indépendantes, peut devenir un cauchemar de maintenance.

4. Dépendance ou couplage
Ceci est difficile à juger simplement en regardant le code, mais devient très évident si quelqu'un essaie de corriger vos bugs. Lorsque d'autres choses changent dans un autre objet, l'opération ici change-t-elle? Ces changements sont-ils évidents? Avez-vous besoin de changer l'API si souvent pour s'adapter à des choses? Ceux-ci suggèrent que les relations entre les modules ne sont pas simples

5. Entrées utilisateur ou applications
Enfin, dans quelle mesure les entrées utilisateur ou l'application sont-elles acceptées sur l'API / UI? Lorsque plusieurs utilisateurs / applications possibles (à des fins différentes) doivent vous donner - sont-ils évidents? Y a-t-il des états / détails qui ne sont pas liés à l'abstraction la plus élevée, mais qui continuent à faire reculer l'interface?

Une question simple que je poserais généralement est la suivante: si au lieu d'un programme, si j'avais demandé que la même fonction soit exécutée par un être humain, aurais-je rempli ces informations sur un formulaire papier ? Sinon, je ne suis pas assez simple ici.

Je ne dirai pas que cette liste est exhaustive, mais je suppose que le critère est la facilité ou la difficulté d'utilisation et de modification du logiciel. C’est simple.

Dipan Mehta
la source
1
Il a demandé des mesures et des mesures. Celles-ci sont subjectives, donc je ne pense pas qu'elles soient très pertinentes.
psr
@psr Je suis d'accord avec vous. Cela ressortait également très clairement de la réponse de Thomas. Cependant, il a mentionné la simplicité relative à la lisibilité . La réponse de Thomas traite de la complexité de Cyclomatic - cela vous indique à quel point il est complexe de tester le code et non à quel point le code est complexe en termes de lisibilité et peut étendre la maintenabilité . Ce sont deux concepts très différents. C'est précisément pourquoi j'ai écrit cette réponse pour mettre la contradiction flagrante. Malheureusement, à ma connaissance, il n'y a aucune métrique qui se réfère à la simplicité du code en termes de lisibilité.
Dipan Mehta
"utiliser des noms qui sont impossibles à se méprendre" - à mon humble avis, cela vise beaucoup trop haut, à un objectif irréaliste et impossible. Je préférerais ne pas essayer d'être aussi catégorique et simplement dire "utiliser des noms clairs et sans ambiguïté".
Péter Török
@ PéterTörök Je suis d'accord. Je pense qu'habituellement, dans de nombreuses organisations, des règles très clairement définies de conventions de dénomination et encore une certaine confusion au sujet de l' intension de la variable particulière persistent. L'accent a donc été mis sur le fait que la clarté de l'objectif équivaut à la simplicité par opposition à une règle formelle. Peut-être que je suis allé trop loin dans la façon dont j'ai décrit. Merci.
Dipan Mehta
@Dipan La complexité cyclomatique est liée à la lisibilité du code, grâce à la mémoire de travail. Le code avec une complexité cyclomatique élevée (ou même juste une profondeur de bloc élevée) est difficile à conserver dans la mémoire de travail, donc plus difficile à lire directement.
Thomas Owens
0

Je ne connais pas de bonnes mesures existantes pour la simplicité du code (cela ne signifie pas qu'elles n'existent pas - juste que je ne les connais pas). Je pourrais en proposer, peut-être que certains aideront:

  • Simplicité des fonctionnalités linguistiques utilisées: si la langue possède des fonctionnalités qui peuvent être considérées comme "avancées" et "simples", vous pouvez compter le nombre d'occurrences des fonctionnalités avancées. La façon dont vous définissez «avancé» pourrait être un peu plus subjective. Je suppose que certains pourraient dire que c'est aussi comme mesurer l'habileté d'un programme. Un exemple courant: certains pourraient dire que l' ?:opérateur devrait être une fonction «avancée», d'autres pourraient être en désaccord. Je ne sais pas à quel point il serait facile d'écrire un outil qui puisse tester cela.

  • Simplicité des constructions dans le programme: vous pouvez mesurer le nombre de paramètres qu'une fonction acceptera. Si vous avez> n % de toutes les fonctions avec> m paramètres, vous pouvez choisir de le considérer comme non simple, selon la façon dont vous définissez n et m (peut-être n = 3 et m = 6?). Je pense qu'il existe des outils d'analyse statique qui peuvent mesurer cela - je pense que JTest a simplement mesuré des fonctions avec> m paramètres.

  • Vous pouvez essayer de compter le nombre de boucles imbriquées ou de structures de contrôle. Je pense que ce n'est en fait pas une mauvaise métrique et je pense qu'il y a un nom (je ne me souviens pas du haut de ma tête). Encore une fois, je pense qu'il existe des outils (encore une fois, comme JTest) qui peuvent mesurer cela, dans une certaine mesure.

  • Vous pouvez essayer de mesurer la «refactorabilité». Si votre code contient beaucoup de morceaux de code qui pourraient être refactorisés mais ne le sont pas , cela ne pourrait peut-être pas être simple. Je me souviens aussi du temps où j'ai travaillé avec JTest qu'il a essayé de mesurer cela aussi, mais je me souviens que je n'étais pas souvent d'accord avec lui dans ce cas, donc YMMV.

  • Vous pouvez essayer de mesurer le nombre de couches entre différentes parties de votre système. Par exemple: combien de morceaux de code différents toucheront les données provenant d'un formulaire Web avant qu'elles ne soient stockées dans la base de données? Cela pourrait être difficile à mesurer correctement ...

FrustratedWithFormsDesigner
la source
2
Je crois que # 3 s'appelle la profondeur de bloc. Elle est également liée à la complexité cyclomatique si des structures de contrôle décisionnel sont impliquées.
Thomas Owens
Aucune explication de downvote?
FrustratedWithFormsDesigner
Je ne suis pas d'accord avec la "simplicité des fonctionnalités linguistiques". Des fonctionnalités avancées sont là pour simplifier le code. Utiliser uniquement les fonctionnalités simples et de base obscurcira ce que le code fait réellement, cela conduit inévitablement à des fuites de couches d'abstraction. Les fonctionnalités avancées du langage permettent d'exprimer des niveaux d'abstraction plus élevés, laissant votre code beaucoup plus dense et lisible. Les fonctionnalités les plus avancées que vous utilisez (judicieusement, bien sûr), le meilleur pour la simplicité. Il suffit de comparer un code dans, disons, Matlab (qui est en effet "avancé") avec un code Fortran similaire composé uniquement de fonctionnalités de base.
SK-logic
Et je ne suis pas d'accord avec un certain nombre de couches métriques également. Si vous pouvez faire quelque chose en une douzaine d'étapes triviales et propres ou en une seule transformation torsadée, mieux vaut le faire en plusieurs étapes. De nombreuses couches simples et clairement séparées sont bien meilleures (et plus simples) qu'une seule couche torsadée.
SK-logic
@ SK-logic: Je suppose que j'aurais dû appeler ça "l'intelligence", c'est plus proche de ce que je voulais dire. Je dirais seulement que les choses comme ça ?:sont un problème quand elles sont imbriquées à 5 profondeurs. Quant aux couches, les couches bien séparées valent mieux qu'une couche alambiquée. Mais 7 couches principalement redondantes, alors que seulement 2 ou 3 étaient nécessaires, c'est une mauvaise chose.
FrustratedWithFormsDesigner