Avec Python, il est relativement facile de développer des programmes fonctionnels en peu de lignes. En revanche, pour des programmes plus complexes, il faut optimiser son code et le rendre le plus compréhensible possible. Nous allons voir dans cet article quelques astuces pour parvenir à cet objectif.
En Python, et dans tout autre langage, il ne s'agit pas de faire uniquement un code qui fonctionne. Il faut que le code soit lisible, efficace, et maintenable dans le temps.
Pour cela, il faut que le code se documente lui-même. C'est à dire que vous pouvez le comprendre simplement en le lisant. Prenons un exemple :
def mystery(n):
if n <= 1:
return n
else:
return(mystery(n-1) + mystery(n-2))
Est-ce que vous comprenez ce que fait cette fonction ? Je ne pense pas. L'objectif va donc être de réécrire cette fonction de sorte que tout le monde puisse comprendre ce qu'elle fait. Regardez la fonction suivante :
def calculate_n_term_fibonacci(n):
u0 = 0
u1 = 1
fibonaccis = [u0, u1]
for f in range(1, n):
fibonaccis.append(fibonaccis[-1] + fibonaccis[-2])
return fibonaccis[n]
Ceux qui ont fait un peu de maths comprendront assez facilement ce que fait cette fonction : elle calcule le n-ième terme de la suite de fibonacci. Mais qu'est-ce qui a changé pour que la fonction devienne plus compréhensible ?
Il faut toujours choisir des noms explicites, qui représentent l'objet qu'on manipule et qui permettent de comprendre diretement son utilité.
Pour une variable, il faut choisir un nom représentatif. Pour une fonction, il faut choisir un nom qui décrit ce qu'elle fait. Pour une classe, il faut choisir un nom qui représente le rôle de la classe.
Par exemple, imaginons une classe qui permet de gérer des dates comprenant des temps (ex: 01/01/2022 00:00). On pourrait l'appeler Date
, ce ne serait pas faux. Mais ce ne serait pas complet, le mieux est de l'appeler Datetime
car on comprend directement qu'elle gère à la fois la date et le temps.
Il faut essayer au maximum de résoudre les problèmes de la façon la plus logique et la plus intuitive possible. Si on reprend notre exemple plus haut, la première fonction résoud notre problème de façon récursive. Evidemment on ne pense normalement pas à ça en premier, la méthode la plus simple étant quasiment toujours la méthode itérative. C'est pour cela que quand vous avez lu le code, vous n'avez normalement pas compris ce que fait la fonction. En revanche dans le deuxième cas, c'est beaucoup plus intuitif et compréhensible.
Bien sûr cela ne s'applique pas qu'aux fonctions récursives, mais est extensible pour absolument tout en programmation : lorsque vous devez résoudre un problème, ne cherchez pas compliqué, résolvez le de la façon la plus simple possible et celle à laquelle tout le monde pense.
Finalement, le formattage du code est très important. Il faut rassembler les blocs qui vont ensemble, et les espacer de ceux qui ne sont pas liés. Cela n'est pas visible dans notre exemple, mais imaginons que vous déclarez 3-4 variables, et qu'ensuite vous effectuez plusieurs opérations avec. Vous allez séparer la déclaration des variables des opérations.
En bref, faites des blocs dans votre code, et ces blocs doivent rassembler les éléments liés entre eux.
Il faut essayer de diviser les éléments du code de la façon la plus granulaire possible (pour la programmation orientée objet en tout cas). C'est à dire qu'une classe ne doit avoir qu'une seule responsabilité et ne servir qu'à une chose, de même qu'une fonction ne doit faire qu'une seule chose.
Cela permet de comprendre directement l'utilité de chaque élément du code, et aussi d'être très flexible dans la maintenance et le développement car les modifications du code sont centralisées. Par exemple, si vous faites la même action plusieurs fois dans le code, il est bien de faire une fonction et de l'utiliser 3 fois, comme ça si vous souhaitez modifier les actions effectuées vous ne les modifiez qu'une seule fois plutôt que de le faire 3 fois.
Certains vous diront de commenter chaque bout de code. En Python, on utilise souvent les docstrings pour documenter chaque classe, chaque fonction; je ne vous recommande pas de faire ça. Il est mieux de faire un code qui se documente tout seul comme je l'explique plus haut que de faire un code pas clair et de tout expliquer avec des commentaires. Les commentaires utiles sont ceux qui donnent une information qui n'est pas trouvable dans le code, par exemple une prise de décision (pourquoi une solution récursive a-t-elle été choisie au lieu d'une solution itérative par exemple), l'explication d'un paramètre de fonction ambigü, ou même un commentaire TODO.
Retenez que le meilleur commentaire est celui que vous n'avez pas à écrire.
Bien, maintenant que nous savons comment écrire du bon code, il faut appliquer ceci au Python.
Sachez déjà que Python permet de nombreux raccourcis mais attention il faut es utiliser avec parcimonie car ils peuvent quelques fois diminuer la clarté du code. Vous connaissez déjà une grande partie de ces raccourcis, mais il faut penser à les utiliser en pratique, et les utiliser à bon escient. Un exemple :
a = 5
if a > 3:
return True
else:
return False
Ce code vous paraît bon ? Peut-être que si vous avez déjà fait du C, vous ne voyez pas de problème. En revanche, en Python on peut écrire directement :
a = 5
return a > 3
Maintenant, est-ce que ce code est plus clair ? Objectivement non, mais pour quelqu'un qui fait du Python c'est la syntaxe à préférer, et c'est celle que vous trouverez plus claire à force de vous entraîner. Il faut donc vous efforcer d'utiliser ce genre de raccourcis ; même s'ils paraissent moins clair, c'est en fait la syntaxe que tout le monde adopte et c'est la plus simple car la plus courte.
Autre exemple :
def x_is_greater_than_4(x):
return x > 4
Une telle fonction peut être utile si vous souhaitez filtrer une liste par exemple. Comment la réécrire de façon plus "Pythonic" ?
x_is_greater_than_4 = lambda x: x > 4
Encore une fois cela peut paraître moins clair, mais c'est la solution vers laquelle il faut s'orienter en Python.
Dernier exemple, cette méthode (qui fait partie d'une classe qu'on va nommer Datetime
) :
def get_actual_datetime(self):
return dt.datetime.now()
Est-ce que vous arrivez à trouver le problème ? Il y a un paramètre en trop, le self
. Mais on ne peut pas se débarrasser de ce paramètre car notre fonction est en fait une méthode d'instance. Ca pose un autre problème : on a besoin d'instancier un objet pour utiliser cette fonction, alors que ce n'est pas logique puisque cette fonction n'a aucun lien avec l'objet instancié. C'est pour cela qu'existent les méthodes statiques : elles permettent de dire que la méthode n'utilise pas d'instance. Pour cela on utilise simplement le décorateur @staticmethod
. La fonction devient donc :
@staticmethod
def get_actual_datetime():
return dt.datetime.now()
Et nous n'avons plus besoin du paramètre self
! Notre fonction s'utilisera donc de la façon suivante : Datetime.get_actual_datetime()
. Encore une fois, on peut se demander si c'est pertinent de mettre cette fonction ici dans cette classe en tant que méthode statique. Eh bien, ou la mettre si ce n'est là ? C'est totalement cohérent de la mettre dans cette classe.
Vous avez maintenant les bases pour commencer à faire du code clair et bien structuré. Je ferai un exercice prochainement qui vous permettra de pratiquer et de refacto un peu de code.
Si cet article vous a plus, n'hésitez pas à me faire un retour par commentaire ou par mail.
On se retrouve prochainement pour de nouvelles astuces !