Génie logiciel : coder facilement, coder à plusieurs, qualité du code, gestion de projets#

L’industrie logiciel a beaucoup évolué en 20 ans et plusieurs standards ont émergé pour travailler à plusieurs et produire un logiciel avec peu d’erreurs. Cette page est plus un guide de bonne conduite, un ensemble de pratiques permettant d’améliorer la qualité du code. Ces pratiques ne sont sans doute pas utiles si votre code est juste une étude et n’a pas vocation à durer. Dans ce cas, je recommande tout de même l’utilisation d’un Logiciel de suivi de source. Les Tests unitaires et la Documentation sont à considérer dès le début du projet dans le cas contraire. Il est beaucoup plus difficile de les ajouter par la suite.

Robustesse du code#

Tests unitaires#

Les fonctions d’un programme prenne part à un grand ensemble et lorsqu’une erreur se déclare, il n’est pas toujours évident de localiser la source d’erreur. Ce cas arrive fréquemment lorsqu’on modifie le code d’une fonction utilisée par beaucoup d’autres. Les tests unitaires ont pour objectif de d’assurer de la qualité du code. Ils permettent de vérifier qu’une modification n’a pas de répercussionss inattendues. La règle veut qu’on ajout un test unitaire pour chaque fonction ajoutée au code et chaque bug découvert. Dans la pratique, si on ne le fait pas tout de suite, on le fait rarement. Il est impensable de vendre ou écrire un projet open source sans ces tests. La plupart des langages permettent d’écrire cela rapidement. En Python Qu’est-ce qu’un test unitaire ? avec les modules :

resources distantes

Les tests unitaires sont difficiles à mettre en place dès qu’une resource distance est impliquée. Le test peut échouer parce qu’Internet n’est pas accessible, parce que le site web ne répond pas, pour un problème d’identification. Ce problème est abordée au paragraphe suivant avec le concept de mock.

Une des réponses est d’introduire plusieurs séries de tests :

  • tests unitaires ou fonctionnels : teste une fonction en particulier, ces tests sont très rapides.

  • tests d’intégration : vérifier qu’une librairie fonctionne toujours avec ses dépendances, surtout après une mise à jour. C’est important surtout si on écrit du code différencié selon la version d’une dépendance.

  • tests de non régression : teste un algorithme et vérifie que sa réponse ne se dégrage après une modification, qu’un bug fixé le reste.

  • tests système ou end to end : teste une fonctionnalité impliquant une ressource externe (serveur SQL, Internet), sur un environnement précis.

site web, serveur

Il faut démarrer un serveur pour tester un site de bout en bout.

Et cela ne suffit pas toujours car la plupart des sites utilisent du javascript pour lesquels il faudrait simuler des événements au clavier (touches, souris) une fois le site lancé. Pour vérifier certains aspects du site web tels qu’ils seront par un internaute :

GUI

Même si on crée de moins en moins d’application GUI (avec une interface graphique), il est préférable de les tester.

A ce sujet, lire Automate the boring stuff with Python.

Mock#

Mocking désigne une astuce uilisée pour écrire un test unitaire pour une fonctionnalité utilisant une ressource externe comme internet. Par exemple, on souhaite tester une fonction qui vérifie que son adresse est la bonne sur un site internet. Cela inclut :

  • télécharger le contenu de la page

  • parser le contenu pour extraire l’adresse

  • vérifier que l’adresse est la bonne

Le problème survient souvent lorsqu’un internet n’est pas accessible. Le test échoue mais pas pour la bonne raison. On construit alors un objet qui retourne toujours le même contenu qu’on aura enregistré. On peut alors tester séparément le fait de télécharger une page et celui d’extraire une adresse et de la comparer avec une autre. En Python, on fait cela avec le module :

Profiling#

