Implémentez l'analyse statique dans le processus, plutôt que de l'utiliser pour trouver des bogues

J'ai été incité à écrire cet article par la grande quantité de documents sur l'analyse statique qui attirent de plus en plus mon attention. Premièrement, ceci Blog du studio PVS, qui se promeut activement sur Habré à l'aide de revues d'erreurs trouvées par leur outil dans des projets open source. Récemment implémenté le studio PVS Prise en charge de Java, et, bien sûr, les développeurs d'IntelliJ IDEA, dont l'analyseur intégré est probablement le plus avancé pour Java aujourd'hui, je ne pouvais pas rester à l'écart.

En lisant de telles critiques, vous avez l'impression que nous parlons d'un élixir magique : appuyez sur le bouton, et la voici - une liste de défauts sous vos yeux. Il semble qu'à mesure que les analyseurs s'améliorent, de plus en plus de bugs seront automatiquement détectés et les produits analysés par ces robots deviendront de mieux en mieux, sans aucun effort de notre part.

Mais il n’existe pas d’élixirs magiques. J'aimerais parler de ce dont on ne parle généralement pas dans des articles comme « voici ce que notre robot peut trouver » : ce que les analyseurs ne peuvent pas faire, quel est leur véritable rôle et leur place dans le processus de livraison de logiciels, et comment les implémenter correctement. .

Implémentez l'analyse statique dans le processus, plutôt que de l'utiliser pour trouver des bogues
Cliquet (source : wikipedia).

Ce que les analyseurs statiques ne pourront jamais faire

