Langage omniprésent

VoletStratégique Le point de départ de DDD, et celui qu’on croit toujours avoir déjà réglé.

Dans un projet ordinaire, trois vocabulaires coexistent. Celui du métier — des gens qui parlent de « dossier », de « bordereau », de « régularisation ». Celui des développeurs — OrderDTO, processItem, statusFlag. Celui de la base — des tables héritées de 2011, des colonnes nommées flg_actif. Chaque conversation traverse deux traductions, et chaque traduction perd de l’information.

Le coût ne se voit pas dans les tickets. Il se voit dans les réunions où l’on découvre après vingt minutes que « validation » ne désigne pas la même chose pour les deux personnes qui en parlent, et dans les correctifs qui portent sur une règle que personne n’avait mal codée — juste mal comprise.

Le langage omniprésent est la réponse : un seul vocabulaire, employé partout, par tout le monde, y compris dans le code.

Ce n’est pas un glossaire

C’est le malentendu le plus courant. Un glossaire est un artefact mort : on l’écrit, on le range, il vieillit. Le langage omniprésent est vivant, et son test est brutalement concret.

Une méthode du code peut-elle être lue à voix haute devant un expert du domaine, sans traduction, et être comprise — ou corrigée ?

Si l’expert dit « une commande passe en attente de validation client quand elle dépasse le plafond du compte », alors le code doit contenir quelque chose comme commande.attendreValidationClient() et non order.setStatus(3). La deuxième version n’est pas seulement moins lisible : elle rend la vérification impossible. Personne, dans le métier, ne peut confirmer ou infirmer un 3.

Note

« Omniprésent » ne veut pas dire « universel dans l’entreprise ». Un langage omniprésent est omniprésent à l’intérieur d’un contexte délimité — c’est précisément le sujet de la page Contextes délimités. Chercher un vocabulaire unique pour toute l’organisation est le grand échec classique, et le langage est justement l’outil qui permet de repérer où il faut couper.

Ce qu’un nom coûte

Comparons deux modèles du même métier. Le premier est technique ; le second parle.

​
// ---------- Le modèle qui ne dit rien ----------
class OrderManager {
  processOrder(data: OrderData): void {
    if (data.total > data.customer.limit) {
      data.status = 3;                      // 3 = ?
      this.notificationHelper.send(data);
    } else {
      data.status = 2;
      this.stockService.handle(data);
    }
  }
}

// ---------- Le modèle qui parle ----------
class Commande {
  soumettre(plafond: Plafond): void {
    if (this.totalTTC().depasse(plafond)) {
      this.etat = EtatCommande.AttenteValidationClient;
      this.enregistrer(new ValidationClientRequise(this.id));
    } else {
      this.etat = EtatCommande.Confirmee;
      this.enregistrer(new CommandeConfirmee(this.id));
    }
  }
}
# ---------- Le modèle qui ne dit rien ----------
class OrderManager:
    def process_order(self, data: OrderData) -> None:
        if data.total > data.customer.limit:
            data.status = 3                    # 3 = ?
            self.notification_helper.send(data)
        else:
            data.status = 2
            self.stock_service.handle(data)


# ---------- Le modèle qui parle ----------
class Commande:
    def soumettre(self, plafond: Plafond) -> None:
        if self.total_ttc().depasse(plafond):
            self._etat = EtatCommande.ATTENTE_VALIDATION_CLIENT
            self._enregistrer(ValidationClientRequise(self.id))
        else:
            self._etat = EtatCommande.CONFIRMEE
            self._enregistrer(CommandeConfirmee(self.id))
// ---------- Le modèle qui ne dit rien ----------
impl OrderManager {
    fn process_order(&self, data: &mut OrderData) {
        if data.total > data.customer.limit {
            data.status = 3;                    // 3 = ?
            self.notification_helper.send(data);
        } else {
            data.status = 2;
            self.stock_service.handle(data);
        }
    }
}

// ---------- Le modèle qui parle ----------
impl Commande {
    fn soumettre(&mut self, plafond: Plafond) {
        if self.total_ttc().depasse(plafond) {
            self.etat = EtatCommande::AttenteValidationClient;
            self.enregistrer(Evenement::ValidationClientRequise(self.id));
        } else {
            self.etat = EtatCommande::Confirmee;
            self.enregistrer(Evenement::CommandeConfirmee(self.id));
        }
    }
}

Les deux versions font la même chose. Mais la seconde peut être relue par le métier, ce qui déplace le moment où l’erreur est détectée : en revue de modèle plutôt qu’en recette. Et elle rend une question possible — « et si le plafond est exactement atteint ? » — que la première dissimule derrière un >.

