CSS objet et CSS fonctionnel

Dans un projet web, les prix sont bien souvent négociés en amont en fonction d’estimations, mais deviennent par la suite quasiment immuables. Il faut alors réussir à réaliser le projet en respectant les délais et un certain standard de qualité. Mais c’est souvent après les estimations que la machine s’emballe : il n’est pas rare d’avoir à ajouter à ces contraintes l’avis changeant du client, les limites techniques découvertes sur le tard, les variations des spécifications, … Le client souhaite ensuite de nouvelles fonctionnalités et exprime de nouveaux besoins. Les choses continuent de se gâter lorsqu’il s’agit de reprendre du code écrit plusieurs mois (ou années) auparavant, lorsque des nouvelles technologies et méthodes apparaissent, lorsque l’équipe technique elle-même évolue.

Ça devient très vite un enfer à gérer, ce que « certaines études américaines » ont pu qualifier d’« entropie croissante ». C’est un gros mot qui veut dire ici que sur le long terme la production est hautement imprévisible, désordonnée voire chaotique : on avance de rustine en rustine et la complexité augmente jusqu’à ce que cela devienne risqué d’ajouter ne serait-ce qu’une « dernière » rustine. À ce stade là, on cesse d’espérer tenir le niveau de qualité et l’on crée de la dette technique.

La nouvelle ère des guide de styles zombies

Pour répondre à cette dette technique, en particulier sur le code front-end, de nombreuses équipes mettent désormais en place une approche en guide de styles (styleguide) et en composants. Cette méthode, notamment développée dans le désormais célèbre « atomic design », permet d’appréhender globalement des interfaces en système : une collection cohérente de composants connectés entre eux, ainsi que des méthodes partagées au sein d’une équipe, organisées autour d’un artefact (le guide de styles) dans l’objectif de répondre à une stratégie définie en amont (prototyper rapidement, redesigner un site existant, créer une plateforme de toutes pièces, …). L’approche est souvent bien pensée, méthodique et efficace, mais se retrouve très vite abandonnée une fois l’objectif atteint.

Or, un design system n’offre toutes ses possibilités que sur le long terme : il doit vivre et a besoin pour ça d’amour, d’être intégré aux process, d’être un outil pour toute l’équipe. Sans ça, il devient ce que Jina Anne a pu appeler un « zombie styleguide » : c’est un système qui n’est pas pensé sur le temps long des process mais de manière statique et éphémère, quand l’entropie n’existe justement qu‘en croissance continuelle. Et quand on arrête de vouloir la contenir, alors cette entropie contre laquelle on luttait reprend ses droits sur le projet. Et le styleguide passe d’inutilisé à inutilisable, mi-vivant mi-mort : zombie.

Si le guide de styles n’est pas pensé dynamiquement sur le temps long, il se fera rattraper par l’entropie de la production.

Le problème du « guide de styles zombie » est avant tout une illustration, parmi d’autres, de la difficulté qui existe de penser, en amont de toute démarche de code, la collaboration et l’évolutivité au sein d’une équipe. Confrontés avec mes collègues à ce problème, nous avons essayé d’y répondre sous l’angle des « process » et du manque d’adoption de la méthodologie au sein de l’équipe  : comment « faire » équipe ? Comment aligner et impliquer tout une équipe dans un design system ? Comment choisir les méthodes qui favorisent la réflexion, la collaboration et donc la maintenabilité d’un système ?

Mais cette première approche reste incomplète si elle n’est pas envisagée ensuite sous son angle plus « technique » : comment proposer un outil qui soit à la fois garant de l’identité de marque tout en permettant la flexibilité nécessaire à son utilisation dans différents contextes ?

Ma première expérimentation technique est venue à la lecture d’un article de 2016 par Sarah Drasner qui, après avoir rappelé que coder c’est 30 % de création et 70 % de maintenance, proposait de regarder la maintenabilité du code front-end à travers la lentille des deux paradigmes de programmation dominants : la programmation orientée objet et la programmation fonctionnelle.

Avertissement : l’idée ici n’est pas de revenir précisément sur le débat entre Programmation Orientée Objet et Programmation Fonctionnelle mais plutôt de voir comment certains concepts de ces paradigmes de programmation peuvent s’appliquer, au moins sur le plan métaphorique, à CSS, qui n’est pas un langage de programmation.

Le CSS objet