Si un programme lent ou gros consommateur de mémoire, on veut savoir où le programme perd du temps ou occasionne des pics de mémoire. Le profiling permet de mesurer la vitesse d’exécution et la consommation de chaque fonction ou chaque ligne. Voir :

On peut aussi regarder le bytecode généré par l’interpréteur Python : Fun with Python bytecode.

Couverture des tests unitaires#

La couverture d’un code informatique mesure le nombre de lignes de code effectivement executée lors de l’exécution des tests unitaires. Une couverture de 80% est souvent un bon gage de qualité. En deça, il faut généralement s’attendre à quelques surprises. Pour mesurer cette couverture, un module coverage. Il est plus facile d’augmenter la couverture pour des fonctions de calculs numériques et c’est plus difficile pour un processus d’automatisation qui requiert l’accès à une ressource externe. On s’aperçoit aussi que les exceptions ne sont pas souvent couvertes par les tests unitaires.

Déploiement#

Une application web fonctionne rarement sur une seule machine, l’application est déployée sur plusieurs machines. Une entreprise dispose en générale de plusieurs environnements de test afin de pouvoir tester une application ou un service en grandeur réelle sans l’exposer aux utilisateurs. Cela requiert pas mal d’automatisation afin que cela soit facile et rapide. Voir ansible.

Tests logiques et preuve formelle#

Tous les tests présentés ci-dessus ne font que tester un cas particulier. La fonction marche avec ce jeu de données dans ce cas précis. Le test ne vérifie pas que la fonction est valide dans tous les cas. Cela ne peut passer que part une preuve formelle que le code fonctionne. Dans certains cas, cette preuve formelle est nécessaire car toute erreur a un coût prohibitif. Les logiciels de calculs utilisée par Ariane Espace le sont. On rentre dans le domaine des mathématiques.

Ces outils ne s’appliquent pas aux langages faiblement typés. Ils s’intéressent entre autres aux erreurs d’arrondis. Il est difficile de prévoir la valeur d’un résultat si le type change au cours des calculs. Néanmoins, il existe des approches intermédiaires comme le module hypothesis.

Pratiques utiles#

Logging#

Logger consiste à enregister des événements, des opérations effectués par un programme dans le but de les analyser plus tard en cas d’erreur. Les logiciels sont maintenus conçus pour ne plus s’arrêter en cas d’erreur, celle-ci est donc loggée ainsi que la séquence d’événements qui a débouché sur cette erreur en espérant que cette information sera suffisante pour comprendre et corriger l’erreur. Dans le cas de site web, l’information est utilisée pour mesurer l’audience de pages internet, la fréquence d’événements, leur enchaînements. Pour éviter de ralentir le programme, l’information est stockée dans un fichier texte plat. En Python, cela se fait avec le module logging (voir aussi logbook).

Logger n’est pas aussi simple qu’il y paraît. Il faut choisir un format de fichier qui facilite l’analyse. Le plus souvent :

<data> <niveau> <message>

Le niveau de logging (souvent INFO, TRACE, WARNING, ERROR) détermine la quantité d’information que le programme enregistre. Plus il y en a, plus c’est lent. Quand on développe, on logge tout, en production, on se restreint aux avertissements et erreurs (voir Good logging practice in Python, Python 101: An Intro to logging).

La difficulté majeure quand on logge survient quand le programme s’exécute en parallèle. En règle générale, la fonction qui permet de logger une information ne fait pas partie des arguments qu’elle reçoit : c’est une variable statique. De cette façon, le programme est plus clair mais il arrive que la même fonction s’exécute plusieurs fois en parallèle. Il devient alors difficile de reconstituer une séquence d’événement appartenant au même fil d’exécution. Les outils de logging sont capables de reconnaître différents contextes (le thread) mais cela a un coût en terme de performance. Il faut éviter de logger trop d’information, âs plus d’une dizaine par secondes.

Documentation#

