...

Sur les bonnes pratiques de code avec Python

20 avril 2022

Post en cours de rédaction.

Écrire du code de bonne qualité est indispensable lorsqu'on souhaite développer des applications pérennes qui dépassent un certain niveau de complexité. Pour autant le concept de qualité d'une base de code est difficile à cerner. Il regroupe une multitude d'aspects, et bien souvent deux personnes qui s'y réfèrent ont à l'esprit des choses très différentes. Ici, on explicite une partie de ce qui se cache derrière ce concept de qualité, ainsi que quelques bonnes pratiques de code (en particulier pour le langage Python). Ces bonnes pratiques sont parfois plus faciles à appliquer grâce à un outillage adapté : cet article contient ainsi des liens vers des ressources extérieures permettant d'apprendre à utiliser cet outillage, mais également d'approfondir certains sujets.

Qualité et complexité

Les bases de codes gagnent rapidement en complexité au cours du temps, en particulier quand elles sont le fruit d'un travail collaboratif entre plusieurs contributeurs. Plus la complexité est importante, moins il est rapide de comprendre les implications d'une addition, d'une délétion ou d'une modification d'une partie du code. Si la complexité est trop grande, les erreurs d'implémentation se font beaucoup plus fréquentes. En outre, il est plus difficile pour de nouveaux contributeurs de se joindre au projet, car le coût d'entrée devient très important.

On peut d'une certaine manière voir la qualité comme le fait de limiter la complexité de sa base de code, qui doit rester simple à lire et à comprendre, et à laquelle il doit être facile de contribuer. Sous cet angle, un code est de meilleure qualité qu'un autre s'il conduit au même résultat avec une complexité moindre. Bien entendu il n'existe pas de métrique unique pour mesurer la complexité du code et cette dernière est parfois subjective. Toutefois, il existe des concepts et des principes qui une fois suivis aident à produire du code qui sera en général moins complexe, et donc de meilleure qualité. Dans la suite de cet article, on détaille comment une bonne utilisation de Git, d'outils d'analyse de code statique et de tests unitaires permet de maintenir un niveau de qualité élevé (en abordant quelques points majeurs - on donne aussi des références pour aller plus loin). Ceci est particulièrement important dans un environnement de développement logiciel, mais devient en réalité utile rapidement, dès lors que plusieurs personnes travaillent sur du code commun. En outre, dans un environnement de recherche, respecter les bonnes pratiques dans le cadre de projets expérimentaux permet de minimiser le coût de développement d'une application production-ready en aval.

Tout à un coût et on ne déroge pas à la règle ici. Pour chaque projet, des arbitrages sont nécessaires, car demander un niveau de qualité élevé et donc le respect d'un certain nombre de bonnes pratiques a un prix qu'il n'est pas toujours souhaitable de payer. Il faut toutefois noter que l'apprentissage de bonnes pratiques et de l'utilisation d'outils constitue pour une organisation un investissement qui dépasse le cadre d'un simple projet, et qui apporte des bénéfices sur le long terme.

La PEP 8

La PEP 8 est un guide stylistique pour Python, un ensemble de conventions qui permet d'homogénéiser le code et d'appliquer certaines bonnes pratiques. Il est recommandé de respecter les conventions de la PEP 8 par défaut lorsqu'on écrit du code Python, ce qui facilite la lecture du code pour d'autres contributeurs. Les conventions couvrent par exemple les aspects suivants :

  • Structure du code : indentation, longueur maximale d'une ligne de code, lignes vides, imports, etc. ;
  • Commentaires : docstrings, blocs de commentaires, etc. ;
  • Conventions d'appellation : noms à éviter, noms de classes, noms d'exceptions, noms de variables, noms de constantes, etc. ;
  • Annotations de type pour les fonctions et les variables.

À noter que les environnements de développement (IDE) intègrent en général des outils d'analyse de code qui surlignent les violations aux conventions de la PEP 8. Il est plus facile d'écrire du code propre stylistiquement en utilisant ces outils (ou des librairies adaptées, voir ci-dessous).

Comment maintenir un haut niveau de qualité ?

Gestion de version (Git)