La programmation orientée objet (OOP) est un paradigme de programmation qui se base sur la création d’objets réutilisables et la création de relations entre ces objets. Son objectif est d’isoler les différentes parties d’un programme dans différentes portées indépendantes. Celui-ci est alors divisé en différentes abstractions, bien souvent des classes, qui tentent de définir l’essentiel d’une fonctionnalité par exemple, et en différentes instances qui sont des applications concrètes de ces classes.

Je résume souvent la programmation orientée objet à quatre aspects :

  1. une classe sert à isoler et définir les différentes propriétés d’une entité ;
  2. une classe peut être étendue mais pas modifiée (l’« open/closed principle ») ;
  3. les objets sont des instances de classe, et une classe est donc réutilisable via plusieurs objets ;
  4. ces objets ont chacun une unique responsabilité (le « single responsibility principle »).

L’approche « objet » en CSS est sans doute celle qui paraît la plus naturelle aujourd’hui. Dans les méthodologies de design UI et dans des outils de design comme Sketch, l’emphase est souvent mise sur le fait de diviser une interface en différents éléments (« symbols »). Beaucoup de métaphores alimentant ces approches sont des analogies avec des objets concrets (les briques LEGO, les pièces d’une voiture dans le process industriel) ou moins concrets (les morphèmes d’un langage, les notes de musique, les atomes d’un organisme).

L’approche en composant dans le process industriel

On peut ainsi traduire le style d’une interface sous forme de classes regroupant des propriétés et d’instances appliquant concrètement ces classes. Par exemple pour une classe « card » et des objets « événement » et « article » on pourrait définir :

Le CSS semble « by design » proposer la même chose, avec un nom de classe qui encapsule les propriétés qui la définissent. Des préprocesseurs comme Sass favorisent d’ailleurs encore plus ce paradigme en permettant de bien faire la distinction entre une définition (la classe, au sens OOP du terme) et son utilisation (l’objet), là où justement le CSS amalgame ces deux concepts.

CSS way vs Sass way.

Nicole Sullivan a proposé il y a dix ans maintenant d’appliquer la métaphore de l’OOP au CSS en proposant OOCSS (pour « Object-Oriented CSS »), dont les deux principes fondateurs reprennent les aspects mentionnés plus tôt.

  • Le premier principe distingue les objets dont la responsabilité est de définir la structure d’un composant et les classes dont la responsabilité est définir une esthétique variable (« cosmetics »), et qui viennent étendre les premiers.
  • Le deuxième principe distingue conteneurs et contenants, et insiste sur la nécessaire indépendance (et donc la nécessaire communication) des objets CSS entre eux afin de favoriser leur réutilisation. C’est également ce qu’invitent à faire des méthodologies comme BEM, qui propose d’identifier et de visualiser clairement un objet, ses sous-éléments, ses états et son scope, et de le nommer en fonction de cette dépendance afin de favoriser la sémantique des objets.

L’avantage du CSS objet est ainsi que les objets sont naturellement des métaphores concrètes, ce qui facilite leur utilisation et la communication autour d’eux. Ils favorisent la décomposition d’une interface en différentes parties isolées, ce qui est exactement ce que l’on cherche à faire dans la méthodologie design system. Le CSS objet propose donc une modularité organisée et offre une sémantique lisible et compréhensible.

En revanche, son inconvénient est qu’en divisant une interface en objets, il ne fait que déplacer le problème de flexibilité de l’interface vers ses objets : comment bien appréhender un objet en sachant qu’on ne pourra pas le modifier par la suite ? Comment savoir ce qu’un objet définit et quelles propriétés lui sont essentielles ? Que fait-on de ces propriétés (de positionnement notamment) qui « n’appartiennent » pas à la définition d’un objet ou de ces propriétés liés à un contexte particulier ?

Le CSS fonctionnel

La programmation fonctionnelle (FP) est un autre paradigme de programmation qui se base sur la déclaration d’un certain nombre de règles ou d’expressions qui décrivent le résultat que l’on attend. On déconstruit ainsi un programme, non plus en objets, mais en différentes expressions et fonctions qui décrivent tout ou partie du résultat. Une application peut ainsi être vue comme le résultat produit par l’évaluation mathématique de ces différentes expressions.

Comme pour OOP, je retiens surtout trois concepts importants de la programmation fonctionnelle :

  1. Un paradigme « déclaratif » : les programmes sont divisés en expressions abstraites qui peuvent être combinées ensemble afin de séparer les tâches et d’avoir une certaine composabilité ;
  2. La transparence référentielle : une fonction (« pure ») renverra toujours le même résultat lorsqu’on l’appelle avec les mêmes arguments, de telle sorte que l’on peut remplacer l’appel de la fonction par son résultat. L’expression ne changera qu’un état particulier et défini de la référence qu’il renvoie, et n’a pas de connection avec l’« extérieur » (les effets de bord).
  3. L’immuabilité : une référence ne peut pas être modifiée après sa création et renverra toujours la même valeur, ce qui apporte une certaine fiabilité.

