Guide pratique des idées de Butler Lampson et Xerox PARC — réseau, architecture d’OS, nommage, mise en cache, RPC — et pourquoi elles façonnent encore les systèmes à grande échelle.

Butler Lampson a été l’un des concepteurs de systèmes informatiques les plus influents des cinquante dernières années. À Xerox PARC dans les années 1970 et 80, il a contribué à définir comment les ordinateurs en réseau devaient se comporter — non pas comme des machines isolées, mais comme des parties d’un environnement partagé où programmes, fichiers, imprimantes et personnes pouvaient interagir de façon fiable.
Ce qui rend le travail de Lampson particulièrement durable, c’est qu’il portait sur les fondamentaux : des interfaces qui montent en charge, des mécanismes qui se composent, et des systèmes qui considèrent les pannes du monde réel plutôt que de les traiter comme des exceptions.
« Échelle » ne signifie pas seulement posséder un énorme datacenter. C’est ce qui arrive quand votre système a beaucoup d’utilisateurs, beaucoup de machines et du désordre réel. Pensez : un bureau où des centaines d’ordinateurs et services partagent logins et fichiers ; un produit utilisé simultanément par des milliers de clients ; ou une application d’entreprise qui doit rester opérationnelle quand un serveur tombe, qu’un lien réseau ralentit, ou qu’un déploiement se passe mal.
À ce stade, les problèmes difficiles changent. On ne se demande plus « ça marche sur ma machine ? » mais plutôt :
Il ne s’agit pas d’un tour de nostalgie. Le travail de Lampson est utile parce qu’il a produit des idées de conception qui ont tenu : interfaces claires, briques simples, et systèmes conçus pour la défaillance.
Nous nous concentrerons sur les concepts qui ont traversé le temps jusque dans les systèmes d’exploitation modernes et l’informatique distribuée — mise en réseau, RPC, nommage, mise en cache et sécurité pratique — pour que vous puissiez reconnaître ces motifs dans les architectures actuelles et appliquer les leçons à vos services.
Imaginez un bureau où chaque personne a un ordinateur personnel puissant sur son bureau, connecté à des services partagés qui font que tout l’espace de travail semble former un système cohérent. C’était le pari de Xerox PARC : pas seulement « un ordinateur », mais un environnement en réseau où calcul, documents et communication circulent facilement entre personnes et machines.
PARC visait à rendre l’informatique personnelle pratique pour le travail quotidien — écrire, concevoir, partager des fichiers, imprimer des brouillons et collaborer — sans avoir besoin d’un opérateur de mainframe ni de rituels spéciaux. L’objectif n’était pas un appareil révolutionnaire isolé, mais une configuration de travail viable au quotidien.
L’Alto était la partie « personnelle » : un ordinateur conçu pour le travail interactif. Ethernet était la partie « lieu de travail » : un réseau local rapide qui permettait aux Altos de communiquer entre eux et avec des ressources partagées.
Ces ressources partagées étaient essentielles, pas accessoires :
Cette combinaison a fait évoluer le modèle mental : votre machine est puissante seule, mais elle devient beaucoup plus utile quand elle peut utiliser de façon fiable des services réseau.
PARC n’en restait pas aux prototypes ou aux démos isolées. Ils assembleaient des systèmes complets — matériel, OS, réseau et applications — puis apprenaient de l’usage réel.
Cette boucle de rétroaction a révélé les problèmes difficiles qui n’apparaissent qu’en pratique : nommer des choses, gérer la surcharge, faire face aux pannes, maintenir des performances prévisibles, et faire en sorte que les ressources partagées se sentent « proches » plutôt que distantes.
Beaucoup de systèmes PARC reflètent une approche reconnaissable : primitives simples associées à une rigueur d’ingénierie. Garder des interfaces petites et compréhensibles, construire des services qui se composent proprement, et tester les idées en déploiements réels. Ce style explique en grande partie pourquoi ces leçons se transfèrent encore aujourd’hui.
L’Alto n’était pas seulement « un ordinateur sur un bureau ». Il a marqué un tournant en combinant trois idées dans une expérience quotidienne : une machine personnelle, une interface graphique de qualité, et un réseau local rapide qui vous connectait à des ressources partagées.
Cette combinaison a reprogrammé les attentes. Votre machine vous appartenait : réactive, interactive et toujours disponible — mais elle était aussi une porte vers un système plus vaste : serveurs de fichiers, imprimantes et outils collaboratifs. C’est la graine de la mentalité client/serveur.
Avant les systèmes de type Alto, l’informatique signifiait souvent se rendre à la machine (ou à un terminal). L’Alto inversait cela : le « client » vit avec l’utilisateur, et le réseau fait paraître les capacités partagées proches.
En pratique, « client/serveur » n’était pas un simple diagramme — c’était un flux de travail. Une partie du travail se fait localement quand elle nécessite un retour instantané : édition de texte, dessin, interaction avec des fenêtres. D’autres tâches se font à distance parce qu’elles sont naturellement partagées ou trop coûteuses à dupliquer sur chaque poste : conserver des documents faisant autorité, gérer l’impression, coordonner les accès et, plus tard, exécuter des services communs.
Remplacez « Alto » par « portable » et « serveur de fichiers/impression » par « services cloud », et le modèle mental est familier. Votre appareil reste le client : il rend l’interface, met en cache des données et gère les interactions à faible latence. Le cloud reste le serveur : état partagé, collaboration, politique centralisée et calcul élastique.
La leçon : de bons systèmes embrassent cette division plutôt que de la combattre. Les utilisateurs veulent réactivité locale et tolérance hors-ligne ; les organisations veulent une vérité partagée et un accès coordonné.
Cette séparation crée une tension constante pour les concepteurs d’OS et systèmes :
Le travail PARC a rendu cette tension visible tôt. Une fois que l’on considère le réseau comme partie intégrante de l’ordinateur, il faut concevoir des interfaces, de la mise en cache et des comportements de panne pour que « local » et « distant » paraissent former un seul système — sans prétendre qu’ils sont identiques.
Ethernet est facile à sous-estimer parce qu’il ressemble à « juste du réseau ». À Xerox PARC, c’était la percée pratique qui a permis à une salle d’ordinateurs personnels de se comporter comme un système partagé.
Avant Ethernet, connecter des ordinateurs signifiait souvent des liaisons coûteuses et spécialisées. Ethernet a changé l’économie : un medium partagé relativement peu cher auquel beaucoup de machines pouvaient se raccorder.
Cela a déplacé l’hypothèse par défaut de « un gros ordinateur » à « plusieurs petits ordinateurs coopérant », parce que la collaboration n’exigeait plus une infrastructure héroïque.
Tout aussi important, la nature partagée d’Ethernet a encouragé un nouveau type de conception système : des services pouvaient vivre sur des machines différentes, imprimantes et serveurs de fichiers pouvaient être attachés au réseau, et les équipes pouvaient itérer rapidement parce que la connectivité n’était pas rare.
Aujourd’hui nous traitons le réseau comme la mémoire ou le stockage dans un OS : ce n’est pas un accessoire, c’est une partie de la plateforme. Le comportement « local » de votre app dépend souvent d’appels distants, de données distantes, d’identités distantes et de configuration distante.
Une fois cela accepté, on cesse de concevoir comme si le réseau se tiendrait en dehors.
Un réseau partagé signifie contention. Les paquets sont retardés, perdus ou réordonnés. Des pairs redémarrent. Des commutateurs sont surchargés. Même quand rien n’est « cassé », le système peut sembler défaillant.
La posture correcte est donc de construire pour un fonctionnement normal dans des conditions imparfaites :
Ethernet a rendu l’informatique distribuée faisable ; il a aussi imposé la discipline que l’informatique distribuée exige.
À Xerox PARC, un « service » était simplement un programme qui faisait un travail pour d’autres sur le réseau.
Un service de fichiers stockait et renvoyait des documents. Un service d’impression acceptait un document et produisait du papier. Un annuaire aidait à localiser le bon serveur de fichiers, l’imprimante ou la personne sans mémoriser les détails machines. Chaque service avait un but clair, une interface définie, et des utilisateurs (personnes ou programmes) qui en dépendaient.
Fragmenter un gros système en petits services rend les changements plus sûrs et plus rapides. Si le système d’impression doit évoluer, il peut le faire sans repenser le stockage des fichiers. Les frontières clarifient les responsabilités : « ici résident les fichiers » vs « ici se font les impressions ».
De plus, les services encouragent l’habitude de concevoir les interfaces en premier. Quand votre programme doit parler à une autre machine, on est forcé de spécifier entrées, sorties et erreurs — détails qui restent souvent vagues à l’intérieur d’un monolithe.
Plus de services signifie plus de requêtes réseau. Cela peut ajouter de la latence, augmenter la charge et créer de nouveaux modes de panne : le service de fichiers peut être disponible tandis que le service d’impression est en panne, ou l’annuaire peut être lent.
Un monolithe échoue « tout à la fois » ; les services distribués échouent de façon partielle et confuse. La solution n’est pas d’éviter les services, mais de concevoir explicitement pour la panne partielle.
Beaucoup d’apps cloud tournent désormais en services internes : comptes utilisateur, facturation, recherche, notifications. La leçon PARC s’applique toujours : découper pour la clarté et l’évolution indépendante — mais prévoir la latence réseau et les pannes partielles dès le premier jour.
Pour des conseils pratiques, les équipes associent souvent frontières de services avec timeouts basiques, retries et messages d’erreur clairs (voir /blog/failure-is-normal).
Remote Procedure Call (RPC) est une idée simple avec un grand bénéfice : appeler une fonction sur une autre machine comme si c’était un appel local. Plutôt que d’emballer manuellement une requête, l’envoyer sur le réseau et déballer la réponse, RPC permet à un programme de dire « exécute getUser(42) » et de laisser le système gérer l’échange de messages en arrière-plan.
Cet objectif de « ressenti local » était central dans les travaux PARC — et c’est toujours ce que recherchent les équipes : interfaces claires, comportement prévisible et moins de pièces exposées dans le code applicatif.
Le danger est que RPC peut ressembler trop à un appel de fonction normal. Un appel local s’exécute ou fait planter votre processus ; un appel réseau peut être lent, disparaître, s’exécuter partiellement, ou réussir sans que vous ayez de retour. Les bons designs RPC intègrent ces réalités manquantes :
Les timeouts et réponses perdues rendent les retries inévitables. C’est pourquoi l’idempotence est importante : une opération est idempotente si l’exécuter une fois ou plusieurs fois produit le même effet.
Exemple simple : chargeCreditCard(orderId, amount) n’est a priori pas idempotent — retenter après un timeout risque de débiter deux fois. Une conception plus sûre est chargeCreditCard(orderId) où orderId identifie de façon unique le débit, et le serveur considère les répétitions comme « déjà faites ». Le retry devient sûr parce que le serveur peut dédupliquer.
Les API modernes sont des descendants directs de la mentalité RPC. gRPC rend explicite le modèle « appeler une méthode distante » avec interfaces définies et messages typés. REST est souvent plus orienté ressource que méthode, mais l’objectif est similaire : standardiser la communication entre services, définir des contrats et gérer les pannes.
Quel que soit le style, la leçon PARC reste : le réseau est un outil, pas un détail à ignorer. Un bon RPC facilite la distribution sans prétendre que c’est gratuit.
Un système distribué ne semble « distribué » que quand il casse. Beaucoup de jours, il semble cassé parce que quelque chose est introuvable.
Le nommage est difficile parce que le monde réel ne reste pas immobile : machines remplacées, services déplacés, réseaux renumérotés, et les gens attendent toujours des chemins stables et mémorables comme « le serveur de fichiers » ou « imprimer sur LaserWriter ». Si le nom que vous tapez est aussi l’emplacement, chaque changement devient une panne visible par l’utilisateur.
Une idée-clé de l’ère PARC est de séparer ce que vous voulez de là où ça vit actuellement. Un nom doit être stable et signifiant ; une localisation est un détail d’implémentation qui peut changer.
Quand les deux sont fusionnés, on obtient des systèmes fragiles : raccourcis, IP codées en dur et dérive de configuration.
Les services d’annuaire répondent à la question « où est X maintenant ? » en mappant noms vers localisations (et souvent vers des métadonnées comme type, propriétaire ou règles d’accès). Les meilleurs annuaires n’enregistrent pas seulement des correspondances — ils codent aussi le fonctionnement d’une organisation.
Les bons designs de nommage et annuaires partagent quelques propriétés pratiques :
DNS est l’exemple classique : un nom convivial mappe vers un ensemble changeant d’IPs, avec un cache contrôlé par des TTL. À l’intérieur des entreprises, les systèmes de découverte de services (ceux qui soutiennent des noms comme “service-a.prod”) répètent le même modèle : noms stables, instances changeantes, et tension constante entre performance du cache et vitesse de mise à jour.
La leçon est simple : si vous voulez des systèmes qui montent en charge — et restent compréhensibles — traitez le nommage comme un problème de conception de première classe, pas comme une réflexion après coup.
La mise en cache est simple : conservez une copie locale de quelque chose que vous avez déjà récupéré pour que la requête suivante soit plus rapide. Au lieu de traverser le réseau (ou interroger un disque lent ou un serveur occupé) à chaque fois, vous réutilisez la copie locale.
À Xerox PARC, cela comptait parce que stations de travail en réseau et services partagés rendaient coûteuse l’habitude de « redemander au serveur ». La mise en cache faisait paraître les ressources distantes rapides — la plupart du temps.
Le hic, c’est la fraîcheur. Un cache peut devenir incorrect.
Imaginez un document partagé stocké sur un serveur. Votre poste met en cache le fichier pour l’ouvrir instantanément. Un collègue modifie le même document et enregistre une nouvelle version. Si votre cache ne s’en aperçoit pas, vous risquez de voir l’ancien contenu — ou pire, d’éditer une copie obsolète et d’écraser le travail plus récent.
Chaque conception de cache est donc un compromis entre :
Les équipes gèrent généralement ce compromis avec quelques outils :
Les mêmes motifs apparaissent partout : CDN qui mettent en cache le contenu web près des utilisateurs, navigateurs et apps mobiles qui mettent en cache ressources et réponses d’API, et couches de cache de base de données (Redis, Memcached) qui réduisent la charge sur les magasins primaires.
La leçon durable : la mise en cache est souvent le gain de performance le plus simple — à condition d’être explicite sur ce que « assez frais » signifie pour votre produit.
La sécurité à grande échelle n’est pas seulement « qui êtes-vous ? » — il s’agit aussi de « que pouvez-vous faire, maintenant, avec cette ressource précise ? » Lampson et la tradition Xerox PARC ont promu une idée très pratique : les capacités.
Une capacité est un jeton infalsifiable qui accorde l’accès à quelque chose — un fichier, une imprimante, une boîte aux lettres ou une opération de service. Si vous détenez le jeton, vous pouvez effectuer l’action autorisée ; sinon non.
L’important est l’infalsifiabilité : le système rend impossible, par construction ou calcul, de créer un jeton valide en devinant.
Pensez-y comme une carte-clé d’hôtel qui n’ouvre que votre chambre (et seulement pendant votre séjour), et non un mot griffonné disant « j’ai le droit ».
Beaucoup de systèmes reposent sur la sécurité basée sur l’identité : vous vous authentifiez en tant qu’utilisateur, puis chaque accès est vérifié contre une ACL (liste d’accès) sur la ressource qui dit quels utilisateurs/groupes peuvent faire quoi.
Les ACL sont intuitives, mais elles deviennent lourdes dans les systèmes distribués :
Les capacités inversent le défaut. Plutôt que de demander constamment à une autorité centrale, vous présentez un jeton qui encode déjà le droit.
Les systèmes distribués passent constamment du travail entre machines : un frontend appelle un backend ; un ordonnanceur confie une tâche à un worker ; un service déclenche un autre service. Chaque saut a besoin d’un moyen sûr de porter juste assez d’autorité.
Les capacités rendent cela naturel : vous pouvez transmettre un jeton avec une requête, et la machine réceptrice le valide sans réinventer la confiance à chaque fois.
Bien conçues, elles réduisent les permissions excessives accidentelles et limitent le rayon d’explosion quand quelque chose tourne mal.
Les capacités apparaissent aujourd’hui comme :
La leçon : concevez l’accès autour de délégation, périmètre et expiration, pas seulement autour d’identités longue durée. C’est la pensée par capacités, remise au goût du jour pour l’infrastructure moderne.
Les systèmes distribués ne « cassent » pas d’une manière propre. Ils échouent de façon désordonnée et partielle : un processus plante en plein traitement, un switch redémarre, une liaison réseau perd des paquets, ou un événement d’alimentation coupe une baie mais pas les autres.
Du point de vue utilisateur, le service est « up », et pourtant une tranche en est inaccessible.
Un modèle de panne pratique est franc :
Une fois cela accepté, on cesse de traiter les erreurs comme des « cas limites » et on les considère comme du flux de contrôle normal.
La plupart des systèmes s’appuient sur un petit ensemble de réponses.
Timeouts empêchent un appelant d’attendre indéfiniment. L’important est de choisir des timeouts basés sur des données de latence réelles, pas des suppositions.
Retries peuvent récupérer des fautes transitoires, mais peuvent aussi multiplier la charge pendant une panne. C’est pourquoi l’exponential backoff (augmenter l’intervalle entre tentatives) et le jitter (aléa) comptent : ils évitent des tempêtes de retries synchronisées.
Basculement (failover) vers une instance de secours aide quand un composant est réellement mort, mais cela ne fonctionne que si le reste du système peut détecter la panne de façon sûre et rapide.
Si vous retentez une requête, vous pouvez l’exécuter plus d’une fois. C’est la livraison au moins une fois : le système s’efforce de ne pas perdre le travail, mais des duplications peuvent survenir.
Une seule fois (exactly-once) signifie que l’action se produit une fois, sans duplicata. C’est une promesse agréable, mais difficile à tenir à travers une coupure réseau.
Beaucoup d’équipes préfèrent rendre les opérations idempotentes (sûres à répéter), de sorte que l’au-moins-une-fois devient acceptable.
Les équipes les plus fiables injectent activement des pannes en staging (et parfois en production) et observent : tuer des instances, bloquer des chemins réseau, ralentir des dépendances, et vérifier alarmes, retries et impact utilisateur.
Traitez les incidents comme des expériences qui améliorent votre conception, pas comme des surprises « qui ne devraient pas arriver ».
Les systèmes d’exploitation vieillissent vite : chaque nouvelle fonctionnalité multiplie les interactions possibles, et c’est là que se cachent les bugs.
L’école de Lampson — façonnée à Xerox PARC — considère la structure de l’OS comme une stratégie d’échelle. Si le noyau est désordonné, tout ce qui repose dessus hérite de ce désordre.
Une leçon récurrente de l’ère PARC est de garder le noyau (ou le « cœur de confiance ») étroit et composé de primitives simples et composables. Plutôt que d’intégrer des dizaines de cas spéciaux, définissez quelques mécanismes faciles à expliquer et difficiles à mal utiliser.
Des interfaces claires comptent autant que les mécanismes eux-mêmes. Quand les frontières sont explicites — ce qu’un composant promet, ce qu’il peut supposer — on peut remplacer des implémentations, tester des parties en isolation et éviter les couplages accidentels.
L’isolation limite le rayon d’explosion. Que ce soit la protection mémoire, la séparation de processus ou l’accès aux ressources au moindre privilège, l’isolation transforme « un bug n’importe où casse tout » en « un bug est contenu ».
Cette approche pousse aussi vers des designs de type capacité : donner au code seulement l’autorité dont il a besoin et rendre l’accès explicite plutôt qu’implicite.
Le pragmatisme se manifeste aussi en performance : construire des chemins rapides pour les opérations courantes et éviter le surcoût qui n’apporte ni sécurité ni clarté.
Le but n’est pas d’optimiser chaque micro-détail — c’est de rendre le cas d’usage habituel instantané tout en préservant la correction.
On retrouve ces idées dans les noyaux modernes, les runtimes de langages et les plateformes conteneurisées : une base de confiance réduite, des API bien définies et des frontières d’isolation (processus, sandboxes, namespaces) qui permettent aux équipes d’expédier rapidement sans partager les mêmes modes de panne.
Les détails ont changé ; les habitudes de conception payent toujours.
La grande réussite de PARC n’était pas une invention unique — c’était une manière cohérente de construire des systèmes en réseau utilisables. Les noms ont changé, mais les problèmes de fond (latence, pannes, confiance, propriété) n’ont pas disparu.
Un petit « dictionnaire mental » aide lors des revues d’architecture :
Utilisez ceci pour évaluer un système à grande échelle :
Une nouveauté moderne est la rapidité à prototyper des architectures distribuées. Des outils comme Koder.ai (plateforme vibe-coding qui construit web, backend et apps mobiles depuis le chat) accélèrent la phase du « premier système fonctionnel » — React côté frontend, Go + PostgreSQL côté backend, et Flutter pour le mobile — tout en permettant d’exporter le code source et d’évoluer vers une base sérieuse de production.
La leçon de l’ère Lampson s’applique toujours : la vitesse n’est un gain que si vous gardez des interfaces nettes, rendez le comportement en cas d’échec explicite (timeouts, retries, idempotence), et traitez le nommage, la mise en cache et les permissions comme des décisions de conception de première classe.
Copiez la discipline : interfaces simples, contrats explicites et conception pour pannes partielles. Adaptez les mécanismes : aujourd’hui vous utiliserez découverte gérée, API gateways et IAM cloud — pas des annuaires maison et une auth bricolée.
Évitez la sur-centralisation (un service « dieu » dont tout dépend) et l’absence de responsabilité (composants partagés sans personne responsable).
Les outils continueront d’évoluer — nouveaux runtimes, clouds, protocoles — mais les contraintes restent : les réseaux tombent en panne, la latence existe, et un système ne scale que si des humains peuvent l’exploiter.
Dans ce contexte, « échelle » signifie fonctionner en présence de nombreux utilisateurs, de nombreuses machines et d’un désordre réel constant. Les difficultés apparaissent quand les requêtes traversent plusieurs services et que les pannes sont partielles : certaines choses fonctionnent, d'autres expirent, et le système doit rester prévisible.
PARC a construit un environnement de travail réseau complet : des ordinateurs personnels (Alto) connectés via Ethernet à des services partagés comme des serveurs de fichiers et d'impression. La leçon-clé est que l’on n’apprend les vrais problèmes systèmes que quand des personnes utilisent quotidiennement un système de bout en bout : les noms, la surcharge, la mise en cache, les pannes et la sécurité deviennent inévitables.
C’est la séparation pratique qui perdure : effectuer les interactions sensibles à la latence localement (UI, édition, rendu) et placer l’état partagé ou faisant autorité dans des services (fichiers, identités, collaboration, règles). L’objectif de conception devient réactivité locale élevée avec un comportement global cohérent lorsque le réseau est lent ou peu fiable.
Parce que le réseau devient une dépendance de premier ordre, pas un détail en arrière-plan. Quand de nombreuses machines partagent un medium et que les services communiquent souvent, il faut s’attendre à :
Les bonnes pratiques sont donc : instrumenter tôt, utiliser des timeouts et retenter avec prudence pour ne pas empirer une panne.
Découper en services améliore la clarté et l’évolution indépendante : chaque service a un but ciblé et une interface définie. Le coût est l’ajout de sauts réseau et de modes de panne partiels, donc il faut de la discipline sur les contrats et la fiabilité (timeouts, retries, comportement d’erreur visible pour l’utilisateur).
RPC permet d’appeler une opération distante comme si elle était locale, mais un bon design RPC rend explicites les réalités réseau. Concrètement, il faut :
Sinon, RPC favorise des designs fragiles du type « on dirait local, donc j’oublie que c’est distant ».
Les timeouts et les réponses perdues rendent les retries inévitables, et ceux-ci peuvent dupliquer le travail. On conçoit la sécurité des opérations en :
orderId)Ceci est crucial pour des actions comme les paiements, le provisionnement ou l’envoi de notifications.
Si un nom est aussi une localisation (hôte/IP/chemin codé en dur), les migrations et pannes deviennent des interruptions visibles par l’utilisateur. Séparez les noms stables des localisations changeantes via un annuaire ou un système de découverte, et laissez les clients interroger « où est X maintenant ? » en mettant en cache les réponses avec des règles de fraîcheur claires (TTL).
La mise en cache est souvent le gain de performance le moins coûteux, mais elle introduit le risque d’obsolescence. Les contrôles courants sont :
L’essentiel est d’écrire ce que « suffisamment frais » signifie pour chaque donnée afin que la cohérence ne soit pas accidentelle.
Une capacité est un jeton infalsifiable qui accorde des droits spécifiques sur une ressource ou une opération. Comparées aux vérifications identité+ACL partout, les capacités facilitent la délégation et le principe du moindre privilège dans les systèmes multi-sauts :
Des analogues modernes incluent les tokens OAuth, les identifiants cloud à périmètre restreint et les URLs signées/JWT (à utiliser prudemment).