Overreacted

Éléments d’ingénierie UI

2018 M12 30 • ☕️☕️ 10 min read

Dans mon article précédent, je parlais d’admettre les lacunes dans nos connaissances. Vous pourriez en conclure que je suggère de se résigner à la médiocrité. Il n’en est rien ! C’est juste que nous travaillons dans un domaine extrêmement vaste.

Je suis fermement convaincu qu’on peut « commencer n’importe où » et que vous n’avez pas besoin d’apprendre les technologies dans un ordre particulier. Mais j’accorde aussi une grande valeur au développement d’une expertise. Personnellement, j’ai toujours été principalement intéressé par la création d’interfaces utilisateurs.

J’ai ruminé ces derniers temps sur les choses que je connais bien et qui ont de la valeur à mes yeux. Bien sûr, je suis à l’aise avec quelques technologies (ex. JavaScript et React). Mais les leçons plus importantes tirées de l’expérience sont difficiles à définir. Je n’avais jamais tenté de les formaliser par écrit. Voici ma première tentative d’établir un catalogue de ces leçons et d’en décrire quelques-unes.


On trouve de nombreux « parcours d’apprentissage » sur les technologies et les bibliothèques. Mais quelle bibliothèque sera à la mode en 2019 ? Et 2020 alors ? Devriez-vous apprendre Vue ou React ? Et Angular ? Que choisir, Redux ou Rx ? Avez-vous besoin d’apprendre Apollo ? REST ou GraphQL ? On est rapidement submergés. Et si l’auteur avait tort ?

Mes plus grands progrès en termes d’apprentissage sont sans rapport avec une technologie particulière. En fait, c’est toujours lorsque je me battais avec un problème concret d’UI que j’ai appris le plus. Parfois, je découvrais ensuite des bibliothèques ou des motifs de conception qui m’aidaient. D’autres fois, je créais mes propres solutions (des bonnes comme des mauvaises).

C’est cette combinaison entre la compréhension des problématiques, l’expérimentation de solutions, et l’application de différentes stratégies qui m’a fourni les expériences d’apprentissage les plus gratifiantes de ma vie. Cet article se concentre juste sur les problématiques.