La plupart des frameworks CSS semblent proposer aujourd’hui des classes « utilitaires » qui permettent une approche plus déclarative du CSS. On peut ainsi exprimer le style d’une interface à l’aide de ces utilitaires, qui sont comme des appels de fonction retournant toujours les mêmes références, c’est-à-dire sous forme de déclarations immuables et transparentes référentiellement :

Plusieurs méthodes, souvent réunies sous la bannière de l’« Atomic CSS », se fondent uniquement sur cette approche et se trouvent finalement assez proches des principes de la programmation fonctionnelle. C’est le cas par exemple du « utility-first CSS » proposé par l’équipe de Yahoo. Un exemple parmi d’autres, la « mega-bottombar » dans le code source de yahoo.com donne ceci :

En les reprenant point par point, on peut ainsi retrouver les différents aspects mentionnés auparavant de la programmation fonctionnelle :

  • Un paradigme « déclaratif » appliqué au CSS : les styles sont divisés en déclarations abstraites en mappant une classe à une propriété de style spécifique plutôt qu’à plusieurs (« Pos® » pour « position relative » par exemple). Il résulte que le style du composant ici n’est effectivement que la combinaison mathématique de toutes ces expressions.
  • La notion de « transparence référentielle » s’applique puisqu’ici chaque référence (ou classe donc) semble pure (« Pos® » renvoie systématiquement et uniquement une position relative).
  • De la même manière, chaque classe est ainsi immuable et fiable, n’étant définie qu’une seule fois et renvoyant systématiquement la même valeur.

Parce qu’il est déclaratif et non impératif, le CSS fonctionnel permet de contrôler le style là où le contenu est défini et donc d’ajuster les composants de manière contextuelle directement lors de leur utilisation. Il offre beaucoup de flexibilité là où le CSS objet demanderait d’étendre un objet ou de créer une nouvelle instance pour chaque contexte. Le CSS fonctionnel propose une approche flexible dans laquelle chaque composant peut « consommer » le style directement depuis le HTML comme une sorte d’API, facilitant grandement les phases de mise en page mais aussi de recettage qui demandent souvent à ce que les composants ne répondent pas uniquement à une charte graphique mais à des contextes techniques de positionnement, de breakpoint ou encore d’alignement. Surtout, elle apporte plus de sécurité : il paraît en effet plus sûr d’ajouter ou de retirer une classe sur un composant HTML spécifique que d’ajouter ou de retirer une propriété d’une classe qui s’applique à plusieurs éléments.

En apportant plus de lisibilité en déclarant directement sur le composant les références qu’il consomme, le CSS fonctionnel troque néanmoins de la sémantique contre plus de verbosité sur le HTML. Par ailleurs, si le CSS peut sembler naturellement orienté objet, il n’est en rien naturellement immuable ou transparent référentiellement : un nommage de classe n’oblige à rien, en tout cas n’interdit pas l’ajout d’effets de bord (par exemple une classe « bold » qui apporterait également une propriété de couleur) et, encore plus, la nature même de la cascade CSS permet la réassignation d’une classe et s’interdit donc une immuabilité « technique » par défaut. (Harry Roberts propose d’ailleurs des ébauches de solutions techniques pour garantir de l’immuabilité dans le CSS.) Cette dernière limitation technique n’est en revanche pas vraiment un inconvénient : le problème n’est pas réellement technique mais « humain », c’est-à-dire de promouvoir dans des guidelines et dans l’architecture CSS cette approche fonctionnelle.

Vers un design system flexible et sémantique

Le problème du « guide de styles zombie » n’est globalement qu’une manière d’illustrer le problème plus général de la collaboration et de la maintenance d’un projet web et, comme on le précisait plus tôt, d’inscrire l’approche en design system dans un temps plus long, celui des process d’une équipe, celui dans lequel les méthodologies accompagnent le projet de sa conception à son amélioration bien après la livraison. Dans ce temps long, il semble nécessaire d’essayer d’identifier et de distinguer le plus tôt possible ce qui est voué à perdurer de ce qui a plutôt vocation au changement.

