Comment coder proprement
Les conseils proposés dans ce guide sont tirés, en majeur partie, de l’ouvrage Clean Code: A Handbook of Agile Software Craftsmanship de Robert C. Martin. Il s’agit d’un ouvrage de référence sur la question des bonnes pratiques de programmation. Seul le contenu des quatre premiers chapitres de l’ouvrage est résumé dans ce guide. Nous vous invitons à consulter le livre si vous désirez aller plus loin.
Tous les exemples ont été écrit en python, puisqu’il s’agit du langage de programmation le plus utilisé en sciences des données. Les conseils de ce guide peuvent cependant être appliqués à d’autres langages de programmation.
Table des matières
1. Nommer les variables
Coder proprement nécessite d’abord et avant tout de bien nommer ses variables. Des variables mal nommées peuvent rapidement rendre le code incompréhensible, même pour son auteur.
Le grand principe à respecter lorsqu’on nomme ses variables : révéler l’intention. Pour le dire succintement il faut dire : d’une variable -> ce que c’est. d’une fonction -> ce qu’elle fait.
Quelques conseils pour bien nommer ses variables
- Éviter les noms de variable courts.
- Éviter les abréviations.
- Éviter des noms de variables qui varient peu entres eux.
- Éviter d’utiliser deux mots différents pour dénoter la même chose/action (un mot par concept).
- Utiliser un verbe qui décrit ce que fait une fonction lorsque vous la nommez.
Conseil | Status | Exemple |
---|---|---|
1 | Mauvais | a = pd.read_csv("articles.csv") |
1 | Bon | articles = pd.read_csv("articles.csv") |
2 | Mauvais | tot_obs = 10 |
2 | Bon | total_observations = 10 |
3 | Mauvais | count_of_trees_in_the_city = 50 count_of_plants_in_the_city = 40 |
3 | Bon | trees_count = 50 plants_count = 40 |
4 | Mauvais | get_observations() fetch_annotations() |
4 | Bon | get_observations() get_annotations() |
5 | Mauvais | def articles(file_path): [code] |
5 | Bon | def get_articles(file_path) [code] |
2. Les commentaires
Il y a une croyance assez répendue dans le monde de la programmation selon laquelle on doit abondamment commenter son code. Selon l’auteur de Clean Code, cette croyance est fausse. Le grand danger réside dans le fait qu’on peut oublier de mettre à jour des commentaires. Lorsque cela se produit, les commentaires peuvent nous induire en erreur. Pour citer Robert C. Martin : «les commentaires peuvent mentir, mais le code dit la vérité». Sachant cela, il vaut mieux, la plupart du temps, utiliser son énergie pour essayer d’écrire du code propre plutôt que d’écrire des commentaires. Si l’on devait retenir un grand principe par rapport aux commentaires ce serait celui-ci : privilégier le code propre. Voici donc quelques conseils en lien avec les commentaires.
Quelques conseils par rapport aux commentaires
- Plutôt que d’écrire de longs commentaires afin d’expliquer comment fonctionne un bout de code malpropre, prenez ce temps pour essayer de rendre votre code plus propre.
- Évitez les commentaires qui expliquent ce que fait du code simple. Vous pouvez tenir pour acquis que la personne qui lira votre code connaît les fonctionnalités de base du langage que vous utilisez. Il ou elle n’a pas besoin de plus d’explications.
- Ne pas laisser de code commenté. On peut avoir tendance à commenter du code qui ne sert plus, mais dont on pense avoir besoin plus tard. Il vaut mieux l’effacer et se reposer sur notre outil de contrôle des versions (git).
Conseil | Status | Exemple |
---|---|---|
1 et 2 | Mauvais | # itération sur la liste d'articles pour aller chercher les titres # 'a' représente la liste des articles (qui est une liste de liste) list = [] for i in range(52): list.append(a[i][3]) |
1 et 2 | Bon | title_index = 3 number_of_articles = len(articles) article_titles = [] for article_index in range(number_of_articles): article_titles.append(articles[article_index][title_index]) |
3 | Mauvais | articles = get_articles(path) # articles = articles[:-1] # articles = do_something_funky(articles) save(articles) |
3 | Bon | articles = get_articles(path) save(articles) |
Les commentaires sont utiles pour
- Justifier un choix, par exemple si vous faites un choix d’algorithme, un choix de librairie, ou un choix de conception qui mérite d’être justifié.
- Indiquer la durée que prend l’exécution d’un bout de code (si c’est long).
- Documenter une fonction (préciser le type des paramètres et les données retournées). Voir, par exemple, l’outil Docstrings en python
- Expliquer un bout de code complexe (dont la logique serait difficile à comprendre même pour un programmeur ou une programmeuse d’expérience).
3. Les fonctions
Lorsqu’un projet grossit, écrire des fonctions devient inévitable. Si l’on veut écrire du code propre, on doit répondre à la question de savoir ce qu’est une bonne fonction, et comment décomposer son code en fonctions. C’est à ces questions que cette section tentera de répondre.
L’utilité des fonctions
- Éviter la redondance. Avoir plusieurs versions d’un même bout de code peut générer des erreurs. En effet, si l’on veut modifier ce bout de code, on devra mettre à jour chaque version. Or, il se peut qu’on oublie de le faire pour une version. On prend alors le risque d’avoir deux bouts de code qu’on croit identiques, mais qui font des choses différentes.
- Dévoiler ce que son code fait. En imbriquant notre code dans des fonctions, et en faisant bien notre travail de nommage de ces fonctions, on dévoile au lecteur ce que notre code fait.
- Masquer la complexité. La personne qui lit notre code n’a pas toujours besoin de savoir comment fonctionne notre code dans le détail. En imbriquant notre code dans des fonctions, on offre au lecteur une sorte de résumé de ce que notre code fait. S’il veut avoir plus de détails, il pourra aller voir l’implémentation de nos fonctions.
Conseils pour écrire de bonnes fonctions
- Courtes. Viser un maximum de 20 lignes. Si elle est plus longue, on doit la décomposer en de multiples fonctions plus courtes.
- Aucun side effect : Une fonction ne change pas la valeur de variables déclarées à l’extérieur d’elle.
- Un niveau d’abstraction. Le code au plus bas niveau d’abstraction est le code qui fait les opérations les plus simples, par exemple, lire un fichier, faire des opérations simples sur des chaînes de caractères ou sur des listes, faire des opérations mathématiques, etc. Une fonction qui regroupe du code du plus bas niveau serait au premier niveau d’abstraction, et une fonction qui regroupe une multitude de fonctions de premier niveau serait au deuxième niveau, et ainsi de suite. Dans une fonction, il faut éviter d’avoir des lignes de codes qui sont à différents niveaux d’abstractions.
- Fait une chose (à un niveau d’abstraction). Votre fonction doit uniquement faire ce que son nom suggère. Par exemple, si votre fonction s’occupe de télécharger des données, elle doit uniquement faire cela. Elle ne doit pas s’occuper d’une partie de travail de nettoyage des données. «Faire une chose» doit être compris en relation avec le concept de niveau d’abstraction. La «chose», ou l’action, que fait une fonction peut très bien être décomposable en de multiples actions plus simples. Pour faire une analogie avec une tâche de la vie quotidienne, «acheter du lait» peut être considéré comme faire une action, même si l’on peut décomposer cette action en une multitude d’actions plus simples. Si j’arrête prendre un café en allant acheter du lait cependant, j’ai alors fait deux choses.
Conseil | Status | Exemple |
---|---|---|
2 | Mauvais | VALEUR_CONSTANTE = 5 def function_avec_side_effect(arg): VALEUR_CONSTANTE = arg |
2 | Bon | VALEUR_CONSTANTE = 5 def function_sans_side_effect(arg): arg = arg + VALEUR_CONSTANTE return arg |
3 | Mauvais | def function_multi_niveau(argument_1, argument_2): argument_1 = fait_quelque_chose_de_tres_complexe(argument1, argument2) argument_1 = argument_1 + argument_2 return argument_1 |
3 | Bon | def function_un_niveau_simple(argument_1, argument_2): argument_1 = argument_1 ** 2 argument_1 = argument_1 + argument_2 return argument_1 |
3 | Bon | def function_un_niveau_complexe(argument_1, argument_2): argument_1 = fait_quelque_chose_de_tres_complexe(argument_1) argument_1 = fait_une_autre_chose_complexe(argument_1, argument_2) return argument_1 |
4 | Mauvais | def nettoyer_les_dates(data): data = enlever_les_mauvaises_date(data) data = mettre_les_dates_dans_le_bon_format(data) data = calcule_moyenne_par_jour(data) data = calcule_moyenne_par_mois(data) return data |
4 | Bon | def nettoyer_les_dates(data): data = enlever_les_mauvaises_date(data) data = mettre_les_dates_dans_le_bon_format(data) return data def calculer_statistiques_par_jour data = calcule_moyenne_par_jour(data) data = calcule_moyenne_par_mois(data) return data |
Structure de l’article de journal
Dans un fichier de fonctions (ou le fichier d’une classe), on suggère de mettre les fonctions du plus haut niveau en haut du fichier, et les fonctions de plus bas niveau en bas du fichier. De cette manière, le lecteur pourra avoir accès à une vue d’ensemble en lisant le début du fichier, et pourra avoir accès à la complexité seulement s’il le désire. Le premier exemple de code ci-bas présente un bout de code qui obéit à ce principe, et le second un bout de code qui y désobéit.
Dans le premier exemple, la première fonction que l’on rencontre, nettoyer_les_donnees(data)
, est celle du plus haut niveau. On
rencontre ensuite les fonctions de plus bas niveau selon leur ordre d’apparition dans nettoyer_les_donnees(data)
. Dans le second exemple, les fonctions sont dans un ordre aléatoire. On voit
tout de suite qu’il est plus difficile de naviguer dans ce «fichier» de fonctions.
Exemple de code qui respecte la structure de l’article de journal
def nettoyer_les_donnees(data):
data = remplacer_les_NA_par_moyenne(data)
data = nettoyer_les_dates(data)
data = gerer_les_donnees_aberrantes(data)
return data
def remplacer_les_NA_par_moyenne(data):
[code]
def nettoyer_les_dates(data):
[code]
def gerer_les_donnees_aberrantes(data):
for colonne in data:
if colonne_contient_valeurs_aberrantes(colonne):
effacer_valeurs_abberantes(colonne)
def colonne_contient_valeurs_aberrantes(colonne):
[code]
def effacer_valeurs_abberantes(colonne):
[code]
Exemple de code qui NE respecte PAS la structure de l’article de journal
def colonne_contient_valeurs_aberrantes(colonne):
[code]
def effacer_valeurs_abberantes(colonne):
[code]
def remplacer_les_NA_par_moyenne(data):
[code]
def nettoyer_les_dates(data):
[code]
def nettoyer_les_donnees(data):
data = remplacer_les_NA_par_moyenne(data)
data = nettoyer_les_dates(data)
data = gerer_les_donnees_aberrantes(data)
return data
def gerer_les_donnees_aberrantes(data):
for colonne in data:
if colonne_contient_valeurs_aberrantes(colonne):
effacer_valeurs_abberantes(colonne)
Ce contenu a été mis à jour le 10 février 2023 à 15 h 41 min.