Git est un outil indispensable pour travailler en collaboration sur du code, dont l'utilité augmente avec l'expérience de chaque contributeur. Plusieurs bonnes pratiques sont à souligner concernant l'utilisation de Git, même si de nombreux workflows différents existent :

  • Il est conseillé de ne pas faire directement de modifications du code sur la branche principale mais plutôt de travailler sur une branche ad-hoc pour implémenter une nouvelle fonctionnalité, corriger un bug, etc. Cette manière de travailler demande d'ouvrir une merge request pour faire passer des changements sur la branche principale et permet de systématiser les revues de code : chaque merge request est assignée à une personne qui s'assure que le code écrit remplit bien son rôle, qu'il n'apporte pas de complexité inutile, qu'il laisse la porte ouverte à des changements ou à des extensions futures, que des tests sont implémentés (si nécessaire), etc. Les revues sont la clé de voûte de la qualité d'un dépôt de code (et au-delà de ça, elles sont un puissant moteur de montée en compétence au sein d'une organisation, car elles permettent à chacun d'apprendre quotidiennement du savoir-faire des autres) ;
  • Pour faciliter la revue de code au moment de merge, il est préférable de limiter la taille des branches (par exemple on évite d'inclure 10 nouvelles fonctionnalités indépendantes dans une même branche), et de faire des petits commits avec des messages clairs ;
  • Pour garder un historique propre, une fonctionnalité importante de Git est l'interactive rebase (voir ce post de blog).

Analyse de code statique

L'analyse de code statique permet de respecter facilement un grand nombre de bonnes pratiques (en particulier concernant le formatage du code). Même si le code n'est jamais exécuté pendant l'analyse, cette dernière évite de fait beaucoup de bugs qui auraient provoqué des erreurs au runtime. Elle peut se faire dans l'environnement de développement du codeur, mais également dans une pipeline d'intégration et de distribution continue (CI/CD), ce qui est facilement configurable avec Gitlab pour n'intégrer que du code avec un haut niveau de qualité à la branche principale du projet (voir cette page de documentation). Plusieurs outils/standards sont couramment utilisés :

  • Flake8 et/ou Pylint sont des outils d'analyse statique de code (de type linter) qui imposent le respect des conventions de la PEP 8. Ces outils permettent par exemple de détecter :
    • les variables qui n'existent pas ;
    • les variables inutilisées ;
    • les erreurs de syntaxe, etc.
    Ces deux outils sont complémentaires l'un de l'autre et utiliser les deux ne conduit pas à des conflits.
  • Black et isort sont des formateurs automatique de code qui respectent les conventions de la PEP 8. Ces deux formateurs sont également compatibles.
  • Mypy est un type checker statique pour Python. Python donne la possibilité d'utiliser des annotations de type au sein de son code. Ces annotations ne modifient pas le comportement du code au runtime (le langage est typé dynamiquement) mais permettent l'utilisation d'outils comme mypy pour repérer des bugs courants.

Dans l'environnement de développement du codeur, tous ces outils sont faciles à utiliser au travers de pre-commit hooks. Un pre-commit hook fait tourner un ensemble pré-configuré de contrôles et/ou de formateurs automatiques avant d'effectuer un commit avec Git. Quelques points sur l'utilisation de la librairie :

  • Au moment de chaque commit, pre-commit effectue les contrôles et opérations de formatage pour lesquels il est configuré. Si certains contrôles ne passent pas, le commit ne se fait pas. Il est aussi possible de lancer pre-commit avec la commande pre-commit run --files ;
  • Il est possible de sauter les tests avec git commit -n, ou même des tests spécifiques ;
  • La librairie se configure dans un fichier .pre-commit-config.yaml à placer à la racine du projet.

Cette vidéo montre un exemple de configuration de GitLab pour utiliser les librairies mentionnées ci-dessus dans une pipeline CI/CD.

Tests unitaires

Les tests unitaires sont un élément très important d'une base de code stable. Un test unitaire aide à vérifier le bon fonctionnement d'une partie précise d'une application (par exemple d'une fonction). En outre, les tests unitaires permettent parfois d'étoffer la documentation de la partie concernée, en montrant comment elle doit être utilisée. Avoir une batterie de tests complète constitue une protection contre l'apparition de bugs lorsqu'une partie du code est modifiée. Avec GitLab, il est possible de configurer la pipeline CI/CD pour qu'un ajout à la branche principale nécessite que tous les tests s'exécutent sans erreur.

La manière optimale de maintenir cette batterie complète pour un dépôt de code est d'implémenter des tests unitaires pertinents en même temps que chaque nouvelle fonctionnalité. La bonne implémentation des tests peut être contrôlée au cours de la revue de code précédant l'intégration à la branche principale.

Pour Python, les frameworks majoritairement utilisés sont :

A propos de ce blog

Les informations qui y sont diffusées n'engagent que les contributeurs et en aucun cas les institutions dont ils dépendent.