Les mots interdits

Quelques familles de noms signalent presque toujours qu’un concept métier n’a pas été trouvé.

NomCe qu’il cacheÀ chercher
Manager, Processor, HandlerUn verbe métierQue fait-il vraiment ? Facturier, Expediteur
Helper, UtilsUn concept sans nomLe concept
Data, Info, ObjectUne entité ou un objet valeurLa chose elle-même
status: number, type: stringUne machine à états ou un type métierLes états nommés, un objet valeur
flag, isXxx en sérieUne règle métierLa règle, exprimée en un mot
process, handle, executeLe nom de l’opération métiersoumettre, regulariser, cloturer

Le test le plus efficace n’est pas une règle de nommage mais une question posée à l’expert : « comment appelez-vous ça, vous ? ». La réponse est souvent un mot qu’aucun développeur n’aurait inventé, et c’est précisément sa valeur.

Tip

Une conséquence directe : le langage du métier étant très souvent le français, c’est un argument sérieux pour écrire le domaine en français, même dans un code par ailleurs anglophone. « Régularisation », « bordereau », « à-valoir » n’ont pas d’équivalent anglais exact, et les traduire est exactement la perte d’information que le langage omniprésent cherche à supprimer. La convention à tenir est alors nette : français pour le domaine, anglais pour la technique.

Le langage révèle les frontières

C’est la fonction la plus sous-estimée de cet outil. Trois signaux, tous observables en réunion :

Un mot a besoin d’un adjectif pour être compris. Quand une équipe se met à dire « commande client » et « commande fournisseur » pour éviter la confusion, ce n’est pas un problème de vocabulaire : ce sont deux concepts distincts que le même mot recouvre, et donc probablement deux contextes.

Deux mots désignent la même chose. « Dossier » chez les uns, « demande » chez les autres, pour l’objet même. Soit l’un des deux termes doit disparaître, soit les deux groupes ne parlent effectivement pas de la même chose et il faut comprendre en quoi.

Un même mot déclenche deux réactions différentes. Dites « client » dans une salle où se trouvent le commerce, la facturation et le support : le premier pense prospect et potentiel, le deuxième pense entité juridique et IBAN, le troisième pense utilisateur avec un historique d’incidents. Aucun n’a tort. Il y a trois modèles.

Ces signaux sont la matière première du travail sur les contextes délimités, et c’est la raison pour laquelle le langage se traite avant les frontières : ce sont les frictions du vocabulaire qui indiquent où couper.

Le langage change, et c’est normal

Un point que le mot « omniprésent » masque : le langage n’est pas figé. Il évolue à mesure que la compréhension du domaine s’affine, et renommer est un acte de modélisation, pas une opération cosmétique.

Evans appelle cela le refactoring toward deeper insight. Le scénario typique : pendant des mois, une notion est modélisée comme un attribut booléen. Puis quelqu’un du métier dit une phrase qui change tout — « en fait, une commande bloquée et une commande suspendue, ce n’est pas pareil, la suspension a une durée » — et un objet valeur Suspension apparaît, avec une période, un motif et une règle de reprise. Le modèle vient de gagner en profondeur, et le code doit suivre le même jour.

Warning

Le corollaire déplaisant : un modèle qu’on refuse de renommer se fossilise. Si « on ne peut plus renommer OrderStatus parce que c’est dans l’API publique », alors le langage omniprésent s’arrête à la frontière de l’API — ce qui est acceptable, à condition que ce soit une décision consciente et que la traduction soit assurée par un adaptateur, pas par les développeurs de tête.

Comment on l’obtient en pratique

  • Parler directement aux experts, pas à travers un intermédiaire qui reformule. Chaque relais ajoute une traduction, et c’est la traduction qui est l’ennemi.
  • Écrire les règles avec leurs mots, littéralement, avant de coder. Les scénarios de tests d’acceptation sont un excellent véhicule : leur texte est relisible par le métier, et il fixe le vocabulaire.
  • Corriger le code quand le mot change, immédiatement. Un renommage différé est un renommage abandonné.
  • Faire un event storming, qui est le moyen le plus rapide connu de fabriquer un langage partagé : deux heures de post-its produisent plus de vocabulaire commun que trois mois de spécifications.
  • Accepter les désaccords comme de l’information. Deux experts qui ne s’accordent pas sur un terme viennent de signaler une ambiguïté réelle du métier, qui aurait fini en bug.

Références