Code et métier : de l'abstraction à la modélisation
Code et métier : de l'abstraction à la modélisation
Introduction
Un tech lead pose la question à son équipe : "Dans notre code, comment on sait qu'on est au bon niveau d'abstraction ?" Silence. Chacun a une intuition, mais personne n'a la même. Ce moment de flottement est révélateur : la question de l'abstraction et du design de code est centrale dans le travail quotidien des équipes, mais elle est rarement posée explicitement.
Cet article explore deux facettes complémentaires du problème : comment le code exprime le métier à travers l'abstraction et l'encapsulation, et pourquoi trouver les bons concepts métier demande du temps et de la collaboration.
Abstraction et encapsulation : les fondations
Ces deux concepts ont été popularisés par la programmation orientée objet, mais ils ont de la valeur quel que soit le paradigme utilisé. Grady Booch les définit ainsi dans Object-Oriented Analysis and Design :
Abstraction focuses on the essential characteristics of some object, relative to the perspective of the viewer.
Encapsulation hides the details of the implementation of an object.
Soit :
L'abstraction se concentre sur les caractéristiques essentielles d’un objet, du point de vue de l’observateur.
L’encapsulation cache les détails d’implémentation d’un objet.
En pratique, l'abstraction permet de se concentrer sur ce qui est essentiel du point de vue de l'utilisateur du code. L'encapsulation garantit que les détails d'implémentation ne fuient pas partout dans la base de code.
Prenons l'exemple d'une pile, la structure de données classique : quelle que soit son implémentation (tableau, liste chaînée), ses caractéristiques essentielles sont push et pop. L'appelant n'a pas à connaître ni à dépendre du reste.
Ce que ces principes impliquent en architecture
Ces notions se déclinent à plusieurs niveaux dans un projet d'équipe :
La notion de couches organise le code par niveaux d'abstraction. Dans une couche métier bien définie par exemple, on ne manipule que des concepts métier : pas de formats de fichiers, pas de requêtes SQL, pas de détails d'ORM. Quand on lit ou écrit du code dans cette couche, on réfléchit au problème métier uniquement. Comme le formule Romeu Moura dans sa conférence TDD: Beyond the intro : même si on était capable de réfléchir à tout en même temps, pourquoi s'y contraindre ?
L'injection de dépendances rend explicites les relations entre couches et facilite les tests automatisés. Elle révèle aussi les couplages excessifs : si les dépendances injectées prolifèrent, c'est souvent le signe que les responsabilités ne sont pas bien séparées.
La cohérence des niveaux d'abstraction est une question utile à se poser en revue de code : "Est-ce que ces cinq lignes de code manipulent des éléments qui sont au même niveau d'abstraction ?" Cette question seule démêle beaucoup de code enchevêtré.
# Mélange de niveaux d'abstraction :
# règle métier, accès base de données, et formatage dans la même fonction
def valider_virement(compte_id, montant):
rows = db.execute("SELECT solde FROM comptes WHERE id = ?", compte_id)
solde = rows[0]["solde"]
if solde - montant < 0:
raise ValueError(f"Solde insuffisant : {solde:.2f}€ disponible, {montant:.2f}€ demandé")
return True
# Niveaux cohérents : cette fonction ne parle que de métier
def valider_virement(compte, montant):
if compte.solde < montant:
raise SoldeInsuffisant(compte, montant)
return True
La gestion de la base de données et le formatage des messages ont leur propre niveau d'abstraction.
Le modèle évolue : c'est inévitable
Mais il ne suffit pas d'organiser le code en couches pour bien exprimer le métier. Eric Evans, dans Domain-Driven Design, l'explique avec un exemple frappant :
The truth is, though, that initial models usually are naive and superficial, based on shallow knowledge.
Il décrit un projet de logiciel de transport maritime. L'intuition initiale de l'équipe : modéliser des navires (Ship) et des conteneurs (Container). C'est ce que n'importe quel développeur ferait en premier.
Résultat après plusieurs mois de travail avec les experts métier : ces deux concepts ont pratiquement disparu du modèle. Le navire a été remplacé par le concept de "vessel voyage" (voyage de navire planifié), qui a un cycle de vie indépendant de celui du navire physique, qui peut changer au dernier moment. Le conteneur s'est révélé être un détail opérationnel sans pertinence pour cette application précise. Et des concepts moins évidents, comme le "bill of lading" (bon de chargement), sont devenus centraux car ils représentent les transferts de responsabilité légale, qui sont en réalité le cœur du métier.
La leçon est double. D'une part, développer un logiciel prend du temps non pas parce qu'écrire du code est long, mais parce que comprendre correctement le problème l'est. D'autre part, une modélisation intuitive n'est pas une mauvaise modélisation : c'est une modélisation de départ, qui évoluera.
Les implications pratiques pour l'équipe
Ces deux aspects, l'architecture en niveaux d'abstraction et l'évolution du modèle métier, se rejoignent dans la vie d'une équipe.
Concevoir pour le changement. Si le modèle va nécessairement évoluer, l'architecture doit rendre ce changement moins difficile. C'est une raison concrète d'investir dans la séparation des couches, les tests automatisés, et le refactoring régulier : pas pour la beauté du code, mais pour garder la capacité de corriger la modélisation quand on comprend mieux le métier.
Accompagner les nouvelles personnes dans l'équipe. Evans observe que chaque nouveau développeur qui rejoint le projet proposera de remettre les navires et les conteneurs dans le modèle : ce sont des personnes compétentes, qui n'ont simplement pas encore parcouru le chemin de la découverte. Cela signifie que l'équipe doit pouvoir expliquer le modèle, mais aussi pourquoi le modèle est ce qu'il est, ce qui nécessite des conversations et de la documentation.
Mauvaise abstraction vs. bonne abstraction. Trouver la bonne abstraction métier est difficile et demande parfois d’attendre d’avoir le bon niveau d’information. Dans The Wrong Abstraction, Sandi Metz propose : "Prefer duplication over the wrong abstraction." Introduire une abstraction qui rend l'expression du besoin métier plus complexe n'est pas un gain, c'est une dette. La question n'est pas "est-ce que ce code est élégant ?" mais "est-ce que ce code exprime clairement ce que le métier attend ?"
Un virement, un remboursement et un prélèvement ont des règles métier fondamentalement différentes. Les regrouper sous une seule fonction crée une fausse unité : chaque paramètre n'a de sens que pour un seul type, et ajouter un nouveau type d'opération oblige à modifier une fonction qui n'avait rien à voir avec ce nouveau besoin.
# Mauvaise abstraction : trois concepts métier distincts forcés dans une seule fonction
def traiter_operation(type, compte, montant, compte_destination=None, reference_transaction=None):
if type == "virement":
debiter(compte, montant)
crediter(compte_destination, montant)
elif type == "remboursement":
transaction = charger_transaction(reference_transaction)
crediter(compte, transaction.montant)
elif type == "prelevement":
debiter(compte, montant)
Trois fonctions séparées (effectuer_virement, rembourser_transaction*,* executer_prelevement*) auraient été plus lisibles et plus fidèles au métier.*
Conclusion
Aligner le code sur le métier n'est pas un état qu'on atteint une fois pour toutes en début de projet. C'est une activité continue, faite de refactoring, de conversations avec les experts métier, et de remises en question régulières du modèle.
Les outils concrets pour y parvenir existent : couches, injection de dépendances, tests automatisés, revues de code. Mais ils ne suffisent pas sans la discipline de maintenir les niveaux d'abstraction cohérents, et sans la volonté collective de retravailler le modèle quand la compréhension du métier s'approfondit.
Pour une équipe, la vraie question n'est pas "avons-nous une bonne architecture ?" mais "est-ce que notre code parle encore le même langage que nos experts métier ?"
Pour aller plus loin sur les pratiques concrètes qui permettent ces conversations, l'Event Storming et l'Example Mapping sont deux outils qui aident les équipes à construire ce langage commun avec leurs experts métier, comme en témoigne ce retour d'expérience sur l'alignement métier-tech grâce au DDD.
Références
- Grady Booch, Object-Oriented Analysis and Design
- Eric Evans, Domain-Driven Design
- Sandi Metz, The Wrong Abstraction
- Romeu Moura, TDD: Beyond the intro