Le code devient trop complexe quand on a peur d’y toucher
Mon passage chez IBM Canada se termine ce mois-ci. Après ces premières années, une idée me reste surtout en tête : le nombre de lignes mesure mal la difficulté d’un système. Ce qui ralentit une modification, c’est le nombre de choses qu’elle peut toucher sans qu’on sache lesquelles.
Je vois la complexité comme un graphe. Les modules sont des nœuds; un appel, une table partagée, un fichier, une variable globale ou un format de message crée une arête. Dix classes bien regroupées peuvent être faciles à changer. Trois modules qui lisent directement les tables les uns des autres peuvent former une toile impossible à isoler.
Prenons une méthode imaginaire :
EnregistrerCommande(formulaire)
convertir les champs
calculer prix et taxes
insérer commande et lignes
décrémenter inventaire
écrire fichier d'export comptable
envoyer courriel
afficher confirmation
Elle mélange interface, règles, transaction SQL, intégration fichier et notification. Si le courriel tombe après le commit, la méthode retourne-t-elle une erreur même si la commande existe? Si l’export réussit puis l’inventaire échoue, que faut-il annuler? Un test de taxe doit préparer une base, un serveur de courriel et un répertoire. Le problème n’est pas la longueur; ce sont les frontières invisibles.
Je commencerais par rendre le workflow explicite :
Formulaire → CommandeService → transaction Commande + Inventaire
↘ événement CommandeCréée → ExportComptable
→ Notification
CommandeService reçoit des types validés, applique les règles et commet l’état essentiel dans une transaction. Les effets externes utilisent un événement identifié et peuvent reprendre. Une interface ServiceCourriel permet un faux en test. Ce découpage n’est utile que si chaque frontière possède un contrat et une responsabilité; créer sept couches qui ne font que passer les mêmes paramètres ajoute des fichiers sans retirer d’incertitude.
Avant de refactorer du code risqué, j’écris des tests de caractérisation : avec cette entrée, le système actuel produit ce résultat, même si le résultat est étrange. Ils ne prouvent pas que le comportement est idéal; ils détectent un changement involontaire. Je crée ensuite une petite couture, déplace une règle et garde chaque étape exécutable.
Quelques signaux sont mesurables :
- combien de modules changent pour une seule règle métier;
- combien de dépendances faut-il démarrer pour un test;
- combien de chemins conditionnels possède une méthode;
- combien d’équipes doivent coordonner une mise en production;
- combien de formats ou tables sont lus sans propriétaire clair;
- combien de temps faut-il pour localiser une erreur avec les logs.
Les duplications demandent du jugement. Deux lignes semblables peuvent représenter des règles qui vont évoluer séparément. Une règle copiée à cinq endroits sous le même nom est plus dangereuse : corriger quatre copies laisse un bug silencieux dans la cinquième. Je cherche d’abord le propriétaire conceptuel, puis j’extrais.
La base et l’exploitation font partie du graphe. Renommer un statut peut briser un import nocturne. Un job planifié n’apparaît dans aucune référence du compilateur. Une chaîne de connexion ou permission ajoutée à la main devient une dépendance cachée. Je maintiens un diagramme léger des flux et un inventaire des consommateurs pour les contrats partagés.
Les protections doivent accompagner la simplification. Extraire un service n’autorise pas tous les clients à appeler chaque méthode. Déplacer une opération hors transaction exige idempotence, file et reprise. Un refactoring qui réduit la longueur du code mais perd l’audit ou la récupération n’est pas une amélioration.
Je pars avec beaucoup de respect pour le code qu’on peut modifier sans retenir son souffle. Ma pratique dans la prochaine étape sera de réduire une dépendance cachée à la fois, entourée d’un test et d’un point de mesure. Si une seule personne garde le graphe dans sa tête, le premier travail n’est pas de tout réécrire : c’est de rendre les frontières et les comportements observables par l’équipe.