L’outil standard en Python est Sphinx. Il aboutit à une documentation statique comme ce site web. Il faut suivre un unique format comme RST ou MD (markdown) pour écrire la documentation et il faut commencer dès les premières fonctions.

Les principaux écueils sont une documentation obsolète car une modification n’a pas été reportée dans la documentation et des exemples qui ne fonctionne pas ou plus. Le plus simple est de s’inspirer de la documentation d’un module qui vous plaît. On y retrouve presque toujours les sections :

  • Un exemple de bienvenue

  • Installation

  • Tutorial

  • Documentation

Il faut également faire attention au choix des noms de classes et fonctions. La documentation est parfois superflue lorsque ceux-ci sont bien choisis.

Style#

Annotations#

Le langage Python est faiblement typé. On précise parfois le type attendu dans la documentation d’une fonction ou d’une classe. Les annotations sont un moyen formel de le faire. Même si elle n’a aucune incidence, cette information peut être utilisée :

Ce concept a été introduit par la version 3.5.

PEP8#

Il est plus facile de lire un code qui suit toujours les mêmes règles d’écriture. En Python, on les appelles PEP8. Ce ne sont pas des règles à suivre à la lettre mais la plupart des développeurs python les suivent.

A vous de voir. A l’école, cela n’a pas d’incidence. Dans une compagnie, c’est très variable.

Design#

En terme de design, scikit-learn est un modèle du genre. La librairie propose des algorithmes de machine learning - ce n’est pas nouveau - mais son adoption rapide est due à la simplicité de son interface, de son design.

  • petites fonctions

  • séparation GUI / web / algorithme

  • long process : prévoir une interruption, logging, processus asynchrone

  • GUI réactive : asynchrone

  • éviter les variables statiques

Outils#

Logiciel de suivi de source#

C’est devenu un outil incontournable pour garder la trace des modifications apporter à un programme. La première tâche est la possiblité de revenir en arrière. C’est un outil qui permet d’accéder rapidement à la partie de code modifiée. Aujourd’hui, la plupart des nouveaux projets commencent sur git. Et vous devriez connaître les trois sites suivants qui hébergent gratuitement les projets open source :

Ces sites sont payants pour tout projet privé. GitHub hébergent la grande majorité des projets open source. Il est aussi une extension du CV.

Gestion de projet#

Le terme consacré est KanBan. Il est issu des méthodes agiles. Concrètement, plus on en nombreux à travailler sur le même projet, plus il est difficile de garder la trace de ce que chacun fait. Cette approche a été en quelque sorte validée par la pratique.

Revue de code#

Une revue de code intervient avant la mise à jour du code d’un logiciel. C’est l’occasion pour un dévelopeur de partager ses modifications avec le reste de son équipe qui commentent les parties du code qui leur déplaisent ou qu’ils approuvent si la mise à jour leur convient. La règle est souvent qu’une modification ne peut être prise en compte dans le code de l’application que si un ou deux autres dévelopeurs la valide.

Poussé à l’extrême, cela devient le pair programming qui consiste à programmer à deux devant le même écran. C’est assez cauchemardesque si c’est permanent. En pratique, la revue de code est un exercice utile. Un logiciel open source : gerrit.

Continuous integration#

On devrait faire tourner l’ensemble des tests unitaires à chaque modification. En pratique, on le fait pas toujours voire rarement car cela prend trop de temps. On délègue cette tâche à une machine distante. Toute cette machinerie fait partie des systèmes d’intégration continue :

  • un dévelopeur ajoute une modification au code source

  • l’application est compilé

  • les tests unitaires sont validés

  • l’application est déployée

Ce circuit est effectué à chaque modification ou chaque nuit. C’est ce type de service que propose gratuitement pour les projets open source travis sur Linux, Circle CI, appveyor sur Windows. Ces solutions s’exécutent à distance. Localement, on peut utiliser Jenkins qui est très simple d’utilisation ou BuildBot Ce site est construit avec Jenkins.