Un projet Python à l’épreuve de la qualité

Emmanuelle R. et Gaëtan L.

 

21/01/2020

  • Introduction
  • 1- Finalités de la qualité
  • 2- Le pythonique
  • 3- Méthodes et orientations en programmation
  • 4- Moyens pour y parvenir : au fond
  • 5- Moyens pour y parvenir : dans la forme
  • 6- Quand ça bloque
  • 7- Liste d'outils pour bien développer
  • Autres liens

 

Sommaire

"La perfection est atteinte, non pas lorsqu'il n'y a plus rien à ajouter, mais lorsqu'il n'y a plus rien à retirer.

Saint Exupery

 

 

 

 

Introduction

l'idée de "sport collectif", de discipline :

  • en cas d’oubli du développeur actuel
  • communication dans l'équipe.

Pourquoi ce sujet ?

D'un côté, il est important d'apprendre à respecter la règle du jeu, càd des normes de développement : dans les méthodes, au fond, dans la forme.

D'un autre côté, le Python, par son mode communautaire, entretient un rapport spécifique à la qualité : le pythonique.

La simplicité est principe à la base de nombreuses règles de qualité, et Python notamment en Python.

van der Rohe : directeur du Bauhaus (or en informatique aussi on parle d'architecture…)

La qualité est là pour établir une règle du jeu :

  • entre les réalisateurs d'un programme
  • et entre un programme et son utilisateur

Mais la simplicité n'est pas un garant suffisant

a=[0]*3

#nom de variable ésotérique
#objet délicat à  manipuler
#proposition 1
a = 4
z = 5
def az(a):  #MOCHE ET BUGUÉ :
	a+=z
az(z)
print(a)

4


#proposition 2
nombre = 4
augm = 5
def incrementer_valeur(valeur):  #MIEUX :
	"""modifies the parameter $valeur and returns it"""
	return valeur + augm
nombre = incrementer_valeur(nombre)
print(nombre)

9

1- Finalités de la qualité

Les finalités de la qualité d'un programme :

 

  • produire un résultat prévisible, toujours identique à paramètres identiques, et qui anticipe toutes les combinaisons des paramètres
  • pouvoir être repris sans peine après une phase d'arrêt dans son développement
  • pouvoir évoluer en fonction de spécifications nouvelles

 

Ces finalités  mettent en relation des lignes de code avec un contexte : un client, un utilisateur, d'autres programmeurs.

 

Le programmeur semble seul face à son code - et il en tire parfois une certaine jouissance. En fait, ce qu'il produit est un objet destiné à entrer dans un réseau de relations, et il doit en tenir compte ! dans une perspective temporelle : la dette technique doit être minimisée

Côté code, la qualité est un impératif qui pèse dans le succès - et le coût potentiel - d'une application.

2- Le pythonique

« Pythonique » ?

 

Code Python bien conçu, un code idiomatique (en accord avec les règles d’usage du langage, et donc compréhensible par tout développeur).

 

  • Zen of Python
  • KISS
  • Les mécanismes du language
  • Biblioteque standard

PEP20 : le Zen de Python                                              

 

  • Le beau est préférable au laid.
  • L’explicite est préférable à l’implicite.
  • Le simple est préférable au complexe.
  • Le complexe est préférable au compliqué.
  • Le plat est préférable à l’imbriqué.
  • L’aéré est préférable au dense.
  • La lisibilité compte.
  • Les erreurs ne devraient jamais se produire silencieusement.
  • À moins d’être explicitement tues.
  • En cas d’ambiguïté, résister à la tentation de deviner.
  • Il devrait y avoir une – et de préférence une seule – manière évidente de le faire.
  • Maintenant est préférable à jamais.
  • Bien que jamais soit souvent préférable à tout de suite.
  • Si l’implémentation est difficile à expliquer, c’est une mauvaise idée.
  • Les espaces de noms sont une sacrée bonne idée – utilisons-les plus souvent !
  • (Et la vingtième)

20 règles Ecrite par Tim Peters. Celle-ci énonce les règles suivant un poème. On peut la retrouver via l’instruction import this dans un interpréteur Python.

class User():
    def __init__(self, name):
        self.name = name

class SecureUser(User):
    def __init__(self, name, password):
        super().__init__(name)
        self.password = password

Explicit is better than implicit.

L’explicite est préférable à l’implicite.

 

Lire un code Python sans se demander sans cesse ce que fait telle ou telle ligne. Utiliser des noms et des constructions explicites.

Par exemple, en programmation objet, lors d’un héritage et de la surcharge de la méthode d’initialisation (__init__), il convient d’appeler explicitement la méthode de la classe parente. Cela ne sera jamais fait automatiquement dans le dos du développeur, afin d’avoir la main sur le comportement voulu.

There should be one – and preferably only one – obvious way to do it.

Il devrait y avoir une – et de préférence une seule – manière évidente de le faire.

 

Python prône le fait qu’il existe toujours une manière optimale de procéder, et donc que toutes ne se valent pas. Celle-ci est préférable car évidente.

La manière évidente d’itérer sur des nombres est par exemple d’utiliser une boucle for.

for i in range(100):
    print(i)

Keep it simple, stupid (KISS)

Il est inutile de créer de nouvelles classes trop vite.

Par exemple, pour un objet qui ne contiendrait que des données, associées à aucune méthode, un dictionnaire suffit:

user = {'username': 'Giles', 'realname': 'Giles Dupont', 'password': '12345'}

= Regrouper dans des fonctions, une action en une seule.

Chaque fonctions doit avoir une action a la fois, et c’est l'enchaînement des fonctions qui produit la fonctionnalité finale.

You ain’t gonna need it (YAGNI)

Tu n’en auras pas besoin

Code composable:

Les mécanismes du langage:

L'unpacking (ou deconstruction)

a = 5
b = 2
a, b = b, a
print(a, b)
2 5

Technique qui permet l’assignation de plusieurs variables en une seule instruction.

Ce qui se passe en interne lors de la 3ème ligne est la création d’un tuple (b, a), qui est ensuite déconstruit et son contenu stocké dans les variables a et b.

 

Conditions

L'unpacking (ou deconstruction)

Toute valeur en Python peut s’évaluer sous forme d’un booléen, sans conversion.

  • Les valeurs None, 0 et le conteneurs vides donnent False
  • Les autres nombres, les conteneurs non vides, valeur non fausse donnent à True.
if ma_liste:
    print("non vide")

La bibliothèque standard

Les fonctions Built-in:

Voir la liste exhaustive sur : Built-in Functions — Python 3.8.1 documentation

ex: min(), max(), sum(), sorted(), etc.

 

print('{} + {} = {}'.format(2, 3, 2 + 3))

.format():

.zip()

x = [1, 2, 3]
y = [4, 5, 6]
zipped = zip(x, y)

Un exemple:

  • Le bloc with open() as marque implicitement la fermeture du fichier, pas de risque d'oublier son close()
  • Utiliser les fonctions max() et min() plutot que de les déterminer à l'ancienne
  • la fonc enumerate() renvoit à la fois le numéro de la ligne (à partir de 0) et la ligne elle même, plus besoin de tenir un compteur à jour
  • ne pas tenir manuellement un compteur d'itération pour connaitre notre position dans une liste, mais lui préférer l'utilisation d'un générateur.
with open(pythonique, 'r') as f:
      
      for i in enumerate(f):
         longueur = abc.split(',')
       
      for i in mylist:
      # et non : for i in len(mylist) /
      # + increment

3- Méthodes et orientations en programmation

Différents paradigmes de programmation ont un impact

sur la qualité du code :

  • la programmation orienté objet. Comme on l'a vu avec le Pythonique, il faut utiliser les méthodes des objets que l'on manipule. La possibilité de créer des instances offre un cadre régulier de programmation censément facile à appréhender par des programmeurs nouveaux venus sur le projet.
    Cela  ne revient pas à répudier la programmation procédurale, qui reste adaptée à des cas tels que par ex., un programme court à usage unique et circonstancié.
  • la programmation fonctionnelle : issue de travaux mathématiques (d'où par ailleurs les fonctions lambda). On a évoqué dans la partie précédente le code composable (5.6). La programmation fonctionnelle intègre ce principe et a l'avantage de poser un cadre où la valeur des variables est sous contrôle. Les fonctions ne modifient pas les variables, mais renvoient une valeur modifiée. Cela renforce la traçabilité des variables et évite les mauvaises surprises (les "effets de bord"). Pour cela, l'idéal est une fonction n'acceptant qu'une valeur en entrée (le paramètre), et ne retournant que la nouvelle valeur. Cf. les décorateurs : le paramètre peut lui même être une fonction.

POO : tout le monde est d'accord sur les méthodes

et sur les propriétés des objets : en Python, tout est objet

On l'a vu en introduction, parallèle possible avec le design, qui implique aussi un collectif.

=> en programmation : les design patterns

 

Programmation fonctionnelle : tout le monde est d'accord sur les variables, standardisation de la forme des fonctions

 

Dans un cadre professionnel, des méthodes renforcent aussi la qualité des spécifications, des livrables : l'agilité par exemple.

Pas d'assez d'expérience en ce domaine pour l'évoquer plus, et cela nous écarterait du sujet de l'approfondir alors que nous l'aborderons prochainement. Rappelons justes deux des maximes "professionnelles" de la PEP 20  : "Maintenant est préférable à jamais.",  "Bien que jamais soit souvent préférable à tout de suite."
Eviter l'urgence ne signifie pas procrastiner => maintenant > jamais > tout de suite

L'encadrement formel de la production de code par les tests

 

les tests unitaires (qui s'appliquent à une partie précise d'une application)

 

le TDD par ex. : test driven development

le développement guidé par les tests :

le fait que l'écriture du code soit soumise aux contraintes du test produit un code qui répond strictement aux besoins, qui est plus concis, qui évite les régressions…

 

Là aussi, temps et expérience manquent encore

pour en parler plus et mieux

mais les tests sont intrinsèquement partie prenante de la qualité

4- Moyens pour y parvenir : au fond

5- Moyens pour y parvenir : dans la forme

La PEP8:

 

Style Guide for Python Code: une des plus anciennes

Le site pep8online permet de vérifier si son code respecte ce guide.

L'indentation, tabulations ou espaces

if True:
	print("True")
         print("False")

  • 4 niveaux d'indentation
  • Ne pas mélanger espace et Tab.

Longueur maximum d'une ligne

  • 80/79 caracteres max.
  • 72 pour les docstrings
  • Si vous devez découper une ligne trop longue, faites la césure après l'opérateur, pas avant.
Un_long_calcul = variable + \
		taux * 100	

Traditionnellement les terminaux des années 80 etaient limités à 80 colonnes et 24 lignes. Standard VT100:

1 ligne == 20 tabulations == 80 espaces

Parenthèse culture générale :

Directives d'importation

import os
import math
# et non
import os, math
from random import randomint
# et surtout pas:
from random import *

sauf :

Dans l'ordre :

  • les directives d'importation faisant référence à la bibliothèque standard ;

  • les directives d'importation faisant référence à des bibliothèques tierces ;

  • les directives d'importation faisant référence à des modules de votre projet.

Le signe espace dans les expressions et instructions

pas d'espace :

  •  parenthèses, crochets et accolades :
  • virgule ou signe deux points :
  • Avant la parenthèse ouvrante pour les paramètres :
  • entourer les opérateurs d'un espace  - sauf dans un paramètre :
# Oui
    spam(salade[1], {oeufs: 2})
# Non
    spam( salade[ 1 ], { oeuf: 2 } )
# Oui
    spam(1)
# Non
    spam (1)
# Oui
    if x == 4: print x, y; x, y = y, x
# Non
    if x == 4 : print x , y ; x , y = y , x
def fonction(parametre=5):
	i = i + 1
# NON
def fonction (parametre = 5):
   	i=i+1
  

La PEP 257 : de belles documentations

https://www.python.org/dev/peps/pep-0257/

Cette chaîne de caractères devient l'attribut spécial__doc__de l'objet:

fonction.__doc__
'Documentation de la fonction.'
def fonction():
    """Documentation brève sur une ligne.
    
    Documentation plus longue...
    
    """

En bref:

  • Le BDFL ( Benevolent Dictator For Life) conseille de sauter une ligne avant de fermer les docstrings, si sur plusieurs lignes.

  • On garde la syntaxe Majuscule/point a la fin.
  • Concerne les fonctions, les méthodes, les modules, les classes

 

Conventions de nommage

  • Noms à éviter: l(L minuscule),O(o majuscule) etI(i majuscule).
  • Noms des modules et packages: noms courts en minuscules. ' _ ' ok mais la PEP 8 déconseille.
  • Noms de classes: CamelCase
  • Noms d'exception: Les exceptions sont des classes, conventionnés:  'Error'
  • Fonctions et méthodes: nom_de_fonction
  • Constantes: NOM_DE_MA_CONSTANTE

Astuce: Depuis python 3.8.1, Il existe un moyen de faire des Constantes avec python:

from typing import Final
MA_CONSTANTE: Final = 1

https://docs.python.org/3/library/typing.html

6- Quand ça bloque

7- Liste d'outils pour bien développer

Autres liens

404 Un projet Python à l’épreuve de la qualité

By G. Lghd

404 Un projet Python à l’épreuve de la qualité

sujet de veille pour la formation CDA Python à Simplon

  • 85