Cet article est la transcription d’une intervention donnée à ParisJS en octobre 2015, sur les liens entre la complexité métier et l’expérience utilisateur.
Aujourd’hui je voudrais vous parler de « comment gérer la complexité métier ». J’ai appelé ça « Happy Path et Sad Path », mais ça veut juste dire « Le chemin où tout se passe bien, et celui où on butte dans tous les cas spéciaux », et où on doit vraiment gérer la complexité.
Au début on présentait Captain Train en disant « c’est un site pour acheter ses billets de train, super rapidement ». Historiquement il a été conçu comme une alternative au site de l’opérateur historique : plus rapide, plus simple.
Aujourd’hui l’ambition est plus large : Captain Train vend des billets dans toute l’Europe. Mais on a toujours cette attention à la simplicité et à la rapidité.
La question est : comment conserver cette rapidité malgré la complexité ?
Je ne vais pas parler de performances chiffrées, mais plutôt de l’impression de simplicité. La frontière entre « rapide » et « simple » est très floue – et souvent, quand une tâche est simple, qu’on n’a pas le cerveau encombré de plein de choses, on va plus vite pour faire cette tâche. Et on en garde un meilleur souvenir.
Happy Path
Sur Captain Train, le Happy Path (celui où tout se passe bien pour l’utilisateur) est très rapide. Une dizaine de clics tout au plus pour acheter un billet de train.
En pratique ça se fait en cinq écrans (en comptant large) :
1 - La recherche
2 - La réservation
3 - Le panier
4 - Le paiement
5 - Le billet
Ça a l’air simple.
Sad Path
Mais tout ne se passe pas toujours aussi bien. Il y a des erreurs, des avertissements, des problèmes, de la complexité métier. C’est le cas dans tous les domaines que vous pouvez rencontrer : le train, la presse, la restauration, l’astronomie, la chirurgie, tous les domaines.
Pour continuer sur l’exemple du train, on va faire un tour de tout ce à quoi vous échappez la plupart du temps, mais qui peut arriver.
Pour commencer, au moment de la réservation, il y a beaucoup de choses qui peuvent se passer.
Par exemple, parfois le train ne part pas de la gare que vous avez demandée.
(Ou il n’arrive pas à la gare demandée, c’est possible aussi.)
Dans ce cas on vous prévient, quand même.
Jusqu’ici, c’est d’une complexité raisonnable.
Cette fois-ci vous décidez de prendre un train OUIGO (vous savez, les TGV low-cost). À bord des OUIGO, les bagages sont payants. Une cause fréquente de réclamations est que l’utilisateur monte dans le train avec trop de bagages.
Si vous n’avez pas sélectionné de bagages, on vous rapelle que vous ne pourrez avoir qu’un bagage « cabine ».
Pour faire une réservation, OUIGO requiert absolument la date de naissance des voyageurs.
Donc au moment où vous voulez réserver, si on n’a pas votre date de naissance, on vous la demande (et on s’en souvient pour les prochaines fois).
OUIGO requiert également le numéro de téléphone. Pareil, si on ne l’a pas on le demande, et on s’en souvient pour les prochaines fois.
J’ai séparé les dialogues pour la démo, mais en vrai si on a besoin de toutes ces informations en même temps, on va même compacter le processus, et les demander en une seule fois dans le même dialogue.
Et ça y est, c’est bon, on peut réserver.
Autre possibilité : vous réservez un train allemand, ou italien. Ça se passe encore un peu différemment. Les trains allemands et italiens demandent des documents d’identification, pour justifier de votre identité à bord du train, en plus du billet.
Donc avant de réserver, on vous demande si vous voulez utiliser votre carte bancaire pour vous identifier, ou votre carte d’identité.
Oh, et la Deutsche Bahn demande une adresse, aussi. Pareil, si on ne l’a pas, on la demande et on s’en souvient pour les prochaines fois.
Ah, et si vous choisissez de présenter votre carte d’identité, la DB demande le numéro de la carte. Et l’adresse indiquée sur la carte d’identité, aussi. Pareil, on la demande, on s’en souvient pour les prochaines fois.
On arrive enfin au Panier, juste avant le paiement. Là encore il peut se passer des choses.
Par exemple vous cliquez sur « Payer », mais vous avez déjà acheté un billet identique. C’est à dire un billet pour le même trajet, le même jour, avec les mêmes personnes qui voyagent.
On est sympa, on prévient, et on vérifie que c’est bien ça que vous voulez, quand même.
Autre problème qui peut arriver : vous allez payer deux fois le même billet.
Ça a l’air bête, mais ça peut arriver quand on fait une recherche, puis qu’on revient plus tard, on en fait une autre, on paye – et paf le billet de la recherche précédente était en fait là aussi.
Pareil, on vous demande de vérifier.
Si vous persistez, vous arrivez enfin au paiement.
Normalement ça se passe bien. Mais si votre banque nous demande de vous authentifier plus sérieusement, vous allez voir le décevant dialogue de confirmation 3D-Secure, celui qui vous envoie un SMS sur votre téléphone mobile.
Il y a pas mal d’abandons d’achat à cet étape, parce qu’il y a beaucoup de possibilités que ça ne fonctionne pas, d’une manière ou d’une autre. Ce dialogue joue donc sur plusieurs choses :
- Rester dans le contexte de l’application (pas de popup ou d’iframe plein écran),
- Afficher un message un peu léger pour dédramatiser,
- Indiquer qui est probablement à blâmer en cas d’erreur bizarre (c’est souvent votre banque),
- Afficher ce dialogue le moins souvent possible (si on a vraiment confiance dans votre paiement, on vous épargne le 3D-Secure).
Ça y est, le paiement s’est bien passé. Vous avez votre billet.
Enfin presque. À ce stade, votre billet est payé, mais pas encore émis. On prétend que vous l’avez déjà, mais ce n’est pas complètement vrai, c’est « en cours ».
Par exemple, si on a vraiment des doutes sur votre paiement, malgré le 3D-Secure, on va jeter un œil manuellement, quand même, avant de vous générer un billet qui vaut de l’argent. Ça nous permet de lutter contre la fraude.
En tout cas on vous informe de ce qui se passe, et de combien de temps ça va prendre.
Une fois qu’on est raisonnablement confiant dans votre paiement, l’émission est lancée.
Normalement ça prend moins d’une minute – mais parfois les systèmes de réservation de la SNCF ou de la DB ont des hoquets, et il faut attendre quelques heures que ça revienne.
Alors pareil, on indique ce qui se passe.
On a réussi à émettre votre billet, mais catastrophe, le serveur qui permet de récupérer les e-billets en PDF est en rade.
Allez, on vous informe, quand même.
Ça y est, on a enfin récupéré votre e-billet en PDF.
Enfin vos billets : ici il y a deux passagers, et ce transporteur génère un PDF par passager. Donc deux PDF. Mais ça dépend.
Et puis parfois le mode de retrait est différent. Par exemple si vous prenez un train TER, le e-billet n’est souvent pas disponible. Dans ce cas il faut retirer un billet cartonné à une borne en gare.
C’est inhabituel, donc on a intérêt à l’indiquer clairement.
Tout ça dans un message d’une seule ligne. Pour info, il y a plus d’un millier de combinaisons différentes de ce message. Ça dépend :
- du transporteur (SNCF, ou Thalys, Trenitalia, DB…)
- du mode d’émission du billet (e-billet ou cartonné)
- du programme de fidélité (carte Grand Voyageur, ou autre…)
- de l’application (application web, ou mobile, ou email)
Contraintes métier
Toutes ces problématiques sont liées au train : ce sont nos contraintes métiers. Mais tout le monde a ses propres contraintes métier (même si ce n’est pas le métier du train).
Comment gérer la complexité qui vient des contraintes métier d’une manière générale ? Comment éviter de tout complexifier juste à cause de quelques contraintes métier ?
Déterminer ce qu’on voudrait faire est plutôt facile :
- Collecter les informations progressivement (mais oui !),
- Prévenir les confusions fréquentes (bien sûr),
- Informer en cas d’erreur (évidemment).
Ce sont des choses plutôt évidentes, nous voulons tous ça.
Éviter l’effet sapin-de-Noël
Mais si on n’y prend pas garde, on répond à des besoin évidents par des solutions évidentes : pour chaque confusion possible, rajouter un avertissement ; pour chaque cas spécial, rajouter un « if » dans le code.
Par exemple sur Captain Train on aurait pu mettre des dialogues systématiques pour :
- Définir les documents d’identification,
- Récapituler la commande avant le paiement,
- Attendre l’émission (avec un petit indicateur de chargement et tout).
Mais ça détériore l’expérience utilisateur, et ça rend le code de moins en moins maintenable.
Garder simple ce qui est simple
Ce qu’on veut vraiment, ce ne sont pas les solutions naïves, c’est garder simple ce qui est simple – et gérer au cas-par-cas quand ça se complique.
-
Partir du cas optimiste : imaginer comment les choses devraient être dans le cas le plus simple – et éviter de ralonger ce chemin.
-
Rajouter des étapes seulement quand c’est nécessaire : avoir une grande granularité sur les informations demandées, demander seulement les informations manquantes, s’en souvenir.
-
Contextualiser les messages d’avertissement et d’erreur.
Pour rebondir sur ce dernier point, on essaie de résister à la tentation d’avoir des messages d’erreurs génériques, comme par exemple :
« Ce train ne part pas de la gare demandée. » 😕
À la place on utilise toutes les informations du contexte pour afficher un message pertinent : quelle était la gare demandée initialement, quelle est celle finalement remontée, et est-ce que c’est tout près ou vraiment loin :
« Ce train ne part pas de Paris, mais de Massy-Palaiseau, à 30km de Paris. » 😌
Autre exemple :
« Pour faire cette opération, contactez votre administrateur. » 😕
Allons bon, qui est mon « administrateur », déjà ? Et si j’arrive à le joindre, je lui demande d’effectuer quelle opération ?
« Pour ajouter un passager, contactez Delphine ou Jean-Luc. » 😌
Certains messages d’avertissement ou d’erreur sont générés à partir de 5 ou 10 bouts de phrases, et 50 lignes de code. Pour un seul message. C’est du travail, mais ça en vaut la peine.
Des promesses, toujours des promesses
Il y a quelques patterns qu’on utilise régulièrement pour améliorer l’expérience utilisateur :
- Chaîner les dialogues avec des promesses,
- Tester les erreurs en priorité,
- Faire des tests utilisateurs.
Le premier pattern concerne le code. Une des manières de ne pas avoir à rajouter des « if » à chaque cas spécial est d’encapsuler les séquences de plusieurs dialogues dans des promesses.
Par exemple, quand on clique sur le bouton « Ajouter au Panier », on construit cette chaîne de promesses :
function addToCartClicked() {
return luggagesDialog.prompt().then(() => {
return birthdateDialog.prompt();
}).then(() => {
return identificationDialog.prompt();
}).then(() => {
return book();
});
}
Si le dialogue n’a pas besoin d’être affiché, la promesse est résolue immédiatement, et on passe au dialogue suivant.
Par contre, s’il est nécessaire d’afficher un dialogue, la promesse sera résolue seulement au moment où l’utilisateur cliquera sur “OK”. Et si à une étape l’utilisateur clique sur “Annuler”, la promesse est rejetée, et toute les étapes suivantes sont court-circuitées.
Un autre exemple, quand on clique sur le bouton « Payer » :
function payButtonClicked() {
return similarTicketDialog.prompt().then(() => {
return duplicateTicketDialog.prompt();
}).then(() => {
return invalidCouponDialog.prompt();
}).then(() => {
return payDialog.prompt();
});
}
Ça correspond exactement à notre cas d’usage : rajouter des dialogues optionnels sur le chemin, tout en maintenant le code lisible.
Tester les cas d’erreur en priorité
Les messages d’erreur sont les premiers à souffrir quand on refactore, ou que l’API change, etc.
Le Happy Path est le plus fréquent, il est utilisé en permanence : s’il casse, ça se verra forcément. Les cas d’erreurs, c’est plus compliqué. Finalement, pour les tests d’intégrations il vaut mieux tester les cas d’erreur en priorité. On n’est pas encore très bon élèves là dessus, mais on essaie.
visit('/pay');
click('Payer');
andThen(function() {
assert.present('Le paiement a été refusé');
});
Tests utilisateurs
Les tests utlisateurs nous ont permis de clarifier certains éléments de l’interface. Parce que épurer, c’est bien, mais parfois on a besoin de rendre les choses plus évidentes, au contraire.
Par exemple il a fallu qu’on aille dans un café faire des tests utilisateurs pour voir que les gens avaient du mal à trouver comment ajouter des passagers, ou comment ajouter une carte de réduction.
Alors on a rendu ça plus clair, sur le fond et sur la forme. D’abord en signalant un peu mieux l’ajout de passagers, avec un bouton « + », purement visuel, qui n’apporte rien fonctionnellement, si ce n’est un signal « Pour ajouter, c’est par ici. »
Ensuite en signalant mieux la possibilité d’ajouter des cartes de réductions.
Pour résumer ce qui peut vous être utile :
-
Maitrisez vos contraintes métiers : ça permet de simplifier le cas le plus fréquent, et de demander des opérations supplémentaires au moment où vous en avez vraiment besoin.
-
Une interface simple aura l’air rapide à l’utilisation, parce qu’elle diminue la charge cognitive. (Et pour moi c’est orthogonal aux performances brutes.)
Merci.