Qu’est-ce que l’analyse du code source, d’un point de vue pratique ? Nous fournissons du code source en entrée et en sortie, en peu de temps (beaucoup plus court que l'exécution de tests), nous obtenons des informations sur notre système. La limitation fondamentale et mathématiquement insurmontable est que nous ne pouvons obtenir de cette manière qu’une classe d’informations assez restreinte.

L’exemple le plus célèbre d’un problème qui ne peut être résolu à l’aide de l’analyse statique est problème d'arrêt: C'est un théorème qui prouve qu'il est impossible de développer un algorithme général capable de déterminer à partir du code source d'un programme s'il bouclera ou se terminera dans un temps fini. Une extension de ce théorème est Théorème de Rice, qui stipule que pour toute propriété non triviale de fonctions calculables, déterminer si un programme arbitraire évalue une fonction avec une telle propriété est un problème algorithmiquement insoluble. Par exemple, il est impossible d'écrire un analyseur capable de déterminer à partir de n'importe quel code source si le programme analysé est une implémentation d'un algorithme qui calcule, par exemple, la quadrature d'un nombre entier.

Ainsi, la fonctionnalité des analyseurs statiques présente des limites insurmontables. Un analyseur statique ne sera jamais capable de détecter dans tous les cas des choses telles que, par exemple, l'apparition d'une "exception de pointeur nul" dans les langages autorisant la valeur null, ou dans tous les cas de déterminer l'occurrence d'un " attribut introuvable" dans les langages typés dynamiquement. Tout ce que l'analyseur statique le plus avancé peut faire, c'est mettre en évidence des cas particuliers dont le nombre, parmi tous les problèmes possibles avec votre code source, est, sans exagération, une goutte d'eau dans l'océan.

L'analyse statique ne consiste pas à trouver des bugs

De ce qui précède, la conclusion découle : l'analyse statique n'est pas un moyen de réduire le nombre de défauts dans un programme. J'oserais dire : lorsqu'il est appliqué pour la première fois à votre projet, il trouvera des endroits « intéressants » dans le code, mais, très probablement, il ne trouvera aucun défaut affectant la qualité de votre programme.

Les exemples de défauts détectés automatiquement par les analyseurs sont impressionnants, mais il ne faut pas oublier que ces exemples ont été trouvés en analysant un large ensemble de bases de code volumineuses. Par le même principe, les pirates qui ont la possibilité d'essayer plusieurs mots de passe simples sur un grand nombre de comptes finissent par trouver les comptes dotés d'un mot de passe simple.

Cela signifie-t-il que l’analyse statique ne doit pas être utilisée ? Bien sûr que non! Et c’est exactement pour la même raison qu’il vaut la peine de vérifier chaque nouveau mot de passe pour s’assurer qu’il est inclus dans la liste d’arrêt des mots de passe « simples ».

L'analyse statique ne se résume pas à la recherche de bugs

En fait, les problèmes résolus en pratique par l’analyse sont beaucoup plus vastes. Après tout, en général, l'analyse statique est toute vérification des codes sources effectuée avant leur lancement. Voici quelques choses que vous pouvez faire :

  • Vérifier le style de codage au sens le plus large du terme. Cela inclut à la fois la vérification du formatage, la recherche de l'utilisation de parenthèses vides/supplémentaires, la définition de seuils sur des métriques telles que le nombre de lignes/la complexité cyclomatique d'une méthode, etc. - tout ce qui entrave potentiellement la lisibilité et la maintenabilité du code. En Java, un tel outil est Checkstyle, en Python - flake8. Les programmes de cette classe sont généralement appelés « linters ».
  • Non seulement le code exécutable peut être analysé. La validité des fichiers de ressources tels que JSON, YAML, XML, .properties peut (et devrait !) être automatiquement vérifiée. Après tout, il est préférable de découvrir que la structure JSON est rompue en raison de certaines citations non appariées à un stade précoce de la vérification automatique de la Pull Request plutôt que pendant l'exécution du test ou l'exécution ? Des outils appropriés sont disponibles : par ex. YAMLlint, JSONLint.
  • La compilation (ou analyse pour les langages de programmation dynamiques) est également un type d'analyse statique. En général, les compilateurs sont capables de produire des avertissements indiquant des problèmes de qualité du code source et ne doivent pas être ignorés.
  • Parfois, la compilation va bien au-delà de la simple compilation de code exécutable. Par exemple, si vous disposez d'une documentation au format AsciiDocteur, puis au moment de le transformer en HTML/PDF le gestionnaire AsciiDoctor (Plugin Maven) peut émettre des avertissements, par exemple en cas de liens internes rompus. Et c'est une bonne raison de ne pas accepter la Pull Request avec des modifications de documentation.
  • La vérification orthographique est également un type d’analyse statique. Utilitaire un sort est capable de vérifier l'orthographe non seulement dans la documentation, mais également dans les codes sources des programmes (commentaires et littéraux) dans divers langages de programmation, notamment C/C++, Java et Python. Une faute d’orthographe dans l’interface utilisateur ou dans la documentation est aussi un défaut !
  • Tests de configuration (à propos de ce qu'ils sont - voir. cette и cette rapports), bien qu'exécutés dans un environnement d'exécution de tests unitaires tel que pytest, sont en fait également un type d'analyse statique, puisqu'ils n'exécutent pas de codes sources lors de leur exécution.

Comme vous pouvez le constater, la recherche de bogues dans cette liste joue le rôle le moins important, et tout le reste est disponible en utilisant des outils open source gratuits.

Lequel de ces types d’analyse statique devriez-vous utiliser dans votre projet ? Bien sûr, plus il y en a, mieux c'est ! L'essentiel est de le mettre en œuvre correctement, ce qui sera discuté plus en détail.

Pipeline de livraison en tant que filtre à plusieurs étapes et analyse statique en tant que première étape

La métaphore classique de l'intégration continue est un pipeline à travers lequel les modifications circulent, depuis les modifications du code source jusqu'à la livraison en production. La séquence standard des étapes de ce pipeline ressemble à ceci :

  1. analyse statique
  2. compilation
  3. tests unitaires
  4. tests d'intégration
  5. Tests d'interface utilisateur
  6. vérification manuelle

Les modifications rejetées à la Nième étape du pipeline ne sont pas transférées à l'étape N+1.

Pourquoi exactement de cette façon et pas autrement ? Dans la partie test du pipeline, les testeurs reconnaîtront la pyramide de test bien connue.

Implémentez l'analyse statique dans le processus, plutôt que de l'utiliser pour trouver des bogues
Pyramide de test. Source: article Martin Fowler.

Au bas de cette pyramide se trouvent les tests plus faciles à écrire, plus rapides à exécuter et qui n’ont pas tendance à échouer. Par conséquent, il devrait y en avoir plus, ils devraient couvrir plus de code et être exécutés en premier. Au sommet de la pyramide, c’est l’inverse qui se produit : le nombre de tests d’intégration et d’interface utilisateur doit donc être réduit au minimum nécessaire. La personne dans cette chaîne est la ressource la plus chère, la plus lente et la plus peu fiable, elle se trouve donc à la toute fin et n'effectue le travail que si les étapes précédentes n'ont trouvé aucun défaut. Cependant, les mêmes principes sont utilisés pour construire un pipeline en parties non directement liées aux tests !

Je voudrais proposer une analogie sous la forme d’un système de filtration d’eau à plusieurs étages. De l'eau sale (changements avec défauts) est fournie à l'entrée ; à la sortie nous devons recevoir de l'eau propre, dans laquelle tous les contaminants indésirables ont été éliminés.

Implémentez l'analyse statique dans le processus, plutôt que de l'utiliser pour trouver des bogues
Filtre à plusieurs étages. Source: Wikimedia Commons

Comme vous le savez, les filtres de nettoyage sont conçus de manière à ce que chaque cascade suivante puisse filtrer une fraction de plus en plus fine des contaminants. Dans le même temps, les cascades de purification plus grossières ont un débit plus élevé et un coût inférieur. Dans notre analogie, cela signifie que les portes de qualité d'entrée sont plus rapides, nécessitent moins d'efforts pour démarrer et fonctionnent elles-mêmes avec moins de prétention - et c'est dans cet ordre qu'elles sont construites. Le rôle de l'analyse statique, qui, comme nous le comprenons maintenant, est capable d'éliminer uniquement les défauts les plus grossiers, est le rôle de la grille « boue » au tout début de la cascade de filtres.

L’analyse statique à elle seule n’améliore pas la qualité du produit final, tout comme un « filtre à boue » ne rend pas l’eau potable. Et pourtant, en conjonction avec d’autres éléments du pipeline, son importance est évidente. Bien que dans un filtre à plusieurs étages, les étages de sortie soient potentiellement capables de capturer tout ce que font les étages d'entrée, il est clair quelles conséquences résulteront d'une tentative de se contenter des seules étapes de purification fine, sans étapes d'entrée.

Le but du « piège à boue » est d’éviter que les cascades ultérieures ne détectent des défauts très grossiers. Par exemple, au minimum, la personne effectuant la révision du code ne doit pas être distraite par un code mal formaté et des violations des normes de codage établies (comme des parenthèses supplémentaires ou des branches trop profondément imbriquées). Les bugs comme les NPE devraient être détectés par les tests unitaires, mais si avant même le test l'analyseur nous indique qu'un bug est inévitable, cela accélérera considérablement sa correction.

Je pense qu'il est désormais clair pourquoi l'analyse statique n'améliore pas la qualité du produit si elle est utilisée occasionnellement et doit être utilisée en permanence pour filtrer les modifications présentant des défauts grossiers. La question de savoir si l’utilisation d’un analyseur statique améliorera la qualité de votre produit équivaut à peu près à la question : « L’eau extraite d’un étang sale sera-t-elle améliorée en qualité de boisson si elle est passée dans une passoire ? »

Implémentation dans un projet existant

Une question pratique importante : comment mettre en œuvre l’analyse statique dans le processus d’intégration continue en tant que « portail qualité » ? Dans le cas des tests automatiques, tout est évident : il existe un ensemble de tests, l'échec de l'un d'entre eux est une raison suffisante pour croire que l'assemblage n'a pas passé le portail qualité. Une tentative d'installation d'un portail de la même manière basée sur les résultats d'une analyse statique échoue : il y a trop d'avertissements d'analyse dans le code existant, vous ne voulez pas les ignorer complètement, mais il est également impossible d'arrêter l'expédition d'un produit. simplement parce qu'il contient des avertissements de l'analyseur.

Lorsqu'il est utilisé pour la première fois, l'analyseur produit un grand nombre d'avertissements sur n'importe quel projet, dont la grande majorité n'est pas liée au bon fonctionnement du produit. Il est impossible de corriger tous ces commentaires d’un coup, et nombre d’entre eux ne sont pas nécessaires. Après tout, nous savons que notre produit dans son ensemble fonctionne, avant même d'introduire l'analyse statique !

De ce fait, beaucoup se limitent à un usage occasionnel de l'analyse statique, ou ne l'utilisent qu'en mode information, lorsqu'un rapport de l'analyseur est simplement émis lors de l'assemblage. Cela équivaut à l'absence de toute analyse, car si nous avons déjà de nombreux avertissements, alors l'apparition d'un autre (aussi grave soit-il) lors du changement de code passe inaperçue.

Les méthodes suivantes pour introduire des critères de qualité sont connues :

  • Fixer une limite au nombre total d'avertissements ou au nombre d'avertissements divisé par le nombre de lignes de code. Cela fonctionne mal, car une telle porte laisse passer librement les modifications comportant de nouveaux défauts, tant que leur limite n'est pas dépassée.
  • Corriger, à un certain moment, tous les anciens avertissements du code comme ignorés et refuser de construire lorsque de nouveaux avertissements surviennent. Cette fonctionnalité est fournie par PVS-studio et certaines ressources en ligne, par exemple Codacy. Je n'ai pas eu l'occasion de travailler dans PVS-studio, quant à mon expérience avec Codacy, leur principal problème est que déterminer ce qui est une « ancienne » et ce qui est une « nouvelle » erreur est un algorithme assez complexe qui ne fonctionne pas toujours correctement, surtout si les fichiers sont fortement modifiés ou renommés. D'après mon expérience, Codacy pouvait ignorer les nouveaux avertissements dans une pull request, tout en ne transmettant pas une pull request en raison d'avertissements qui n'étaient pas liés aux modifications du code d'un PR donné.
  • À mon avis, la solution la plus efficace est décrite dans le livre Livraison continu « méthode à cliquet ». L'idée de base est que le nombre d'avertissements d'analyse statique est une propriété de chaque version et que seules les modifications qui n'augmentent pas le nombre total d'avertissements sont autorisées.

Rochet

Cela fonctionne de cette façon :

  1. Au stade initial, un enregistrement est effectué dans les métadonnées concernant la publication du nombre d'avertissements dans le code trouvé par les analyseurs. Ainsi, lorsque vous construisez en amont, votre gestionnaire de référentiel écrit non seulement « version 7.0.2 », mais « version 7.0.2 contenant 100500 XNUMX avertissements de style de contrôle ». Si vous utilisez un gestionnaire de référentiel avancé (tel qu'Artifactory), il est facile de stocker ces métadonnées sur votre version.
  2. Désormais, chaque demande d'extraction, une fois créée, compare le nombre d'avertissements résultants avec le nombre d'avertissements disponibles dans la version actuelle. Si PR entraîne une augmentation de ce nombre, alors le code ne passe pas le contrôle de qualité pour l'analyse statique. Si le nombre d'avertissements diminue ou ne change pas, alors cela passe.
  3. Lors de la prochaine version, le nombre d'avertissements recalculé sera à nouveau enregistré dans les métadonnées de la version.

Ainsi, petit à petit mais régulièrement (comme lorsqu'un cliquet fonctionne), le nombre d'avertissements tendra vers zéro. Bien sûr, le système peut être trompé en introduisant un nouvel avertissement, mais en corrigeant celui de quelqu'un d'autre. C'est normal, car sur une longue distance, cela donne des résultats : les avertissements sont généralement corrigés non pas individuellement, mais dans un groupe d'un certain type à la fois, et tous les avertissements facilement éliminés sont éliminés assez rapidement.

Ce graphique montre le nombre total d'avertissements Checkstyle pour six mois de fonctionnement d'un tel « cliquet » sur un de nos projets OpenSource. Le nombre d'avertissements a diminué d'un ordre de grandeur, et cela s'est produit naturellement, parallèlement au développement des produits !

Implémentez l'analyse statique dans le processus, plutôt que de l'utiliser pour trouver des bogues

J'utilise une version modifiée de cette méthode, en comptant séparément les avertissements par module de projet et par outil d'analyse, ce qui donne un fichier YAML avec des métadonnées de construction qui ressemblent à ceci :

celesta-sql:
  checkstyle: 434
  spotbugs: 45
celesta-core:
  checkstyle: 206
  spotbugs: 13
celesta-maven-plugin:
  checkstyle: 19
  spotbugs: 0
celesta-unit:
  checkstyle: 0
  spotbugs: 0

Dans n'importe quel système CI avancé, Ratchet peut être implémenté pour n'importe quel outil d'analyse statique sans recourir à des plugins et à des outils tiers. Chaque analyseur produit son propre rapport dans un format texte simple ou XML facile à analyser. Il ne reste plus qu'à écrire la logique nécessaire dans le script CI. Vous pouvez voir comment cela est implémenté dans nos projets open source basés sur Jenkins et Artifactory ici ou ici. Les deux exemples dépendent de la bibliothèque ratchetlib: méthode countWarnings() compte les balises XML dans les fichiers générés par Checkstyle et Spotbugs de la manière habituelle, et compareWarningMaps() implémente le même cliquet, générant une erreur lorsque le nombre d'avertissements dans l'une des catégories augmente.

Une implémentation intéressante du "ratchet" est possible pour analyser l'orthographe des commentaires, des textes littéraux et de la documentation à l'aide d'aspell. Comme vous le savez, lors de la vérification orthographique, tous les mots inconnus du dictionnaire standard ne sont pas incorrects ; ils peuvent être ajoutés au dictionnaire utilisateur. Si vous intégrez un dictionnaire personnalisé au code source du projet, le contrôle de la qualité orthographique peut être formulé de cette manière : exécuter aspell avec un dictionnaire standard et personnalisé. ne devrait pas ne trouve aucune faute d’orthographe.

À propos de l'importance de corriger la version de l'analyseur

En conclusion, le point à noter est que quelle que soit la manière dont vous implémentez l’analyse dans votre pipeline de livraison, la version de l’analyseur doit être corrigée. Si vous autorisez l'analyseur à se mettre à jour spontanément, alors lors de l'assemblage de la prochaine demande d'extraction, de nouveaux défauts peuvent « apparaître » qui ne sont pas liés aux modifications du code, mais sont liés au fait que le nouvel analyseur est simplement capable de trouver plus de défauts - et cela interrompra votre processus d'acceptation des demandes d'extraction. La mise à niveau d'un analyseur doit être une action consciente. Cependant, la fixation rigide de la version de chaque composant de l'assemblage est généralement une exigence nécessaire et un sujet pour une discussion séparée.

résultats

  • L'analyse statique ne trouvera pas de bugs pour vous et n'améliorera pas la qualité de votre produit grâce à une seule application. Un effet positif sur la qualité ne peut être obtenu que grâce à son utilisation constante pendant le processus de livraison.
  • La recherche de bugs n'est pas du tout la tâche principale de l'analyse : la grande majorité des fonctions utiles sont disponibles dans les outils open source.
  • Implémentez des contrôles de qualité basés sur les résultats de l'analyse statique dès la toute première étape du pipeline de livraison, en utilisant un « cliquet » pour le code existant.

références

  1. Livraison continu
  2. A. Kudryavtsev : Analyse de programme : comment comprendre que vous êtes un bon programmeur rapport sur différentes méthodes d'analyse de code (pas seulement statique !)

Source: habr.com

Ajouter un commentaire