Code propre A : de bons noms, de petites fonctions et une responsabilité unique
- Expliquer ce qu’est le Clean Code et pourquoi le code est lu bien plus souvent qu’il n’est écrit
- Choisir des noms clairs pour les variables, les fonctions et les fichiers selon les conventions de Python
- Reconnaître une fonction qui fait plus d’une chose et la découper
- Appliquer le principe de responsabilité unique au niveau des fonctions
Qu’allons-nous apprendre ?
Jusqu’à présent, le but était « que le code fonctionne ». À partir d’aujourd’hui, le but est aussi qu’il soit clair. Nous allons apprendre :
- Le nommage (Naming) — comment choisir des noms pour les variables, les fonctions et les fichiers.
- Les petites fonctions (Small Functions) — pourquoi les fonctions doivent être courtes.
- La responsabilité unique (Single Responsibility) — chaque fonction fait une seule chose.
Pourquoi en avons-nous besoin ?
Le code est lu bien plus souvent qu’il n’est écrit. Vous écrivez une fonction une fois, mais elle est lue à chaque correction de bug, à chaque nouvelle fonctionnalité et à chaque Code Review par un collègue. Et dans un mois, vous la lirez vous aussi comme un étranger.
Dans les entreprises, un code qui fonctionne mais qui n’est pas lisible est refusé en Code Review. Dans les entretiens d’embauche, on vérifie non seulement « avez-vous résolu le problème », mais aussi « la solution est-elle compréhensible ».
Comparez :
def f(l):
t = 0
for x in l:
t = t + x[1] * x[2]
return t * 1.17VAT_RATE = 1.17
def calculate_order_total(items):
subtotal = 0
for item in items:
subtotal = subtotal + item["price"] * item["quantity"]
return subtotal * VAT_RATELes deux fonctions font exactement la même chose. La seconde se comprend sans effort.
Explication à partir des bases
Qu’est-ce que le Clean Code ?
Le Clean Code (code propre) est un code qu’une autre personne peut lire, comprendre et modifier facilement, et en toute confiance. Ce n’est pas un ensemble de règles rigides, mais un ensemble d’habitudes qui rendent le code clair.
Le nommage — de bons noms
Un bon nom répond à la question « qu’est-ce que c’est ? » ou « que fait-il ? » sans avoir à lire le code qui l’entoure.
Les conventions de nommage de Python (selon PEP 8, le guide de style officiel) :
| Quoi | Style | Exemple |
|---|---|---|
| Variables et fonctions | snake_case | total_price, send_email() |
| Constantes (valeurs qui ne changent pas) | UPPER_SNAKE_CASE | VAT_RATE, MAX_RETRIES |
| Classes (le thème POO) | PascalCase | Student, BankAccount |
| Fichiers et modules | snake_case.py | order_utils.py |
Les règles d’un bon nom :
- Décrit l’intention —
days_until_examvaut mieux qued. - Fonction = verbe —
calculate_total(),send_invoice(),is_valid_email(). Variable = nom —total,invoice,email. - Un booléen sonne comme une question —
is_active,has_permission,can_edit. - Une liste au pluriel —
students(une liste),student(un seul élément). - Pas d’abréviations obscures —
customer, pascstmr. (Les abréviations connues commeid,url,maxsont acceptables.) - Pas de Magic Numbers — au lieu de
* 1.17dont personne ne connaît le sens, une constante nommée :VAT_RATE = 1.17.
Les petites fonctions
Une longue fonction (50 lignes et plus) est difficile à comprendre, difficile à tester et difficile à modifier. Règle empirique : si une fonction ne tient pas sur un écran, ou s’il est difficile de la nommer sans le mot « et », elle en fait probablement trop.
La responsabilité unique
Le principe de responsabilité unique (Single Responsibility Principle, SRP) : chaque fonction doit avoir une seule raison de changer, c’est-à-dire un seul rôle.
Un restaurant a un serveur, un cuisinier et un caissier. Si le cuisinier prend aussi les commandes, cuisine et encaisse, chaque changement (un nouveau menu, un nouveau lecteur de cartes) oblige à le « changer », et chacune de ses erreurs nuit à tout. Quand chacun a un seul rôle, il est facile de remplacer, d’améliorer et de trouver où est le problème.
Syntaxe
Il n’y a pas de « nouvelle syntaxe » pour le code propre. C’est le même Python, simplement organisé autrement. Le modèle :
CONSTANT_NAME = value # constants at the top, UPPER_SNAKE_CASE
def verb_noun(clear_parameter): # a function does ONE thing, named by what it does
...
return result
def main(): # one function that connects the small pieces
data = get_something()
result = process_something(data)
show_something(result)
main()Premier exemple de code
Avant — un code qui fonctionne mais qui n’est pas propre. week-2/grades_before.py :
def do(g):
s = 0
for x in g:
s = s + x
a = s / len(g)
if a >= 55:
print("avg", a, "pass")
else:
print("avg", a, "fail")
do([80, 45, 92, 60])Après — le même comportement, un code propre. week-2/grades_after.py :
PASSING_GRADE = 55
def calculate_average(grades):
total = 0
for grade in grades:
total = total + grade
return total / len(grades)
def is_passing(average):
return average >= PASSING_GRADE
def print_report(average):
if is_passing(average):
status = "pass"
else:
status = "fail"
print("Average:", average, "-", status)
student_grades = [80, 45, 92, 60]
average = calculate_average(student_grades)
print_report(average)Sortie (la version « après ». L’ancienne version affiche avg 69.25 pass, le même calcul formulé de façon moins claire) :
Average: 69.25 - passAnalyse du code ligne par ligne
| Dans l’ancien code | Dans le code propre | Pourquoi c’est mieux |
|---|---|---|
do | Trois fonctions avec des noms | Le nom dit ce que fait chacune. |
g, s, x, a | grades, total, grade, average | Pas besoin de « décoder ». |
55 au milieu du code | PASSING_GRADE = 55 | Une valeur avec un sens et un nom. Si la note change, on la modifie à un seul endroit. |
| Calcul + décision + affichage dans une seule fonction | calculate_average, is_passing, print_report | Chaque fonction fait une seule chose. |
L’avantage pratique : vous voulez afficher la moyenne sur un site web plutôt qu’à l’écran ? Seule print_report change. Vous voulez modifier la note de passage ? Seulement PASSING_GRADE. Et calculate_average est utile ailleurs aussi, car elle renvoie une valeur au lieu de l’afficher.
Un autre exemple
Une fonction « qui fait tout » dans un système d’inscription, et comment la découper. Avant :
def register(n, e, a):
if "@" not in e:
print("bad email")
return
if a < 16:
print("too young")
return
u = n.strip().lower().replace(" ", ".")
print("Welcome " + n + "! Your username is " + u)Après :
MIN_AGE = 16
def is_valid_email(email):
return "@" in email
def is_old_enough(age):
return age >= MIN_AGE
def create_username(full_name):
return full_name.strip().lower().replace(" ", ".")
def register_user(full_name, email, age):
if not is_valid_email(email):
print("Invalid email address")
return
if not is_old_enough(age):
print("You must be at least", MIN_AGE, "to register")
return
username = create_username(full_name)
print("Welcome " + full_name + "! Your username is " + username)
register_user("Dana Cohen", "dana@mail.com", 24)
register_user("Avi Levi", "avi-mail.com", 30)Sortie :
Welcome Dana Cohen! Your username is dana.cohen
Invalid email addressAnalyse :
"@" in email—invérifie si un morceau de texte apparaît dans un autre texte, et renvoieTrue/False..strip()— supprime les espaces au début et à la fin..lower()— met en minuscules..replace(" ", ".")— remplace les espaces par des points. Ce sont des méthodes destrque l’on peut enchaîner l’une après l’autre.returnsans valeur — quitte immédiatement la fonction (renvoieNone). Ainsi, on ne continue pas vers l’inscription après une erreur. Cette technique s’appelle Early Return (ou Guard Clause).register_user« se lit maintenant comme une histoire » : vérifier l’e-mail, vérifier l’âge, créer un nom d’utilisateur, saluer. Tous les détails sont dans de petites fonctions.
Erreur courante
Erreur 1 : des noms qui mentent.
def get_user(user_id):
user = find_in_list(user_id)
user["last_login"] = "today" # changes data!
delete_old_sessions(user) # deletes data!
return userLe nom dit « obtenir », mais la fonction modifie et supprime. Celui qui l’appelle pour « simplement obtenir un utilisateur » sera surpris. Un nom doit décrire tout ce que fait la fonction. Et s’il est difficile de la décrire sans « et », il y a plus d’une responsabilité.
Erreur 2 : le découpage excessif. L’inverse pose aussi problème :
def add_one(number):
return number + 1Une fonction d’une ligne qui cache une opération évidente ne fait qu’ajouter des sauts à la lecture. Découpez quand il y a une idée qui mérite un nom (is_old_enough), pas à chaque ligne.
Erreur 3 : des noms trop génériques. data, info, temp, result, manager, process(). Des noms comme ceux-là peuvent presque toujours être améliorés : data → student_records, process() → apply_discount().
Exercices
Les exercices n’utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d’abord seul, et n’ouvrez la solution qu’ensuite.
Exercice 1: Des noms qui s’expliquent d’eux-mêmes
Le code suivant fonctionne, mais il est impossible de comprendre ce qu’il fait :
def f(a, b):
c = a * b
if c > 500:
c = c - 50
return c
print(f(120, 5))
print(f(30, 2))Réécrivez-le avec de bons noms pour la fonction et les variables, et remplacez les nombres 500 et 50 par des constantes nommées. La sortie doit rester la même :
550
60Afficher la solution
DISCOUNT_THRESHOLD = 500
DISCOUNT_AMOUNT = 50
def calculate_order_price(unit_price, quantity):
price = unit_price * quantity
if price > DISCOUNT_THRESHOLD:
price = price - DISCOUNT_AMOUNT
return price
print(calculate_order_price(120, 5))
print(calculate_order_price(30, 2))550
60- Le nom de la fonction est un verbe qui décrit ce qu’elle fait.
500et50étaient des Magic Numbers — maintenant on sait ce qu’ils sont, et si la remise change, on la modifie à un seul endroit.
Exercice 2: Les conventions de nommage
Pour chaque nom — est-il écrit selon les conventions de Python ? Sinon, écrivez le nom correct.
TotalPrice (a variable)
numberofstudents (a variable)
max_grade (a variable)
vat_rate (a constant that never changes)
GetAverage (a function)Afficher la solution
| Nom | Correct ? | Nom correct |
|---|---|---|
TotalPrice | Non — les variables s’écrivent en snake_case | total_price |
numberofstudents | Non — difficile à lire sans tirets bas | number_of_students |
max_grade | Oui | — |
vat_rate | Non — les constantes s’écrivent en majuscules | VAT_RATE |
GetAverage | Non — les fonctions s’écrivent en snake_case, et un verbe est préférable | get_average / calculate_average |
Exercice 3: Une fonction — une responsabilité
La fonction suivante calcule une moyenne, décide si l’étudiant a réussi, et affiche aussi — trois rôles :
def report(name, grades):
total = 0
for g in grades:
total = total + g
avg = total / len(grades)
if avg >= 55:
print(name + ": average", avg, "- passed")
else:
print(name + ": average", avg, "- failed")
report("Dana", [80, 85])
report("Avi", [50, 46])Découpez-la en trois fonctions : calculate_average, is_passing et print_report(name, average). print_report reçoit une moyenne déjà calculée et ne fait qu’afficher. La sortie doit rester :
Dana: average 82.5 - passed
Avi: average 48.0 - failedAfficher la solution
PASSING_GRADE = 55
def calculate_average(grades):
return sum(grades) / len(grades)
def is_passing(average):
return average >= PASSING_GRADE
def print_report(name, average):
if is_passing(average):
status = "passed"
else:
status = "failed"
print(name + ": average", average, "-", status)
dana_average = calculate_average([80, 85])
print_report("Dana", dana_average)
avi_average = calculate_average([50, 46])
print_report("Avi", avi_average)Dana: average 82.5 - passed
Avi: average 48.0 - failedcalculate_averagene fait que calculer,is_passingne fait que décider, etprint_reportreçoit une moyenne prête et ne fait que l’afficher. Le calcul a lieu en dehors d’elle.- Si
print_reportappelait elle-mêmecalculate_average, elle calculerait et afficherait de nouveau. Le code est plus court, mais les responsabilités ne sont pas séparées. - Maintenant,
calculate_averagepeut aussi servir à un endroit qui n’a pas besoin d’afficher.
Exercice 4: Pas de nombres magiques
Dans une boutique en ligne, la fonction suivante reçoit le total de la commande et renvoie le montant à payer :
def final_price(total):
if total < 200:
total = total + 30
return total
print(final_price(150))
print(final_price(250))- Que signifient 200 et 30 dans la boutique ? (Indice : que se passe-t-il pour une petite commande ?) Donnez-leur des noms de constantes.
- Réécrivez la fonction pour que son nom explique exactement ce qu’elle ajoute.
Résultat attendu :
180
250Afficher la solution
FREE_SHIPPING_MINIMUM = 200
SHIPPING_FEE = 30
def add_shipping(order_total):
if order_total < FREE_SHIPPING_MINIMUM:
return order_total + SHIPPING_FEE
return order_total
print(add_shipping(150))
print(add_shipping(250))180
250200 est le montant minimum pour la livraison gratuite, et 30 est les frais de livraison. Avec des noms, personne n’a besoin de deviner.
Exercice 5: Une petite fonction qui répond à une seule question
Écrivez une fonction is_valid_password(password) qui renvoie True si le mot de passe fait au moins 8 caractères et contient au moins un chiffre, sinon False. La fonction n’affiche rien — elle se contente de renvoyer.
print(is_valid_password("abc12345"))
print(is_valid_password("abcdefgh"))
print(is_valid_password("a1"))Résultat attendu :
True
False
FalseAfficher la solution
MIN_PASSWORD_LENGTH = 8
def has_digit(text):
for char in text:
if char.isdigit():
return True
return False
def is_valid_password(password):
return len(password) >= MIN_PASSWORD_LENGTH and has_digit(password)
print(is_valid_password("abc12345"))
print(is_valid_password("abcdefgh"))
print(is_valid_password("a1"))True
False
FalseLe nom is_valid_password commence par is_ — il est clair qu’il renvoie True ou False. La vérification du chiffre a été séparée dans une petite fonction à part.
Résumé
- Clean Code = un code que les autres (et vous dans un mois) comprennent et modifient facilement. Le code est lu bien plus souvent qu’il n’est écrit.
- Noms :
snake_casepour les variables et les fonctions,UPPER_SNAKE_CASEpour les constantes. Fonction = verbe, booléen = question (is_...). - Pas de nombres magiques. Une valeur qui a un sens reçoit une constante nommée.
- De petites fonctions avec une responsabilité unique (SRP). Si le nom a besoin de « et », il faut probablement découper.
- Une fonction qui calcule renvoie. L’affichage est une responsabilité séparée.