Si vous avez déjà travaillé sur une interface utilisateur, vous avez sans doute déjà fait face à certains de ces défis—directement ou en utilisant une bibliothèque. Quoi qu’il en soit, je vous encourage à créer une toute petite appli sans bibliothèques, et à vous amuser à reproduire et résoudre ces problèmes. Ils ont tous plusieurs solutions valables. On apprend en explorant la problématique et en essayant diverses approches et compromis possibles.


  • Cohérence. Vous cliquez sur un bouton « J’aime » et le texte se met à jour : « Vous et 3 autres amis avez aimé cet article. » Vous cliquez à nouveau, le texte revient à sa version d’origine. Facile. Mais peut-être que l’écran recèle d’autres libellés basés sur la même donnée. Peut-être que d’autres indicateurs visuels (tels que la couleur de fond du bouton) ont besoin de changer aussi. La liste des « autres » qui avait été récupérée depuis le serveur et devient visible au survol devrait désormais inclure votre nom. Si vous naviguez sur un autre écran puis revenez, l’article ne devrait pas « oublier » que vous l’avez aimé. La cohérence locale à elle seule recèle tout un tas de subtilités. Mais en prime, d’autres utilisateurs pourraient modifier les données que nous affichons (ex. en cliquant aussi « J’aime » dans l’article que nous consultons). Comment alors synchroniser les données entre les différentes parties de l’écran ? Quand et comment rendre nos données locales cohérentes avec celles du serveur, et inversement ?

  • Réactivité. Les gens ne tolèrent une absence de retour visuel pour leurs actions que sur un temps très court. Pour des actions continues, comme des gestuelles tactiles et du défilement, cette limite est basse (même rater un simple intervalle de rendu de 16ms donne une impression saccadée). Pour des actions discrètes comme les clics, les recherches sur le sujet nous disent que les utilisateurs perçoivent comme identiques les vitesses de tout retour inférieur à 100ms. Si une action prend plus longtemps, il nous faut afficher un indicateur visuel. Mais la meilleure manière de s’y prendre est parfois contre-intuitive. Ces indicateurs risquent de faire « sursauter » la mise en page, et passer à travers plusieurs « stades » de chargement peut donner une impression de plus grande lenteur que celle obtenue sans indicateurs. Dans le même esprit, traiter une interaction en moins de 20ms mais au prix d’une opportunité de rendu ratée peut sembler plus lent que la traiter en 30ms pour préserver la fluidité des animations. Les cerveaux ne sont pas de simples bancs de mesure. Comment s’assurer que nos applis réagissent efficacement à différents types d’interaction ?

  • Latence. Tant les calculs que les accès réseaux prennent du temps. Parfois nous pouvons nous permettre d’ignorer le coût de calcul si ça n’altère pas la réactivité sur les appareils que nous visons (et assurez-vous de tester votre appli sur des appareils bas de gamme). Mais on ne peut pas faire l’impasse sur la latence réseau—qui peut prendre des secondes ! Notre appli ne peut pas juste geler en attendant que les données ou le code se chargent. Ce qui implique que toute action qui nécessite de nouvelles données, du nouveau code, ou des ressources supplémentaires est potentiellement asynchrone et doit gérer le cas « en cours de chargement ». Mais ça peut survenir dans pratiquement n’importe quel écran. Comment gérer agréablement la latence sans afficher une « cascade » de spinners ou se retrouver avec des « trous » dans notre interface ? Comment éviter de faire « sursauter » la mise en page ? Et comment modifier des dépendances asynchrones sans avoir à tout « reconnecter » à chaque fois ?

  • Navigation. On s’attend à ce que l’UI reste « stable » quand on interagit avec. On ne devrait pas voir des trucs disparaître juste sous notre nez. La navigation, qu’elle ait démarré au sein de l’appli (ex. en cliquant un lien) ou en raison d’un événement externe (ex. clic sur le bouton Retour du navigateur), devrait elle aussi respecter ce principe. Par exemple, si j’alterne entre les onglets /profile/likes et /profile/follows d’un écran de profil, ça ne devrait pas effacer le champ de recherche situé hors des onglets. Même naviguer vers un autre écran devrait être analogue à entrer dans une pièce. Les gens s’attendent à ce qu’ils puissent revenir ensuite et retrouver les choses telles qu’ils les ont laissées (avec, peut-être, des trucs en plus). Si vous êtes en plein milieu d’un flux, cliquez sur un profil, puis revenez, ce serait énervant de perdre votre position dans le flux—ou de devoir attendre qu’il se charge à nouveau. Comment architecturer notre appli pour qu’elle gère une navigation libre sans perdre d’importants éléments de contexte ?

  • Péremption. On peut rendre le bouton « Retour » instantané en mettant en place un cache local. Dans ce cache, on peut « mémoriser » des données pour y accéder rapidement même si on aurait théoriquement dû les recharger. Mais la mise en cache amène son lot de problèmes. Les caches peuvent se périmer. Si je change un avatar, il devrait être mis à jour dans le cache aussi. Si je publie un nouvel article, il a besoin d’apparaître immédiatement dans le cache, à moins d’invalider carrément le cache. Ça devient vite délicat et sujet à erreurs. Que se passe-t-il si la publication échoue ? Combien de temps le cache devrait-il rester stocké ? Quand je rafraîchis le flux, est-ce que je le combine avec celui présent dans le cache, ou est-ce que je vire sa version cachée ? Et comment gérer, dans le cache, les notions nécessaires à la pagination et au tri ?

  • Entropie. La deuxième loi de la thermodynamique dit quelque chose comme « avec le temps, ça devient le bordel » (je paraphrase, hein). Ça vaut pour les interfaces utilisateurs aussi. On ne peut pas prédire les interactions exactes des utilisateurs, pas plus que leur séquence. À tout moment, notre appli peut être dans un état parmi un énorme nombre de possibles. On fait de notre mieux pour rendre le résultat prévisible et limité par notre conception. En fait, on n’a pas envie de regarder une capture d’écran de bug et de se demander « mais purée comment ils en sont arrivés là ?! ». Pour N états possibles, il y a N×(N–1) transitions possibles entre eux. Par exemple, si un bouton peut être dans un état parmi 5 (normal, activé, survolé, dangereux, désactivé), le code qui met à jour le bouton doit être correct pour 5×4=20 transitions possibles—ou en interdire certaines. Comment apprivoiser cette explosion combinatoire d’états possibles et assurer un rendu visuel maîtrisé ?

  • Priorité. Y’a des choses plus importantes que d’autres. Une boîte de dialogue pourrait avoir besoin de sembler être physiquement « au-dessus » du bouton qui l’a invoquée, et de « déborder » des limites de mise en page de son conteneur. Une tâche fraîchement planifiée (ex. en réponse à un clic) peut s’avérer plus importante à traiter qu’une tâche au long cours déjà démarrée (ex. pré-afficher les prochains articles sous la limite de défilement de la fenêtre). Alors que notre appli s’étoffe, les parties du code écrites par des personnes et équipes différentes entrent en concurrence pour des ressources limitées telles que le processeur, le réseau, l’espace disponible à l’écran, et le budget de poids du bundle. Parfois on peut classer les candidats sur une échelle partagée « d’importance », comme la propriété CSS z-index. Mais ça finit rarement bien. Chaque développeur est biaisé en faveur de son code lorsqu’il en estime l’importance relative. Et si tout est important, rien ne l’est ! Comment amenons-nous des composants indépendants à coopérer plutôt qu’à se battre pour les ressources ?

  • Accessibilité. Les sites web inaccessibles ne sont pas un problème à la marge. Par exemple, au Royaume-Uni 1 personne sur 5 souffre d’au moins une forme de déficience. (Voici une infographie bien fichue.) (pareil en France, soit 14 millions de personnes, NdT) J’en ressens moi-même les effets. J’ai beau n’avoir que 26 ans, j’ai du mal à lire les sites utilisant des fontes fines ou un contraste faible. J’essaie de moins utiliser mon trackpad, et je crains le jour où j’aurai à naviguer sur des sites mal implémentés en n’utilisant que mon clavier. Il faut qu’on arrête de pondre des applis horribles pour les personnes ayant des difficultés—et la bonne nouvelle, c’est que dans ce domaine il y a beaucoup d’améliorations qui sont à portée de main. Ça commence par la sensibilisation, l’éducation et l’outillage. Mais nous devons aussi rendre le chemin vertueux facile pour les développeurs. Comment peut-on faire de l’accessibilité un état par défaut plutôt qu’une réflexion après coup ?

  • Internationalisation. Notre appli doit fonctionner dans le monde entier. Ce n’est pas seulement que les gens parlent des langues différentes, mais on doit aussi prendre en charge des mises en page de droite à gauche sans surcharger les ingénieurs produits. Comment gérer diverses langues sans sacrifier sur la latence ou la réactivité ?

  • Livraison. Il faut bien que le code de notre appli atteigne l’ordinateur de l’utilisateur. Quels format et transport utiliser ? Ça peut sembler évident, mais il y a plein de compromis là aussi. Par exemple, les applis natives ont tendance à précharger tout leur code, ce qui implique que l’appli a un poids énorme. Les applis web ont tendance à avoir un poids initial plus petit, au risque d’introduire de la latence en cours d’utilisation. Comment choisir à quels endroits introduire de la latence ? Comment optimiser nos mécanismes de livraison en fonctions des typologies d’utilisation de l’appli ? De quel genre de données aurions-nous besoin pour mettre en place une solution optimale ?

  • Résilience. Vous aimez peut-être les bugs (insectes, NdT) si vous êtes féru·e d’entomologie, mais je doute qu’ils vous éclatent au sein de vos programmes. Et pourtant, certains de vos bugs atterriront inévitablement en production. Que se passe-t-il alors ? Certains bugs causeront un comportement certes incorrect mais bien défini. Par exemple, peut-être que votre code produit un affichage incorrect sous certaines conditions. Mais si le code de rendu plante ? On ne peut plus raisonnablement continuer puisque l’affichage est incohérent. Un plantage au sein du rendu d’un article ne devrait pas « massacrer » le flux complet, ou le placer dans un état semi-cassé qui entraînerait sûrement d’autres plantages par la suite. Comment écrire du code de façon à isoler les échecs de rendu et de récupération de données, pour que le reste de l’appli puisse continuer à tourner ? Comment introduit-on de la tolérance à l’erreur dans des interfaces utilisateurs ?

  • Abstraction. Dans une toute petite appli, on peut coder en dur plein de cas spéciaux pour traiter les problèmes énumérés ci-dessus. Mais les applis ont tendance à grossir. On veut continuer à pouvoir réutiliser, décliner, et refusionner des parties de notre code, et travailler dessus à plusieurs. On veut pouvoir définir des frontières claires entre les périmètres de compétence des différents contributeurs, afin d’éviter de rigidifier des logiques applicatives qui pourraient évoluer souvent. Comment créer des abstractions qui masquent les détails d’implémentation d’une partie donnée de l’UI ? Comment éviter de voir des problèmes résolus refaire surface au fil de la croissance de notre appli ?


