Qualité du code

Produire un code de qualité est nécessaire pour réduire le temps de développement, limiter les bugs, faciliter la maintenance, etc… Voici quelques grandes pistes à explorer pour tendre vers cet objectif

Bonnes pratiques

Structuration

L’architecture du code doit être simple et compréhensible rapidement

La duplication de code est à proscrire absolument. On connait également ceci sous l’acronyme DRY (Don’t Repeat Yourself). Un code dupliqué augmente le risque d’introduction de bug et diminue fortement la lisibilité. Lorsque des tâches répétitives sont identifiées à plusieurs endroits du programme, il faut structurer le code en factorisant les opérations concernées dans une fonction ou une classe.

En programmation orientée objet le couplage mesure la dépendance d’une classe par rapport à une autre. Réduire le couplage revient à limiter la dépendance des objets entre eux, ce qui facilite la compréhension globale du code, et donc la maintenance. La cohésion consiste à déléguer à une classe l’ensemble des opérations qui relève de son champ d’action. De façon générale, on cherchera donc un couplage faible et une forte cohésion.

Lisibilité

Lisibilité du code nom des variables, indentation, structuration en fonctions, classes, utilisation des structures de données et des constructions optimales.

La longueur des lignes doit être limitée à 80 caractères ce qui correspond à un affichage optimal sur un écran. Lorsque l’on ne peut pas réduire la longueur de l’instruction, il faut la découper sur plusieurs lignes:

MY_MAP = Basemap(llcrnrlon=-10, llcrnrlat=40, urcrnrlon=10, urcrnrlat=55,
                     rsphere=(6378137.00, 6356752.3142), resolution='l',
                     projection='merc')

Pour des raisons de lisibilité, il est conseillé:

  • de ne pas avoir plus de 5 arguments dans une fonction. Le constructeur de la classe Basemap ci dessus ne respecte pas cette recommendation. Une amélioration possible serait de lui passer un tuple définissant les limites de l’affichage.

  • d’utiliser des noms de variable explicites

  • de ne pas avoir plus de 8 variables dans le corps d’une fonction ou d’une méthode.

Documentation

Le commentaire a pour vocation de répondre à la question Pourquoi ? La réponse à la question Comment ? doit être apportée par le code lui même. Pour ne pas surcharger le programme, le commentaire doit être pertinent et apporter une information que le code lui même ne donne pas. A titre d’exemple, le commentaire ci dessous n’est pas nécessaire, la méthode append() étant suffisamment explicite:

>>> my_list.append(elt) # ajoute un élément à la liste my_list

Une densité des commentaires inférieure à 20% est généralement synonyme d’un code pas assez documenté.

Tests

Les tests unitaires permettent de confronter automatiquement le code à un grand nombre de situations pour vérifier la conformité avec le cahier des charges et effectuer des tests de non régression lors de la modification d’une partie du code. Dans le cadre de ce cours, les tests ont été implémentés avec le module doctest qui répond parfaitement à nos attentes. Cependant, dans le cadre de conception logicielle plus poussée, doctest peut se révéler limité. On se tournera alors vers des solutions plus abouties. Parmi celles disponibles, Python dispose en standard du module unittest et d’un certain nombre de modules tiers dont le package pytest. On appelle couverture de code, le pourcentage de code couvert par des tests unitaires. On considère que 10% du temps de développement doit être affecté aux tests.

Conventions de codage

Comme les autres langages, Python définit un certain nombre de convention dans la PEP 8. Les plus importantes sont rappelées ici:

  • Use 4-space indentation, and no tabs.

  • 4 spaces are a good compromise between small indentation (allows greater nesting depth) and large indentation (easier to read). Tabs introduce confusion, and are best left out.

  • Wrap lines so that they don’t exceed 79 characters.

  • This helps users with small displays and makes it possible to have several code files side-by-side on larger displays.

  • Use blank lines to separate functions and classes, and larger blocks of code inside functions.

  • When possible, put comments on a line of their own.

  • Use docstrings.

  • Use spaces around operators and after commas, but not directly inside bracketing constructs: a = f(1, 2) + g(3, 4).

  • Name your classes and functions consistently; the convention is to use CamelCase for classes and lower_case_with_underscores for functions and methods. Always use self as the name for the first method argument (see A First Look at Classes for more on classes and methods).

  • Don’t use fancy encodings if your code is meant to be used in international environments. Python’s default, UTF-8, or even plain ASCII work best in any case.

  • Likewise, don’t use non-ASCII characters in identifiers if there is only the slightest chance people speaking a different language will read or maintain the code.

Outils de contrôle

Comme d’autres langages, Python dispose d’un certain nombre d’outils permettant d’effectuer une certaine mesure de la qualité du code produit. Dans ce cours nous utiliserons Pylint.

Pylint effectue un certain nombre de vérifications visant à s’assurer que le code produit respecte un certain nombre de recommendations et délivre des messages de criticité différente:

  • [R]efactor : de la duplication de code a été détectée

  • [C]onvention : les conventions de codage ne sont pas respectées

  • [W]arning : problème mineur

  • [E]rror : problème majeur. Mais lors de l’utilisation de certains modules, des constructions valides peuvent générer des erreurs de ce type. Numpy par exemple…

  • [F]atal : erreur interrompant le processus d’analyse.

L’ensemble des contrôles effectués par Pylint est donné ici.

Pour illustrer le fonctionnement de Pylint, on considère le code contenu dans le fichier fact.py, implémentant la fonction factorielle:

1# fact.py
2def fact(n):
3    if n <= 0:
4        return 'n must be strictly positive'
5    if n <= 2:
6        return n
7    return n*fact(n-1)

Ce code est parfaitement fonctionnel:

>>> import fact
>>> fact.fact(5)
120

Pour mesurer la qualité de ce code, ouvrir un terminal et exécuter Pylint. La dernière ligne de l’affichage donne une mesure de la qualité de ce code:

$ pylint fact.py

Your code has been rated at 5.71/10

L’examen des premières lignes fournit des indications sur les corrections à apporter:

  • C:  1, 0: Missing module docstring (missing-docstring) relève l’absence de docstring pour le module (fichier .py)

  • C:  1, 0: Invalid argument name "n" (invalid-name) : pour des raisons de lisibilité Pylint prohibe l’utilisation de variables de moins de 3 caractères

  • C:  1, 0: Missing function docstring (missing-docstring) : les fonctions contenues dans le module doivent elles aussi posséder leur docstring. Corrigeons le fichier en ce sens:

 1# fact.py
 2"""
 3author = daniel.courivaud@esiee.fr
 4"""
 5
 6def fact(num):
 7    """
 8    Retourne la factorielle de num.
 9
10    Args:
11        num: valeur entiere positive
12
13    Returns:
14        fact(num) : num*(num-1)* ... * 2
15    """
16    if num <= 0:
17        return 'num must be strictly positive'
18    if num <= 2:
19        return num
20    return num*fact(num-1)

On peut également exécuter Pylint en important le package avec l’option -m. La qualité est maintenant optimale:

$ python -m pylint fact.py

Your code has been rated at 10.00/10 (previous run: 5.71/10, +4.29).

On peut également lancer Pylint depuis un programme Python :

# fact.py
"""
author = daniel.courivaud@esiee.fr
"""

import pylint.lint

# fact() function code here...

if __name__ == "__main__":
    pylint_opts = ['--disable=line-too-long', __file__]
    pylint.lint.Run(pylint_opts)

L’ensemble des options de Pylint est donnée sur cette page.

L’utilisation de Pylint est fortement encouragée.