Nous avons toujours essayé, avec les développeurs et designers avec lesquels j’ai pu travailler, de traduire dans le code cette distinction essentielle, et nos échecs ont souvent été liés au fait de ne pas anticiper le changement là où il aurait pu être anticipé. À ce titre, la programmation orientée objet et la programmation fonctionnelle apportent avec leurs concepts une distinction qui, si elle peut être contestable et discutée, nous a en tout cas permis d’y voir plus clair dans notre manière de concevoir et construire notre design system. Surtout, en mettant des concepts (instances, pureté, immuabilité, …) sur des approches, elle aide à construire des lignes directrices et nous rappelle que l’on ne code pas uniquement pour la machine mais aussi pour les collègues développeurs ou développeuses qui liront le code et pour le développeur que l’on sera demain.

Le CSS objet nous permet alors de définir les briques sémantiques, lisibles et pérennes sur lesquelles se fondent tout le système, quand le CSS fonctionnel nous apporte des outils flexibles, compréhensibles et composables pour construire tout ce que l’on souhaite à partir de ces briques.

À l’heure actuelle, un de nos design systems pourrait ainsi ressembler schématiquement à ça :

Si l’on met de côté les bases (styles de base, reset, …) et les dépendances (principalement les design tokens), le design system tourne ainsi autour de trois « artefacts ».

Les composants bénéficient du CSS objet : ils contiennent une définition, des états et des relations (avec d’autres objets), ils sont ouvert à l’extension (les classes modifiers de BEM) ; ils sont agnostiques et abstraits, c’est-à-dire définissant uniquement une forme et indépendants de tout contenu ; ils sont réutilisables et disposent d’un nommage sémantique lisible.

Exemple de la structure d’un objet.

Les utilitaires bénéficient du CSS fonctionnel : ils apportent aux objets des références abstraites qui ne font pas sens à être définies comme des propriétés d’un objet ou comme des entités indépendantes. Un exemple de cette approche est le système d’espacement, dont la flexibilité permet de travailler sereinement sur un design et dont l’immuabilité permet d’être « cache friendly ».

Aperçu simplifié du système d’espacement. Chaque référence est préfixée et définit une propriété (margin ou padding), une direction (top, right, …), une taille issue des design tokens (-s, ‑m, …) et éventuellement un breakpoint d’application (@main signifiant ici « à partir du principal breakpoint défini dans les design tokens »).

Enfin, le système de grille assez classique bénéficie de manière hybride à la fois du CSS objet (définissant des lignes, des colonnes et des types de grilles) et du CSS fonctionnel (apportant des références d’alignement par exemple).

Ce système repose donc, pour fonctionner sur le long terme, sur des lignes directrices fondées sur cette flexibilité et cette sémantique qu’apportent les deux approches CSS héritées des paradigmes de programmation. Mais ces langages de programmation n’ont-ils jamais eu à affronter la zombification de leurs outils ? Je me l’imagine oui. Et je me réconforte en me disant que nous ne sommes pas seuls, que nous pouvons apprendre les uns des autres, que nous pouvons collaborer ensemble pour la maintenabilité, c’est-à-dire la préservation de notre guide de styles bien aimé, si souvent la victime de cette zombification qui dévore nos esprits, nos joies et notre temps.

3 commentaires sur cet article

  1. MoOx, le jeudi 6 décembre 2018 à 14:34

    Au final on dirait un peu des styles inlines. Mettre autant de classes que de clés/valeurs… Autant faire des styles inlines ou du CSS-in-JS non ?

  2. PaOam, le jeudi 6 décembre 2018 à 21:34

    @MoOx : le « CSS fonctionnel » ressemble en effet à du style inline. Mais ici il est mis en relation avec du « CSS Objet », Il vient en fait ajouter de la flexibilité au « CSS objet ».
    Je cite : « le CSS fonctionnel permet […] d’ajuster les composants de manière contextuelle »

    De plus je n’aime pas trop le principe de style inline, il ajoute une forte spécificité, rend difficile l’utilisation de variables et pénalise donc la maintenabilité. Quand au CSS-in-JS c’est un autre débat car c’est, pour moi, assez différent.

    En tout cas merci et bravo pour l’article :)

  3. Jean-Rémi Larcelet, le vendredi 7 décembre 2018 à 00:16

    J’ai trouvé assez chaud l’exemple avec Yahoo, je trouve que ça va trop loin. Par contre, ajouter un simple « u– » devant les classes utilitaires, je me sens bête tout de suite, mais je n’y avais jamais pensé !

    Très bon post ! :)