Bonnes pratiques de programmation ================================= Lorsque l'on programme, on passe un certain temps à écrire du code, mais on passe beaucoup plus de temps à **lire** du code que soi, ou d'autres, ont écrit. On peut facilement faire l'analogie entre l'écriture du code et l'écriture d'un texte. Lorsque le texte est mal écrit, qu'il contient des fautes d'orthographe, que les phrases sont mal structurées et que les idées ne sont pas organisées, ce texte est très difficile à lire et donc à comprendre. Il en va de même pour le code : un code brouillon est très difficile et fatiguant à comprendre... de plus, les bugs s'y cachent beaucoup plus facilement. Observez par exemple le code ci-dessous : .. code-block:: python def k(i: int) -> int: # rxkfghh = rxkfgh2 sauf si i = 1 rxkfghh="gh" if i==1: p=7 a=p+2 rxkfgh=1 rxkfghh="temporaire" print(rxkfghh, rxkfgh) else: rxkfghh2="tempoaussi" rxkfgh = i*k(i-1) print(rxkfghh, rxkfgh) return rxkfgh Reconnaissez-vous cet algorithme ? C'est celui de factorielle. Ce serait plus simple à comprendre si l'algorithme était bien écrit non ? Il est donc très important de respecter certaines bonnes pratiques de programmation (qui s'appliquent naturellement à la rédaction d'algorithmes) pour rendre le plus agréable possible la lecture de code. Ce chapitre n'a pas vocation à vous enseigner toutes les bonnes pratiques de programmation... vous aurez un cous à ce sujet au second semestre. Néanmoins, il présente quelques bonnes habitudes que vous devez prendre dès le début. Interface vs. implémentation d'un algorithme ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dans la première partie de ce cours, nous avons parlé à plusieurs reprises de "contrat" ou "spécification formelle". Le contrat caractérise l'**interface** d'un algorithme, c'est-à-dire qu'il explique le plus clairement possible ce que l'algorithme est capable de produire comme sorties étant donné ce qu'on lui fournit en entrée. Les pré-conditions permettent de préciser sous quelles conditions l'algorithme sera capable de fonctionner et les post-conditions nous renseignent sur ce que l'on peut s'attendre à obtenir comme résultats. Par conséquent, lorsqu'on lit un contrat, i.e. que l'on consulte l'interface d'un algorithme, on est renseigné sur ce à quoi on peut s'attendre, ainsi que sur les limites de l'algorithme que l'on va utiliser, sans pour autant avoir besoin de comprendre comment l'algorithme est implémenté. Il nous appartient de composer avec ces informations. Par exemple, quand vous utilisez ``range(0,4)`` en Python, vous savez que vous allez obtenir une liste de 4 éléments de 0 à 3, mais vous ne savez pas comment Python procède pour créer cette liste. Le contrat permet donc de savoir très exactement ce que l'algorithme est capable de faire, mais il ne dit rien sur **comment** l'algorithme va s'y prendre pour résoudre le problème. Le reste de ce chapitre porte sur les bonnes pratiques de programmation pour coder des algorithmes, donc sur le **comment**. Qu'est-ce qui caractérise un code "bien écrit" ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Un algorithme, ou code "bien écrit" doit avoir les propriétés suivantes : * Être facile à lire, pas soi-même mais aussi par les autres. * Avoir une organisation logique et évidente. * Être explicite, montrer clairement les intentions du développeur. * Être soigné et robuste au temps qui passe. Nous allons regarder un peu plus en détails chacune de ces caractéristiques. La code doit être facile à lire ------------------------------- Pour que le code soit facile à lire, il faut d'une part qu'il soit bien structuré et bien présenté, et d'autre part, que les noms des variables et des fonctions soient choisis avec soin. Pour ce qui est de la structure et de la présentation, Python nous aide beaucoup car le langage impose beaucoup de choses qui nous "forcent" à bien présenter notre code. Par exemple, les blocs d'instructions du même niveau doivent être précédés du même nombre d'espaces, ce qui nous conduit naturellement à bien **indenter** notre code. Dans d'autres langages, plus de libertés de présentation sont offertes au développeur, par conséquent, il convient de se forcer à respecter des conventions pour écrire le code le plus propre possible. Par exemple, regardez le code Java ci-dessous. Il est évident que le manque d'indentation ne facilite pas la lecture et la compréhension du code, n'est-ce pas ? .. code-block:: java int i,j,k, m, n, o = 0; n = 9; o=13; int [][] p = new int [n][o]; for(i=0;i 10: continuer = False C'est bien joli tout ça, mais coder proprement ça prend du temps ! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C'est faux ! Il ne faut pas confondre vitesse et précipitation. On a souvent tendance à penser que l'on perd énormément de temps à soigner son code, à le structurer correctement, à le réorganiser et à le documenter, mais c'est faux. Au contraire, on gagne du temps à faire tout cela. Voici quelques explications pour vous en convaincre : * Si vous adoptez les bonnes pratiques dès le début, vous faites déjà 50% du travail. * Si le code est bien écrit, il est plus facile, et donc plus rapide à relire, et n'oubliez pas que vous passez plus de temps à lire votre code qu'à l'écrire... donc quand votre code est propre, vous vous faites gagner du temps. * Si le code est logique et bien structuré, il sera plus facile de retrouver les bugs qu'il contient, et donc de l'améliorer.. Ce sont donc autant de raisons qui devraient vous convaincre qu'il est important d'être organisé, clair, méthodique et rigoureux quand vous développez. De l'importance des commentaires ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Les commentaires sont essentiels pour "éclairer" le code. Un commentaire est un texte qui est ignoré par l'ordinateur lorsqu'il exécute le programme, mais qui peut être lu par le développeur lorsqu'il lit le programme. En Python, une ligne qui débute par le signe ``#`` est un commentaire. On peut aussi faire des blocs de commentaires, sur plusieurs lignes, en utilisant les triples guillemets. Bien que les commentaires soient essentiels, il ne faut pas en abuser. Un bon commentaire peut : * Faciliter la lecture du code, * Apporter une indication sur un choix de conception, * Expliquer une motivation qui ne serait pas évidente (comme dans l'exemple de la matrice triangulaire vu plus haut) * Donner un exemple pour permettre de mieux comprendre ce que fait le code. Quelques exemples de mauvais commentaires : * Un commentaire qui décrit un morceau de code qui n'existe plus, * Un commentaire qui explique une évidence, * Un commentaire sur plusieurs lignes pour expliquer une chose simple. * Un commentaire sur l'historique des modifications d'un fichier. C'est parfois utile, mais dans la plupart des cas, il vaut mieux confier cette tâche à votre gestionnaire de versions qui fera le travail pour vous. Vous trouverez ci-dessous quelques exemples de mauvais commentaires : .. code-block:: python i = 0 #initialisation de la variable i à 0 i = i + 1 # incrémentation de la variable i # Additionne a et b et stocke le résultat dans c c = a + b # Ci-dessous une double boucle pour afficher un tableau for i in range(10): print("Valeur ", i) # fin du for # Et maintenant, on va s'occuper de retourner la valeur de i # Pour cela, on utilise le mot clé return # Et on passe ensuite la valeur de i return i Comme vous le voyez dans l'exemple ci-dessus, il n'est pas judicieux d'utiliser un commentaire pour signaler la fin d'un bloc d'instructions. En général, si vous avez besoin de ce type de commentaire, c'est que votre code est déjà trop long. Dans ce cas, demandez-vous si vous ne pouvez pas découper votre code en fragments plus simples. .. index:: refactorer Attention : les commentaires ne doivent pas pallier un manque de clarté du code. Si vous avez besoin de commentaires pour cela, c'est probablement que vous pouvez améliorer votre code pour le rendre plus lisible. Essayez donc de le **refactorer**, c'est-à-dire de le ré-écrire, au moins partiellement, en l'améliorant. Comme nous l'avons dit plus haut, les commentaires, tout comme le code, doivent être maintenus, c'est-à-dire qu'ils doivent évoluer avec le code, et disparaître si le code disparaît. Par conséquent, il faut veiller au bon dosage de vos commentaires, de sorte à ne pas alourdir inutilement votre travail de maintenance. .. index:: doctest Enfin, une bonne pratique est d'utiliser des outils tels que doctest qui permettent d'écrire des petits tests pour vos fonctions tout en les documentant. Les avantages de cette pratique sont d'une part que cela vous "force" à tester votre code, et qu'il permet en même temps d'en avoir une documentation "par l'exemple" qui sera forcément cohérente avec le code et à jour (sinon, les tests ne fonctionneraient pas). Pour en savoir plus, n'hésitez pas à consulter la documentation de doctest pour Python, https://docs.python.org/3.5/library/doctest.html. Comment nommer les choses ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Les noms que vous choisissez pour vos variables et vos fonctions vont grandement contribuer à la lisibilité de votre code. Par exemple, vous conviendrez que le bloc de code suivant n'est pas très lisible tandis que celui d'après, qui fait exactement la même chose, est plus clair. .. code-block:: python xretyers = 4 eijfzeipfjzpeij = 1 xretyers = eijfzeipfjzpeij + xretyers .. code-block:: python x = 4 x += 1 La première règle est donc de choisir des noms de variables prononçables et faciles à retenir. Vous devez choisir des noms de variables explicites pour vous mais aussi pour les autres. Par exemple, ``a`` est bien moins explicite que ``adresseClient``. De même, ``lf`` est moins explicite que ``largeurFenetre``. Pensez également que vous serez probablement amenés à chercher vos variables dans votre code avec un outil de recherche. À votre avis, combien d'occurrences de ``a`` allez vous trouver ? Et combien d'occurrences de ``adresseClient``\ ? Évitez également de choisir des noms de variables qui induisent un contre-sense. Par exemple, si vous écrivez ``matrice=8``, on pourrait penser que la variable est une matrice, or, il s'agit clairement d'un entier. Au moment de l'affectation, il est facile de se rendre compte du type de la variable, mais maintenant, imaginez que vous rencontriez, au beau milieu du code, la ligne suivante : .. code-block:: python matrice = matrice * 4 Comment allez vous interpréter cette instruction ? Évitez également les noms de variable qui n'ont pas de sens, comme ``plop``, surtout si vous en utilisez plusieurs dans la même portion de code. Vous risquez de vous perdre dans les noms et donc d'introduire inutilement des bugs dans votre code. Ne trichez pas non plus quand vous choisissez vos noms de variables. Dans la plupart des langages de programmation, certains mots sont "réservés" car ce sont des instructions du langage. Par exemple, en Python, on ne peut pas nommer une variable ``if`` ou ``list`` car ce sont des mots réservés du langage. Si vous pensez avoir besoin de ces mots pour nommer vos variables, ne trichez pas en écrivant ``iff`` ou ``llist``, vous risqueriez de vous perdre dans vos propres astuces. La plupart des langages imposent des règles pour le choix des noms de variables. Par exemple, en Python, un nom de variable ne peut pas commencer par un chiffre (il y a d'autres règles). En plus de ces règles imposées par les langages, essayez de ne pas utiliser des caractères qui pourraient porter à confusion. En particulier, les caractères ``o 0 O l i et 1`` peuvent se ressembler fortement, voire être identiques en fonction de la police de caractères utilisée pour l'affichage à l'écran. Enfin, essayez d'être cohérents lorsque vous choisissez vos noms de variables. Par exemple, si vous décidez d'utiliser le français pour nommer vos variables, utilisez le français tout du long, n'alternez pas avec l'anglais. ``longueurPath`` ou ``lenghtChemin`` sont des noms pour le moins curieux ! À propos des environnements de développement ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vous avez à votre disposition des environnements de développement. Pensez à les utiliser et apprenez à exploiter leurs nombreuses possibilités. La plupart de ces environnements vous offrent des outils de *refactoring* qui vous permettent de réorganiser votre code facilement tout en garantissant une certaine cohérence. Par exemple, ils vous permettent de renommer une variable partout dans le code. C'est l'occasion de changer tous vos ``plop`` par des noms plus significatifs. En général, ces outils vous permettent également de vérifier que votre code est correctement indenté, et qu'il respecte des conventions de programmation préétablies et matérialisées par des règles. Vous **devez** apprendre à les utiliser, cela vous fera gagner beaucoup de temps. Ces outils vous offrent également des fonctionnalités d'auto-complétion (c'est-à-dire qu'ils complètent automatiquement le texte que vous êtes en train de taper). Ce sont des fonctionnalités très pratiques qu'il faut également apprendre à maîtriser. Grâce à ces fonctionnalités, vous n'avez plus d'excuses : inutile de choisir des noms de variables courts (type ``a b c``) sous prétexte que c'est plus rapide à taper... .. rubric:: Référence La plupart des idées de ce cours proviennent de l’ouvrage formidable "Coder proprement". Robert C. Martin. Pearson Education France, 2009.