Bien sûr, il y a des tas de soucis que je n’ai pas mentionnés. Cette liste n’est en rien exhaustive ! Par exemple, j’ai passé sous silence la collaboration entre designers et ingénieurs, ainsi que le débogage et les tests. Une autre fois, peut-être.

Il est tentant de lire cette liste de problématiques en se disant qu’une bibliothèque bien précise pour les vues ou la récupération de données apporte la solution. Mais je vous encourage à imaginer que ces bibliothèques n’existent pas, et à relire cet article avec cet état d’esprit. Comment attaqueriez-vous ces sujets vous-même ? Essayez d’en résoudre quelques-uns dans une toute petite appli ! (J’adorerais voir le résultat de vos expérimentations sur GitHub—n’hésitez pas à me tweeter votre réponse.)

Ce que je trouve intéressant avec ces problématiques, c’est que pour la plupart, on les retrouve dans toutes les tailles d’applis. On les repère aussi bien dans de petits composants comme de l’autocomplétion ou des infobulles, que dans des applis énormes comme Twitter ou Facebook.

Réfléchissez à un élément d’UI non trivial dans une appli que vous aimez utiliser, et faites le tour de cette liste de sujets. Pouvez-vous décrire certains des compromis qu’ont retenus les développeurs de cet élément ? Essayez de recréer un comportement similaire à partir de zéro !

J’ai énormément appris sur l’ingénierie UI en expérimentant autour de ces problématiques dans de petites appli sans recourir à des bibliothèques. Je recommande cet exercice à quiconque souhaite acquérir une compréhension plus profonde des compromis de l’ingénierie UI.