gwift-book/chapters/maintenability.tex

137 lines
6.0 KiB
TeX

\chapter{Fiabilité, évolutivité et maintenabilité}
\begin{quote}
The primary cost of maintenance is in spelunking and risk
\cite[139]{clean_architecture}
--- Robert C. Martin
\end{quote}
\section{Poésie de la programmation}
\begin{quote}
La poésie est "l'art d'évoquer et de suggérer les sensations, les impressions, les émotions les plus vives par l'union intense des sons, des rythmes, des harmonies, en particulier par les vers."
-- https://www.larousse.fr/dictionnaires/francais/po%C3%A9sie/61960
\end{quote}
Sans aller jusqu'à demander de développer vos algorithmes sur douze pieds, la programmation reste un art régit par un ensemble de bonnes pratiques, par des règles à respecter et par la nécessité de travailler avec d'autres personnes qui ont \sout{parfois} souvent une expérience, des compétences ou une approche différente.
\section{Tests unitaires et d'intégration}
\begin{quote}
Tests are part of the system.
You can think of tests as the outermost circle in the architecture.
Nothing within in the system depends on the tests, and the tests always depend inward on the components of the system.
-- Robert C. Martin, Clean Architecture
\end{quote}
\section{Complexité cyclomatique\index{McCabe}}
La \href{https://fr.wikipedia.org/wiki/Nombre_cyclomatique}{complexité cyclomatique} (ou complexité de McCabe) peut s'apparenter à mesure de difficulté de compréhension du code, en fonction du nombre d'embranchements trouvés dans une même section.
Quand le cycle d'exécution du code rencontre une condition, cette condition peut être évalue à VRAI ou à FAUX.
L'exécution du code dispose donc de deux embranchements, correspondant chacun à un résultat de cette condition.
Le code suivant \autoref{cyclomatic-simple-code} a une complexité cyclomatique 1; il s'agit du cas le plus simple que nous pouvons implémenter: l'exécution du code rentre dans la fonction (il y a un seul embranchement), et aucun bloc conditionnel n'est présent sur son chemin.
La complexité reste de 1.
\begin{listing}[!hbpt]
\begin{minted}{Python}
from datetime import date
def print_current_date():
print(date.today())
\end{minted}
\caption{Une version ultra-simple de l'affichage du jour de la semaine}
\label{cyclomatic-simple-code}
\end{listing}
Si nous complexifions cette fonction en vérifiant (par exemple) le jour de la semaine, nous aurons notre embranchement initial (l'impression à l'écran de la date du jour), mais également un second embranchement qui vérifiera si cette date correspond à un lundi:
\begin{listing}[!h]
\begin{minted}{Python}
from datetime import date
def print_current_date_if_monday():
if date.today().weekday() == 0:
print("Aujourd'hui, c'est lundi!")
print(date.today())
\end{minted}
\caption{Ajout d'une fonctionnalité essentielle et totalement indispensable}
\end{listing}
La complexité cyclomatique d'un bloc est évaluée sur base du nombre d'embranchements possibles; par défaut, sa valeur est de 1.
Si nous rencontrons une condition, elle passera à 2, etc.
Cette complexité est liée à deux concepts:
\begin{itemize}
\item \textbf{La lisibilité du code}: au plus la complexité cyclomatique sera élevée, au plus le code sera compliqué à comprendre en première instance. Il sera composé de plusieurs conditions, éventuellement imbriquées, il débordera probablement de la hauteur que votre écran sera capable d'afficher
\item \textbf{Les tests unitaires}: pour nous assurer d'une couverture de code correcte, il sera nécessaire de couvrir tous les embranchements présentés. Connaître la complexité permet de savoir combien de tests devront être écrits pour assurer une couverture complète de tous les cas pouvant se présenter.
\end{itemize}
\subsection{Lisibilité du code}
Il est important de noter que refactoriser un bloc, par exemple en extrayant une méthode, n'améliore pas la complexité cyclomatique globale de l'application.
L'amélioration que nous visons ici est une amélioration \textbf{locale}, qui facilite la lecture d'un bloc spécifique, et pas d'un programme complet.
"Améliorons" notre code ci-dessous, pour lui ajouter la possibilité de gérer les autres jours de la semaine:
\begin{listing}[!ht]
\begin{minted}{Python}
from datetime import date
def print_current_date():
if date.today().weekday() == 0:
print("Lundi")
elif date.today().weekday() == 1:
print("Mardi")
elif date.today().weekday() == 2:
print("Mercredi")
elif date.today().weekday() == 3:
print("Jeudi")
elif date.today().weekday() == 4:
print("Vendredi")
elif date.today().weekday() == 5:
print("Samedi")
elif date.today().weekday() == 6:
print("Dimanche")
print(date.today())
\end{minted}
\caption{Un code un peu nul avec une complexité cyclomatique qui l'est tout autant}
\label{Impression du jour de la semaine, version naïve}
\end{listing}
La complexité de ce code est évaluée à 8, même si la complexité effective ne sera que de 7.
Extraire une méthode à partir de ce bloc pourra réduire la complexité de la fonction \mintinline{python}{print_current_date} n'améliorera rien et ne fera que déplacer le problème.
Une solution serait de passer par un dictionnaire, de façon à ramener la complexité à 1:
\begin{listing}[!ht]
\begin{minted}{python}
from datetime import date
def print_current_date():
DAYS_OF_WEEK = {
0: "Lundi",
1: "Mardi",
2: "Mercredi",
3: "Jeudi",
4: "Vendredi",
5: "Samedi",
6: "Dimanche"
}
print(DAYS_OF_WEEK.get(date.today().weekday()))
print(date.today())
\end{minted}
\caption{La même version, avec une complexité réduite à 1}
\end{listing}
\subsection{Tests unitaires}
Le nombre de tests unitaires nécessaires à la couverture d'un bloc fonctionnel est au minimum égal à la complexité cyclomatique de ce bloc.
Une possibilité pour améliorer la maintenance du code est de faire baisser ce nombre, et de le conserver sous un certain seuil.
Certains recommandent de le garder sous une complexité de 10; d'autres de 5.