Avez-vous déjà mis bien plus de temps que vous ne l'imaginiez pour modifier une feuille de style ? Vous êtes-vous déjà dis qu'une modification ne devrait pas être si périlleuse. Derrière un langage si simple à prendre en main se cache un langage qui ne nous offre pas les moyens d'affronter la complexité qui grandit en même temps que l'application.
Grâce à l'aide d'extensions comme Sass, le développement CSS devient tout de suite plus professionnel. On retrouve les concepts des autres langages (variables, méthodes, ...) et cela change radicalement la situation.
Comment faire la transition de nos feuilles de style CSS pour les rendre plus maintenables ? Soit les réécrire complètement. Soit suivre les refactorings présentés dans ce document pour progressivement arriver à notre objectif, tout en ayant à chaque étape, une feuille de style opérationnelle.
Bad Smells
Les Bad Smells sont fortement inspirées de celles présentes dans l'ouvrage référence de Martin Fowler [Fow1]
Duplicate Code
S'il y a bien une chose que nous permet Sass, c'est de pouvoir respecter le principle Don't Repeat Yourself (DRY).
Bien souvent, les duplications de code sont le témoignage de négligences, mais quand il s'agit de CSS, les duplications sont souvent inévitables.
La duplication la plus fréquente consiste à dupliquer la même valeur de propriété encore et encore. Un simple Extract Constant suffit à ne définir cette valeur qu'une seule fois.
Vient ensuite une duplication tout aussi fréquente mais moins apparente, la duplication de plusieurs propriétés CSS que l'on retrouve dans différents blocs. La solution consiste à appliquer soit Extract Declaration Block, soit Introduce Inheritance.
Enfin, les sélecteurs ne sont pas épargnés par toute cette duplication. Pour restreindre les déclarations CSS à une partie de la page, pas d'autre solution que de préfixer à chaque fois par le sélecteur du parent et ainsi de suite. Introduce Nesting supprime toute cette duplication et rend par la même occasion nos feuilles de style bien plus lisible et évolutive.
Long Block Declaration
L'impossibilité de factoriser des blocs de déclaration nous pousse soit à les dupliquer, soit à introduire une
nouvelle classe CSS sans véritable valeur sémantique (ex : .rounded-box
).
Dans le premier cas, les blocs de déclaration s'allongent au risque de nuire à leur maintenabilité.
Un simple Extract Declaration Block suffira à amincir
considérablement vos blocs.
Large File
Plusieurs solutions existent pour diviser un fichier CSS. On peut inclure plusieurs feuilles à l'aide de la
balise HTML link
ou utiliser la règle CSS @import
. Les deux obligent le navigateur à
émettre une requête pour chaque feuille, ce qui augmente le temps de réponse, et aussi d'autres subtilités
([Sou1]).
Une solution courante est de concaténer les feuilles de style lors de la construction du projet (maven, grunt, ...). Appliquer Extract File est souvent plus simple.
Divergent Change
Difficile d'effectuer la moindre modification quand le code concerné est noyé par tout ce qui l'entoure. Il est préférable de structure le code de manière à ce que chaque changement nous conduise à un point bien précis dans le code.
Divergent Change se produit quand un même fichier a beaucoup de raisons de changer. En séparant par exemple la partie layout et les différentes parties du site, il devient facile de modifier le layout général, d'ajouter une nouvelle page ou d'en supprimer une existante.
En appliquant Extract File, chaque partie se retrouve dans sa propre feuille de style (ex : layout, homepage, contact, product).
Shotgun Surgery
Shotgun Surgery est en quelque sorte l'inverse de Divergent Change. Divergent Change se produit quand une feuille de style subit plusieurs types de changement alors que Shotgun Surgery se produit quand une modification implique plusieurs feuilles de style. Idéalement, chaque modification ne porte que sur une seule feuille de style.
Plusieurs refactorings peuvent s'appliquer. Extract Constant permet de condenser plusieurs valeurs vers une même déclaration. Inline File permet de regrouper deux feuilles de style très proches.
Comments
Les commentaires sont souvent nécessaires en CSS pour combler l'absence de variables, apportant avec elles la sémantique derrière une valeur magique. Paradoxalement, on hésite à inclure des commentaires car ceux-ci augmentent la taille des fichiers et se retrouvent inutilement dans le fichier téléchargé par le navigateur.
Extract Constant et Extract Declaration Block nous permettent de remplacer la plupart des commentaires par des noms tout aussi parlant, à la différence que ces noms font désormais partie intégrante du code.
Magic numbers
En parcourant des feuilles de style, on découvre souvent des nombres (ex : unités en px) qui sont identiques ou se ressemblent fortement. Le lien entre toutes ces valeurs n'est pas représenté explicitement. L'introduction de constantes (Extract Constant) est le premier pas pour limiter cela mais comment représenter que la taille est liée à tel autre élément. Introduce Arithmetic propose de représenter ce lien sous forme d'une expression mathématique où l'on viendra souvent réutiliser des constantes existantes.
Almost Identical
Almost Identical représente une situation où de nombreux selecteurs et blocs de déclaration se suivent, se ressemblent, mais ne sont pas identiques. L'utilisation de sprites CSS est l'exemple le plus parlant. Introduce Conditional Branching retire toute ressemblance et rend clairement apparent les quelques différences grâce à l'introduction des structures de contrôles (if, for, ...) incontournables dans les langages impératifs.
Extract Declaration Block peut également servir à factoriser le contenu des blocs de déclaration, notamment grâce à l'ajout de paramètres.
Introduce Inheritance doit être également considéré si les différents sélecteurs partagent une relation d'héritage avec une classe commune.
Color Explosion
La plupart des sites repose sur un nombre restreint de couleurs : une couleur principale et quelques couleurs secondaires. Ces couleurs sont souvent déclinées. Un bleu un peu plus clair par ici, un bleu un peu plus foncé par là. Cette déclinaison se traduit par la définition de nouvelles couleurs et avec cette explosion de couleurs, difficile de ne pas s'emmêler les pinceaux quand arrive le moment de changer nos quelques 2-3 couleurs de base.
En appliquant Extract Constant et Introduce Arithmetic, on va définit nos couleurs de base que l'on déclinera ensuite au besoin grâce à l'utilisation de fonctions permettant de mélanger des couleurs, les assombrir ou les éclaircir, etc.
Catalog of Refactorings
Le catalogue qui suit respecte le format de l'ouvrage référence ayant introduit le concept.
Extract Constant
La valeur d'une propriété se répète.
Remplacer la valeur par une variable initialisée à cette même valeur.
=>
Motivations
Les motivations sont les mêmes que pour l'utilisation d'une constante dans un autre langage. Le code devient plus parlant grâce au nom judicieux de la constante. Mais surtout, la maintenance devient triviale en cas de changement de valeur.
Sass ne permet pas à proprement parlé la création de constantes mais uniquement de variables. Cela ne nous empêche pas d'exploiter les variables comme on le ferait pour déclarer une constante en Javascript.
Sass introduit également la notion de portée : une variable ne sera accessible qu'au sein du bloc où elle est déclarée (intéressant avec le Nesting). Une variable déclarée en dehors d'un bloc est considérée comme globale et pourra toujours être accédée, sauf si masquée par une autre déclaration.
Mechanics
- Créer une variable initialisée avec la valeur extraite
- Remplacer les occurrences de cette valeur dans l'ensemble des feuilles de style
- Vérifier à chaque remplacement que la variable est accessible. Le cas échéant, déplacer la variable au niveau du premier bloc parent commun. Ne jamais réduire la portée de la variable pour ne pas impacter les remplacements précédents.
- Compiler et tester
Exemples
Exemple : (avec Nesting)
Cette feuille de style utilise deux polices différentes. Une par défaut (Helvetica) et une seconde manuscrite pour mettre en avant quelques textes comme les titres ou les notes.
On constate que la police 'Cursive', sans-serif
est déclarée deux fois. On crée donc une variable
avec cette valeur :
On se lance ensuite dans les remplacements.
La variable créée se trouve hors de portée de la deuxième occurrence. Nous devons donc remonter la déclaration scope après scope jusqu'à trouver le premier qui satisfait les règles de visibilité.
La police Helvetica, sans-serif, bien qu'utilisée qu'une seule fois, gagnerait à être remplacée par une variable pour rendre explicite qu'il s'agit de la police par défaut mais aussi pour centraliser les choix de polices (Une modification est plus facile à faire si elle s'opère à un endroit bien défini). C'est ce que nous allons faire :
Extract Declaration Block
Plusieurs propriétés et leurs valeurs se répètent ou se ressemblent fortement.
Extraire ces déclarations dans un mixin dont le nom reflète l'intention du mixin.
=>
Motivations
Les motivations sont identiques à la création d'une méthode dans un autre langage : factoriser le code pour n'avoir qu'un seul endroit à modifier (DRY). Cela permet également d'introduire de la sémantique à l'aide du nom choisi.
Les mixins sont le terme choisi par Sass pour représenter ce qui se rapproche de nos méthodes en Java. Ce terme, inspiré du langage Ruby. n'est pas surprenant puisque Sass a connu le succès à travers le framework Ruby on Rails.
Mechanics
- Créer un nouveau mixin et choisir un nom reflétant l'intention ou le but du mixin.
- S'il est difficile de trouver un nom, c'est peut-être un signe que le choix des déclarations extraites n'est pas pertinent. Une déclaration est peut-être en trop. Les déclarations serait mieux réparties en deux mixins, ...
- Copier les déclarations retenues dans le mixin.
- Identifier si les déclarations contiennent des références à des variables. Il peut être nécessaire, surtout pour maximiser la réusabilité du mixin, de déclarer ces variables comme paramètres.
- Inspecter les déclarations en se demandant si une des valeurs mériterait d'être paramétrée (de manière à également maximiser la réusabilité du mixin). Créer un paramètre pour chaque valeur identifiée.
- Remplacer les déclarations extraites par une inclusion au mixin. Les éventuels arguments doivent coïncider parfaitement avec les valeurs initiales.
- Compiler et tester
Exemples
Exemple : Aucun paramètre
Les trois premières sont nécessaires pour obtenir une police manuscrite. Créons donc notre mixin :
Exemple : CSS3 Polyfill
Ce genre de code est devenu si habituel depuis l'arrivée du CSS3. Indispensable pour supporter les différentes versions des navigateurs, cela n'en reste pas moins regrettable.
Introduce Inheritance
Un élément possède deux classes sont l'un est plus spécifique que l'autre.
Étendre la classe spécifique de la classe générale.
=>
Motivations
Les classes CSS apportent de la sémantique à notre document (ex : footnote, warning, author, ...). Si on reprend
l'exemple, la classe error
nous signale une erreur. La classe error-fatal
va même plus
loin en nous indiquant que c'est une erreur fatale. D'un point de vue sémantique, seule cette dernière classe est
nécessaire. L'ajout de la classe error
est nécessaire en CSS pour factoriser les déclarations
communes à tous les types d'erreur.
Sass propose un mécanisme d'héritage pour représenter les relations "est un" entre classes.
L'héritage ne doit pas être confondu avec les mixins, Tous deux permettent de factoriser une liste de déclarations. Toutefois, les mixins sont mieux adaptés pour de la présentation (ex : rounded-box, manuscript, ...) afin d'éviter l'ajout de classes sans sémantique dans notre document.
L'héritage apporte avec lui tout son lot de complexité, parfaitement décrite par le créateur du langage dans Sass and Compass in Action.
Mechanics
- Identifier deux classes partageant une relation d'héritage au sein des pages HTML.
- Etendre de la classe la plus générale dans chacune des définitions des classes plus spécifiques.
- Supprimer la classe la plus générale uniquement dans les pages HTML et à condition qu'une des classes plus spécifiques soit présente sur cet même élément.
Exemples
Exemple :
Comme pour notre premier exemple, la classe note
est redondante et n'apporte aucune valeur d'un
point de vue sémantique. Rendons la relation d'héritage explicite grâce à Sass.
Extract File
La taille d'une feuille de style nuit à sa maintenabilité
Décomposer la feuille de style en plusieurs de manière à ce que chaque modification entraîne des changements dans une seule feuille de style.
=>
Motivations
Les feuilles de style commencent toujours petites mais ne le restent guère longtemps. Avec le temps, il devient de plus en plus difficile de localiser où les changements doivent être effectués.
De la même manière que l'on n'envisage pas de créer des classes Java de plusieurs milliers de lignes, pourquoi ne
pas en faire autant avec nos feuilles de style ? D'autant plus que des solutions ont toujours existé (plusieurs
balises link
ou la règle @import
).
Mais voilà, cette découpe est problématique pour les temps de réponse. Les deux solutions ne sont pas transparentes pour la navigateur qui a conscience de chacun des fichiers qui doivent être récupérés séparément.
Reste alors le recours à un outil de build (ex : grunt) pour concaténer l'ensemble des feuilles de style ou tout
simplement utiliser Sass qui reprend la syntaxe du @import
et veille à inclure les fichiers
physiquement lors de la compilation vers CSS. (Sass appelle ces fichiers inclut des partials).
Mechanics
- Créer un nouveau fichier en veillant bien à préfixer d'un underscore.
- Déplacer l'ensemble des sélecteurs/blocs de déclaration sélectionnés dans ce fichier.
- Ajouter la règle @import suivi du nom du fichier sans le underscore et sans extension.
Exemples
Exemple : Uniformisation
Voici la situation actuelle :
Sass nous permet d'uniformiser tout cela, tout en améliorant les temps de réponse de notre page.
Inline File
Des changements dans une feuille de style sont toujours accompagnés de changements dans une autre feuille de style.
Fusionner le contenu des deux feuilles de style.
=>
Motivations
Le fameux Divide and Conquer a ses limites. A trop diviser, on arrive à un découpage qui manque de cohésion. Si les modifications ne se résument jamais à une seule feuille de style, il y a fort à parier que le nombre de feuilles de style est trop important.
Réintégrer ces feuilles de style dans d'autres nous permet de revenir à davantage de cchésion. Sass n'est pas d'une grande utilité pour ce refactoring, présent par soucis de complétude avec les autres refactorings décrits.
Mechanics
- Identifier la feuille de style la plus adaptée pour recopier le contenu de notre feuille de style.
- Recopier l'intégralité du contenu en début de fichier si notre feuille de style était incluse avant ou en fin de fichier sinon.
- Supprimer l'import de notre feuille de style avant de la supprimer physiquement.
Introduce Nesting
Les sélecteurs sont trop longs, trop répétitifs
Exploiter l'imbrication pour simplifier les sélecteurs et améliorer la lisibilité.
=>
Motivation
La syntaxe CSS traite chaque sélecteur indépendamment. Malheureusement, en pratique, les sélecteurs sont souvent liés entre eux mais impossible de faire ressortir ces liens.
Sass avec sa syntaxe Nesting nous épargne beaucoup de répétitions et facilite grandement la lecture. Cette fonctionnalité mérite à elle seule l'utilisation de Sass.
Attention toutefois à ne pas reproduire à l'identique la structure HTML en CSS. N'oublions que Sass doit compiler nos feuilles de style vers du CSS et que des sélecteurs trop précis peuvent dégrader les performances.
Mechanics
- Identifier un sélecteur ayant comme préfixe un autre sélecteur.
- Recopier le sélecteur (sans le préfixe) et l'ensemble de son bloc de déclarations à la fin du bloc de déclaration du sélecteur parent.
- Supprimer la déclaration avec l'ancien sélecteur.
Exemples
Exemple : Décomposition par page
L'utilisation du nesting améliore la clarté des feuilles de style. (Discutable sur cet exemple très simple)
Contre exemple : Attention au CSS généré
Compilé en CSS, cela devient moins séduisant, surtout pour inspecter avec Firebug ou autre...
Introduce Arithmetic
Un nombre magique ne parait pas si magique.
Remplacer ce nombre par une expression mathématique expliquant sa valeur.
=>
Motivations
Les nombres magiques sont omniprésents en CSS. Voir le lien entre deux nombres est loin d'être évident, surtout lorsque c'est deux nombres sont présents dans deux feuilles de style différentes. Bien souvent, on change un des nombres est le résultat n'est pas celui espéré. S'en suit alors une série de modifications qui visent à recalculer les nombres liés.
Le CSS nous aide déjà grâce à l'utilisation de mesures relatives (pourcentage, em). Ex :
SASS va plus loin. Grâce à l'utilisation d'expressions, les calculs deviennent apparents. Grâce à l'utilisation de variables (voir Extract Constant), ils deviennent encore plus compréhensibles. Leur modification devient alors aisée.
Cela ne remet pas en cause l'utilisation d'unités relatives qui doit être privilégiées lorsque cela est applicable.
Sass supporte plusieurs types (booléen, string, nombre, couleur, liste) et propose même des fonctions.
Mechanics
- Créer une variable (Extract Constant) contenant la valeur dont d'autres valeurs dépendent.
- Pour chaque variable dépendante
- Vérifier à chaque remplacement que la variable est accessible. Le cas échéant, déplacer la variable au niveau du premier bloc parent commun. Ne jamais réduire la portée de la variable pour ne pas impacter les remplacements précédents.
-
Remplacer l'ancienne valeur par une expression utilisant la variable précédemment créée.
- Attention à la priorité des opérateurs. Utiliser des parenthèses pour changer l'ordre d'évaluation ou pour clarifier l'expression.
- Compiler et tester
Exemples
Exemple : Coloration tableau
See the Pen wBMmVP by Julien Sobczak (@julien-sobczak) on CodePen.
L'arrivée du sélecteur nth-child
nous épargne l'ajout de nombreuses classes CSS sans valeur sémantique :
4 couleurs sont nécessaires pour parvenir au résultat escompté. Deux d'entre elles correspondent aux couleurs principales (celles à alterner). Les deux autres sont étroitement liées puisqu'elles représentent ces mêmes couleurs avec de légères variations pour représenter le croisement des lignes et colonnes.
Modifier les couleurs de base nous obligent à recalculer les couleurs secondaires.
Comparons avec cette autre solution, reposant sur les expressions SASS permettant de créer de nouvelles couleurs à partir d'existantes :
La relation entre les couleurs devient de suite plus parlante et tenter différentes couleurs devient alors un vrai plaisir.
Introduce Conditional Branching
Beaucoup de duplications entre une suite de sélecteurs/blocs de déclarations.
Utiliser les structures de contrôles habituelles pour factoriser le code commun.
=>
Motivations
Le CSS a beau être un langage déclaratif, il n'en reste pas moins que nos bonnes vieilles instructions de contrôle héritées des langages impératifs se font parfois sentir absentes.
Même si les cas d'utilisation restent marginaux, nous verrons deux exemples où les structures de contrôle se révèlent indispensables.
Exemple
Exemple 1 : Les sprites
Les sprites sont présentes dès la première règle du classique de Steve Souders, High Performance Web Sites. Leur impact sur les temps de réponse est conséquent. Là, où des dizaines de requêtes HTTP étaient nécessaires auparavant, une seule suffit grâce à l'utilisation de sprites.
Côté CSS, cela est rendu possible grâce à la propriété background-position
.
Prenons pour exemple le sprite suivant :
Voici un exemple de code CSS nécessaire pour exploiter les différentes icônes.
Pour cet exemple, nous allons définir une variable pour contenir la liste de nos catégories, avant d'itérer dessus pour générer l'ensemble des déclarations. Voici le résultat :
Grâce à Sass et l'utilisation de structures de contrôle, notre application devient à la fois plus performante et plus maintenable. L'ajout d'un nouveau libellé revient simplement à modifier notre variable.
Références
- [Fow1]
- Refactoring: Improving the Design of Existing Code
- [Sou1]
- don’t use @import
A voir également
- Refactoring: Improving the Design of Existing Code pour découvrir le livre qui a introduit le terme Refactoring.
- Sass and Compass in Action pour aller plus loin avec Sass, découvrir ses quelques subtilités et les avantages à ajouter un framework comme Compass afin de bénéficier de l'expérience de nombreux designers.