- Ce guide actualise de façon moderne les principes UNIX traditionnels et propose des lignes directrices open source pour concevoir des CLI agréables à utiliser pour les humains tout en restant robustes pour l’automatisation
- Une bonne CLI doit fonctionner en étant orientée humain tout en respectant les conventions comme
stdout/stderr, les codes de sortie, les pipes et JSON afin de se combiner naturellement avec d’autres programmes - L’aide, la documentation, les messages d’erreur et la sortie doivent permettre à l’utilisateur de savoir quoi faire ensuite, et
--help, les exemples, les suggestions, les indicateurs de progression et les explications d’état y jouent un rôle clé - Les arguments, drapeaux, interactions, réglages et variables d’environnement doivent être pensés à la fois pour les scripts et l’usage dans le terminal, et il est plus sûr d’éviter de recevoir directement des secrets via des drapeaux ou des variables d’environnement
- Le nom, la distribution et jusqu’à la collecte d’analyses d’une CLI ne doivent pas nuire au sentiment de contrôle de l’utilisateur ni à la compatibilité à long terme, et même lorsqu’on rompt avec les conventions, l’objectif doit être clair
Philosophie fondamentale de la conception CLI
- Ce guide est un document open source qui fournit, à partir des principes UNIX traditionnels, des principes de conception modernes pour les programmes en ligne de commande ainsi que des recommandations concrètes
- Par le passé, la ligne de commande était un environnement orienté machine proche d’un REPL sur une plateforme de scripting, mais les CLI actuelles ont davantage un caractère orienté humain en tant qu’interface utilisateur textuelle donnant accès à divers outils, systèmes et plateformes
- La ligne de commande a des contraintes anciennes et des conventions particulières, mais elle peut être utilisée sur presque tous les ordinateurs portables, convient aussi bien à l’usage interactif qu’à l’automatisation, et évolue moins vite que d’autres composants du système
- Ce guide ne traite pas des programmes plein écran pour terminal comme emacs ou vim, et ne dépend d’aucun langage de programmation ni d’aucune chaîne d’outils particulière
Des CLI orientées humain mais composables
- Si une CLI est avant tout un outil utilisé par des humains, il faut penser d’abord aux humains
- Beaucoup de programmes CLI visent les utilisateurs humains tout en conservant tels quels des schémas d’interaction hérités d’une conception centrée sur la machine
- La philosophie UNIX, où de petits programmes fonctionnent ensemble via des interfaces propres, reste importante
- Des conventions comme l’entrée standard, la sortie standard, la sortie d’erreur, les signaux et les codes de sortie rendent possible la composition entre programmes
- Le texte brut ligne par ligne est facile à transmettre par pipe, et JSON est utile lorsqu’il faut des données plus structurées
- La cohérence est la clé pour transformer le coût d’apprentissage d’une CLI en efficacité à long terme
- En suivant les modèles existants, les utilisateurs peuvent plus facilement deviner les commandes et les options
- Quand les conventions nuisent à l’utilisabilité, on peut les rompre avec prudence
- Une bonne CLI ne doit être ni trop silencieuse ni trop bavarde, mais fournir à l’utilisateur exactement le niveau d’information nécessaire
- On considère souvent qu’une CLI est moins facile à découvrir qu’une GUI, mais on peut améliorer son accessibilité à l’apprentissage grâce à une aide complète, des exemples, des suggestions de commandes suivantes et des indications sur la marche à suivre en cas d’erreur
Interaction conversationnelle et robustesse
- L’usage de la ligne de commande ressemble davantage à une conversation faite de plusieurs essais et corrections qu’à une simple exécution unique
- En cas de saisie incorrecte, on peut proposer des corrections si possible
- On peut montrer clairement l’état intermédiaire d’un travail en plusieurs étapes
- On peut demander une confirmation avant une opération dangereuse
- Une CLI doit être réellement robuste, tout en donnant aussi à l’utilisateur le sentiment qu’elle l’est
- Elle doit gérer avec élégance les entrées inattendues
- Quand c’est possible, les opérations doivent être idempotentes
- Elle ne doit pas afficher par défaut des stack traces effrayantes
- L’empathie est importante pour donner à l’utilisateur le sentiment que le logiciel est de son côté
- Il ne s’agit pas d’utiliser des émojis ni de gamifier l’expérience, mais de concevoir soigneusement l’outil pour aider l’utilisateur à réussir
- L’écosystème du terminal comporte beaucoup d’incohérences et de confusion, mais ses faibles contraintes ont permis de nouvelles inventions
- Il faut suivre les modèles existants, tout en pouvant abandonner délibérément des standards qui nuisent à la productivité ou à la satisfaction des utilisateurs
Règles de base à respecter absolument
- Il faut utiliser si possible une bibliothèque d’analyse des arguments en ligne de commande
- En cas de succès, le code de sortie doit être
0, et en cas d’échec, une valeur différente de 0 doit être renvoyée- Les scripts déterminent le succès ou l’échec d’un programme à partir de son code de sortie
- La sortie principale d’une commande doit être envoyée vers
stdout- Même une sortie lisible par machine doit aller par défaut vers
stdoutpour que les pipes fonctionnent correctement
- Même une sortie lisible par machine doit aller par défaut vers
- Les messages destinés à l’utilisateur, comme les journaux et les erreurs, doivent être envoyés vers
stderr- Lors d’un chaînage par pipe, ces messages ne se mélangent pas à l’entrée de la commande suivante
Conception de l’aide
- Si
-hou--helpest passé, il faut afficher une aide suffisante- Les sous-commandes peuvent elles aussi avoir leur propre aide
-hne doit pas être surchargé avec une autre signification
- Si une commande qui requiert des arguments est exécutée sans aucun argument, il faut afficher une aide concise
- Description du programme
- Un ou deux exemples d’appel
- Description des drapeaux
- Indication d’utiliser
--helppour des informations plus détaillées
- Il est recommandé d’inclure dans l’aide un chemin de support
- Un lien vers un site web ou GitHub est courant
- S’il existe une documentation web, l’aide doit pointer vers celle-ci
- S’il existe pour une sous-commande une page spécifique ou une ancre, il est utile de faire un lien direct
- Comme les utilisateurs ont tendance à consulter les exemples avant la documentation, il vaut mieux placer les exemples au début de l’aide
- On peut montrer d’abord des cas d’usage complexes mais fréquents
- S’il y a beaucoup d’exemples, il est approprié de les placer dans une commande de type aide-mémoire séparée ou sur une page web
- L’aide peut être formatée pour être facile à parcourir
- Des titres en gras améliorent la lisibilité
- Il faut le faire d’une manière indépendante du terminal afin d’éviter que des séquences d’échappement apparaissent telles quelles
- Si l’utilisateur s’est trompé et que l’on peut deviner son intention, il faut faire une suggestion
- Par exemple,
brew update jqpeut proposerbrew upgrade jq - On peut demander s’il faut exécuter la commande suggérée, mais il vaut mieux éviter de l’exécuter de force
- Par exemple,
- Si une commande doit recevoir une entrée via
stdinmais questdinest un terminal interactif, elle doit immédiatement afficher l’aide et quitter, ou afficher un message surstderr
Documentation
- L’aide fournit un résumé immédiat et un guidage sur les tâches courantes, tandis que la documentation traite en détail de l’objectif de l’outil, de ce qu’il ne vise pas, de son fonctionnement et de l’ensemble de son usage
- Il faut fournir une documentation web
- Elle est consultable par recherche et l’on peut créer des liens vers des sections précises
- La documentation web est le format le plus complet
- Il faut aussi fournir une documentation dans le terminal
- Elle est accessible rapidement
- Elle reste synchronisée avec la version installée de l’outil
- Elle fonctionne même sans Internet
- On peut envisager de fournir des pages
man- De nombreux utilisateurs consultent d’abord
man mycmd - Comme
gitetnpm, on peut accéder aux pagesmanvia une sous-commandehelp
- De nombreux utilisateurs consultent d’abord
Principes de sortie
- Une sortie lisible par des humains est ce qu’il y a de plus important
- Un critère simple pour savoir si un flux de sortie est destiné à être lu par un humain est de vérifier s’il s’agit d’un TTY
- Il faut fournir une sortie lisible par des machines tant que cela ne nuit pas à l’utilisabilité
- Un flux de lignes en texte brut est l’interface universelle d’UNIX
- Les utilisateurs s’attendent à pouvoir passer le résultat à des outils comme
grepet à ce que cela fonctionne comme prévu
- Si une sortie conviviale pour les humains casse la sortie conviviale pour les machines, on peut proposer
--plain- Dans une sortie tabulaire, découper les cellules sur plusieurs lignes peut casser l’attente d’un enregistrement par ligne
--plainfournit un format tabulaire non modifié pour les scripts
- Si
--jsonest passé, il faut produire du JSON formaté- Le JSON facilite la manipulation de structures de données complexes et peut être utilisé avec
jqainsi qu’avec divers outils CLI JSON - Il convient aussi bien à un pipe direct vers des services web avec
curl
- Le JSON facilite la manipulation de structures de données complexes et peut être utilisé avec
- En cas de succès, il faut afficher quelque chose, mais rester bref
- Les commandes UNIX traditionnelles n’affichent souvent rien s’il n’y a pas de problème, mais pour un humain cela peut donner l’impression que le programme est bloqué
- Si une absence de sortie est nécessaire pour les scripts, une option
-qpeut masquer les sorties non essentielles
- Les commandes qui modifient l’état doivent indiquer à l’utilisateur ce qui a changé
git pushest un exemple qui montre l’opération en cours et le nouvel état de la branche distante
- Il doit être facile de voir l’état actuel du système
git statusaffiche l’état du dépôt ainsi que des indications sur les commandes à exécuter ensuite
- Les opérations qui franchissent les frontières du monde interne du programme doivent généralement être explicites
- Lorsqu’un fichier non passé en argument par l’utilisateur est lu ou écrit
- Lorsqu’un programme communique avec un serveur distant pour télécharger un fichier
- Les couleurs doivent être utilisées avec intention
- Si on en abuse, elles perdent leur sens et rendent la lecture plus difficile
- Il faut désactiver les couleurs si ce n’est pas un TTY, si
NO_COLORest défini, siTERM=dumb, ou si--no-colorest passé
- Il ne faut pas produire d’animations si
stdoutn’est pas un terminal interactif- Cela évite d’encombrer les logs CI avec des indicateurs de progression brouillons
- Lors de l’affichage de gros volumes de texte, on peut utiliser un pager comme
less- Il vaut mieux ne l’utiliser que lorsque
stdinoustdoutest un terminal interactif less -FIRXpeut constituer un ensemble d’options raisonnable
- Il vaut mieux ne l’utiliser que lorsque
Messages d’erreur
- Si les erreurs sont rédigées comme de la documentation, l’utilisateur passe moins de temps à aller chercher la doc
- Les erreurs prévisibles doivent être interceptées et reformulées de manière compréhensible pour un humain
- Exemple : indiquer qu’il est impossible d’écrire dans
file.txtet qu’unchmod +w file.txtpeut être nécessaire
- Exemple : indiquer qu’il est impossible d’écrire dans
- Le rapport signal/bruit est important
- Plus il y a de sorties non pertinentes, plus il est difficile pour l’utilisateur d’identifier le problème
- S’il y a plusieurs erreurs du même type, on peut les regrouper sous un même en-tête explicatif
- Il est préférable de placer l’information la plus importante à la fin de la sortie
- Le regard de l’utilisateur est attiré par le texte rouge, il faut donc l’utiliser avec parcimonie et intention
- Si l’erreur est inattendue ou difficile à expliquer, il faut fournir des informations de debug ou de traceback ainsi qu’un moyen de signaler le bug
- Pour ne pas submerger l’utilisateur, on peut écrire les logs de debug dans un fichier plutôt que dans le terminal
- Il faut rendre le signalement de bug facile
- Par exemple, en fournissant une URL avec autant d’informations préremplies que possible
Arguments et flags
- Les arguments sont des paramètres positionnels, et les flags sont des paramètres nommés
- Dans
cp foo bar, les chemins de fichiers sont des arguments -r,--recursive,--file foo.txtsont des flags
- Dans
- Il faut privilégier les flags plutôt que les arguments quand c’est possible
- Ils demandent plus de frappe, mais leur sens est plus clair
- Cela facilite l’évolution future du mode d’entrée
- Chaque flag doit avoir un nom long
- Par exemple avoir
-het--helpensemble - C’est utile pour rendre l’intention explicite dans les scripts
- Par exemple avoir
- Les flags à une seule lettre doivent être réservés aux options courantes
- Cela permet de préserver l’espace des noms courts pour les flags ajoutés plus tard
- Lorsqu’on applique la même opération simple à plusieurs fichiers, plusieurs arguments sont appropriés
- Une forme comme
rm file1.txt file2.txt file3.txts’accorde bien avec le globbing
- Une forme comme
- S’il y a deux arguments ou plus avec des significations différentes, il y a de fortes chances que la conception soit mauvaise
- Une exception est une action fréquente et centrale comme
cp <source> <destination>, où la brièveté vaut la peine d’être mémorisée
- Une exception est une action fréquente et centrale comme
- S’il existe des noms de flags standard, il faut les suivre
-a, --all-d, --debug-f, --force--json-h, --help-n, --dry-run--no-input-o, --output-p, --port-q, --quiet-u, --user--version
- La valeur par défaut doit correspondre au bon comportement pour la plupart des utilisateurs
- Si l’on suppose que les utilisateurs vont trouver le bon flag, s’en souvenir et l’utiliser à chaque fois, l’expérience sera mauvaise pour la plupart d’entre eux
- S’il n’y a pas d’entrée, on peut demander via un prompt, mais le prompt ne doit pas être obligatoire
- Il faut toujours fournir un moyen de passer l’entrée via des flags ou des arguments
- Si
stdinn’est pas un terminal interactif, il faut ignorer le prompt et exiger les flags ou arguments nécessaires
- Il faut demander confirmation avant une opération dangereuse
- En mode interactif, on peut demander de saisir
youyes - En mode non interactif, on peut exiger
-fou--force - Pour les opérations graves, on peut demander à l’utilisateur de retaper directement le nom de l’élément à supprimer ou fournir un flag comme
--confirm="name-of-thing"
- En mode interactif, on peut demander de saisir
- Si l’outil accepte des entrées ou sorties de fichiers, il faut prendre en charge
-pourstdinoustdout- Cela permet d’enchaîner avec la sortie d’une autre commande sans fichier temporaire
- Quand c’est possible, il faut traiter indépendamment l’ordre des arguments, flags et sous-commandes
- Parce qu’il est fréquent que les utilisateurs relancent une commande après avoir ajouté une option à la fin de la précédente
- Il ne faut pas lire directement les secrets depuis un flag
- Une valeur
--passwordpeut fuiter dans la sortie depsou dans l’historique du shell - Il faut envisager une entrée par fichier comme
--password-fileou viastdin
- Une valeur
Interaction
- Les prompts et éléments interactifs ne doivent être utilisés que si
stdinest un TTY- Pendant l’exécution d’un script ou avec une entrée via pipe, les prompts ne fonctionneront pas ; il faut donc signaler en erreur quels flags doivent être fournis
- Si
--no-inputest passé, il ne faut pas afficher de prompt ni avoir de comportement interactif- Si une entrée est nécessaire, il faut échouer et expliquer comment la fournir via un flag
- Lors de la saisie d’un mot de passe, il ne faut pas afficher ce que tape l’utilisateur
- On gère cela en désactivant l’echo du terminal
- L’utilisateur doit pouvoir s’échapper
- Même en cas de blocage sur des E/S réseau, Ctrl-C doit fonctionner
- Si l’outil est encapsulé par un wrapper comme SSH, tmux ou telnet, où Ctrl-C ne permet pas de quitter, il faut indiquer clairement comment sortir
Sous-commandes
- Si l’outil devient suffisamment complexe, un ensemble de sous-commandes peut réduire cette complexité
- Regrouper plusieurs outils liés dans une seule commande peut faciliter l’utilisation et la découverte
- Cela permet aussi de partager des flags globaux, l’aide, la configuration et les mécanismes de stockage
- Il faut de la cohérence entre les sous-commandes
- Le même sens doit utiliser le même nom de flag
- Les formats de sortie doivent aussi se ressembler
- Pour des sous-commandes à plusieurs niveaux, il faut utiliser un schéma de nommage cohérent
- Un modèle en deux étapes, nom puis verbe, comme
docker container create, est courant noun verbcommeverb nounsont tous deux possibles, maisnoun verbsemble plus courant
- Un modèle en deux étapes, nom puis verbe, comme
- Il faut éviter les commandes aux noms ambigus ou trop proches
- Avoir
updateetupgradeensemble peut prêter à confusion
- Avoir
Robustesse
- Toutes les données saisies par l’utilisateur doivent être validées
- Des données invalides peuvent être fournies ; il faut donc les vérifier tôt et s’arrêter avec une erreur compréhensible
- La réactivité est plus importante que la rapidité
- Il est préférable d’afficher quelque chose à l’utilisateur dans les 100 ms
- Il faut afficher un message même avant une requête réseau pour éviter de donner l’impression que le programme est bloqué
- Les opérations longues doivent afficher leur progression
- S’il n’y a aucune sortie pendant un moment, le programme semble en panne
- Si un indicateur de progression reste longtemps bloqué au même endroit, on peut montrer qu’un travail est toujours en cours avec une estimation du temps restant ou une animation
- Le traitement parallèle est souhaitable quand c’est possible, mais il faut rester prudent
- Il est difficile d’afficher dans le shell la progression de tâches parallèles, et si les sorties se mélangent cela devient confus
- Les multiples barres de progression de
docker pullmontrent bien ce qui se passe - Même si les logs sont masqués derrière une barre de progression en fonctionnement normal, il faut les afficher en cas d’erreur pour permettre le débogage
- Les opérations réseau doivent avoir des délais d’expiration
- Ces timeouts doivent être configurables et avoir des valeurs par défaut raisonnables
- Il faut pouvoir reprendre après un échec
- Après un échec temporaire, relancer avec
<up>et<enter>devrait permettre de reprendre à partir du point d’interruption
- Après un échec temporaire, relancer avec
- Si possible, une approche crash-only est préférable
- En cas d’échec ou d’interruption, on peut quitter immédiatement et remettre le nettoyage à l’exécution suivante
- Les utilisateurs peuvent employer l’outil de manière imprévue
- Ils peuvent l’envelopper dans des scripts, l’utiliser sur un Internet instable, lancer plusieurs instances en même temps, ou l’exécuter dans des environnements non testés
Compatibilité future
- Les sous-commandes, arguments, flags, fichiers de configuration et variables d’environnement sont tous des interfaces, et ils doivent continuer à fonctionner dans la durée
- Les changements possibles doivent être additifs
- Au lieu de casser le comportement d’un flag existant, on peut ajouter un nouveau flag
- Si un changement non additif est nécessaire, il faut prévenir à l’avance
- Lorsqu’un flag déconseillé est utilisé, il faut signaler le changement à venir et indiquer dès maintenant la méthode compatible avec l’avenir
- La sortie destinée aux humains peut généralement évoluer sans problème
- Il faut inciter les utilisateurs à employer
--plainou--jsondans les scripts pour obtenir une sortie stable
- Il faut inciter les utilisateurs à employer
- Il faut éviter les sous-commandes catch-all
- Traiter automatiquement toute sous-commande inconnue comme
runrisque de casser des usages existants lorsqu’on ajoutera plus tard de nouvelles sous-commandes
- Traiter automatiquement toute sous-commande inconnue comme
- Il ne faut pas autoriser des abréviations arbitraires des sous-commandes
- Si
installaccepteiouins, il devient ensuite difficile d’ajouter d’autres commandes commençant pari - Les alias doivent être explicites et rester stables
- Si
- Il faut se demander si la commande s’exécutera de la même manière dans 20 ans
- Il ne faut pas créer une bombe à retardement qui cessera de fonctionner parce que des dépendances Internet externes auront changé ou disparu
Signaux et caractères de contrôle
- Si l’utilisateur envoie Ctrl-C, c’est-à-dire le signal INT, il faut quitter aussi vite que possible
- Il faut dire immédiatement quelque chose avant de commencer le nettoyage
- Le code de nettoyage doit avoir un timeout
- Si Ctrl-C est saisi de nouveau pendant un nettoyage long, il faut pouvoir ignorer ce nettoyage
- Docker Compose indique qu’en appuyant une seconde fois sur Ctrl-C pendant l’arrêt, on peut forcer l’arrêt immédiat des conteneurs
- Le programme doit partir du principe qu’il peut démarrer alors que le nettoyage précédent n’a pas été exécuté
Configuration et variables d’environnement
- La méthode de configuration doit varier selon la spécificité, la stabilité et la complexité
- Les réglages qui changent souvent à chaque exécution se prêtent bien aux flags
- Les réglages relativement stables qui varient selon l’utilisateur, le projet ou la machine se prêtent bien aux flags et aux variables d’environnement
- Les réglages stables pour tous les utilisateurs au sein d’un projet se prêtent bien à un fichier de configuration dédié à la commande et versionné
- Il est préférable de suivre la XDG Base Directory Specification
- Elle vise à prendre en charge des emplacements de configuration génériques comme
~/.configafin de limiter la prolifération des dotfiles dans le répertoire personnel
- Elle vise à prendre en charge des emplacements de configuration génériques comme
- Si vous modifiez automatiquement des fichiers qui ne relèvent pas de la configuration de votre propre programme, il faut obtenir l’accord de l’utilisateur et indiquer précisément ce qui sera fait
- Il est préférable de créer un nouveau fichier de configuration plutôt que d’ajouter du contenu à un fichier de configuration système existant
- L’ordre de priorité de la configuration doit aller du plus élevé au plus faible
- flags
- variables d’environnement du shell en cours d’exécution
- configuration au niveau du projet
- configuration au niveau utilisateur
- configuration à l’échelle du système
- Les variables d’environnement conviennent aux comportements qui changent selon le contexte dans lequel la commande est exécutée
- Les noms de variables d’environnement ne doivent utiliser que des majuscules, des chiffres et des underscores pour une portabilité maximale, et ne doivent pas commencer par un chiffre
- Les valeurs devraient si possible tenir sur une seule ligne
- Les valeurs multiligne posent des problèmes d’utilisabilité lorsqu’elles sont utilisées avec la commande
env
- Les valeurs multiligne posent des problèmes d’utilisabilité lorsqu’elles sont utilisées avec la commande
- Il ne faut pas s’approprier à la légère des noms largement utilisés
- On peut se référer à la liste des variables d’environnement standard POSIX
- Quand c’est possible, il faut vérifier les variables d’environnement génériques
NO_COLOR,FORCE_COLORDEBUGEDITORHTTP_PROXY,HTTPS_PROXY,ALL_PROXY,NO_PROXYSHELLTERM,TERMINFO,TERMCAPTMPDIRHOMEPAGERLINES,COLUMNS
- Le cas échéant, on peut lire des variables d’environnement depuis
.env- C’est utile pour des variables qui changent rarement pendant qu’on travaille dans un répertoire donné
.envne remplace pas un véritable fichier de configuration- Il n’est souvent pas conservé dans la gestion de source
- Il n’a que des valeurs de type chaîne
- Il a tendance à mal vieillir en matière d’organisation
- Il est sujet aux problèmes d’encodage
- Il a tendance à contenir des identifiants sensibles et du matériel de clé
- Les secrets ne doivent pas être lus depuis des variables d’environnement
- Les variables d’environnement peuvent être exposées via les processus, les logs,
docker inspect,systemctl show, etc. - Les secrets doivent être fournis via des fichiers d’identifiants, des pipes, des sockets
AF_UNIX, un service de gestion des secrets, ou d’autres mécanismes IPC
- Les variables d’environnement peuvent être exposées via les processus, les logs,
Noms, distribution et collecte analytique
- Le nom d’un programme CLI doit être simple et facile à mémoriser, car les utilisateurs le saisissent souvent
- S’il est trop générique, il peut entrer en conflit avec d’autres commandes ou semer la confusion
- Il vaut mieux utiliser des minuscules et des tirets uniquement lorsque c’est nécessaire
curlest un bon nom, alors queDownloadURLne l’est pas
- Le nom doit être court, mais les noms trop courts conviennent plutôt à des utilitaires extrêmement courants comme
cd,ls,ps - Le nom doit être facile à taper
- Le cas de Docker Compose est connu : son nom initial était
plum, puis il a été remplacé parfigcar il était maladroit à taper d’une seule main
- Le cas de Docker Compose est connu : son nom initial était
- Il faut, si possible, distribuer l’outil sous forme de binaire unique
- Si ce n’est pas possible, il faut utiliser l’installateur de paquets natif de la plateforme afin de permettre une désinstallation facile
- Les outils spécifiques à un langage, par exemple les linters de code, peuvent partir du principe que l’utilisateur dispose de l’interpréteur de ce langage
- La désinstallation doit être simple
- Comme il est fréquent de vouloir supprimer l’outil juste après l’installation, il est préférable de placer les instructions de désinstallation juste sous les instructions d’installation
- Les métriques d’usage peuvent aider à améliorer l’outil, mais les utilisateurs de CLI s’attendent à garder le contrôle de leur environnement
- Il ne faut pas envoyer de données d’usage ou de crash sans consentement
- Il faut indiquer ce qui est collecté, pourquoi, dans quelle mesure c’est anonymisé, comment cela l’est, et combien de temps cela est conservé
- Idéalement, il vaut mieux un opt-in ; si l’on choisit un opt-out avec collecte par défaut, il faut l’indiquer clairement sur le site web ou au premier lancement et permettre de le désactiver facilement
- On peut aussi envisager des alternatives à la collecte analytique
- instrumenter la documentation web
- instrumenter les téléchargements
- parler directement avec les utilisateurs et encourager les retours ainsi que les demandes de fonctionnalités dans la documentation et le dépôt
1 commentaires
Commentaires Hacker News
Dire que « de nos jours, la plupart des gens ne savent même pas ce qu’est une ligne de commande » est vrai, mais c’était déjà le cas dans les années 1980 qui servent de référence au TFA
La différence, c’est qu’aujourd’hui le nombre de personnes capables de connaître et d’utiliser la ligne de commande est le plus élevé de l’histoire, au moins avec une croissance d’un ordre de grandeur, peut-être même de deux, donc on peut parler d’un âge d’or de la CLI
L’état global et les dépendances empêchent de raisonner clairement, et finissent par rendre le débogage comme l’optimisation des performances plus difficiles
Quand on extrait 500, 1 000, 5 000, 10 000, parfois 50 000 lignes de code pour les exécuter séparément, beaucoup de choses deviennent bien plus claires
Si c’est bien fait, cela peut aussi conduire à une architecture Functional Core, Imperative Shell, car une ligne de commande conforme à la philosophie Unix devrait avoir beaucoup d’opérations sans effet de bord et peu d’opérations avec effets de bord
On peut créer de petites commandes qui produisent l’état du système juste avant qu’une mauvaise décision soit prise, et les exécuter de façon pratiquement sûre même en production
Ensuite, il devient plus facile de transmettre ce type d’outils à des personnes qui devraient faire partie du bus factor, et de les faire participer
Ce que pense du terminal une personne vivant dans un village isolé de la civilisation amazonienne n’a rien à voir avec le sujet
Pour juger un changement qualitatif dans un ensemble dont la taille a grandi, il faut regarder la proportion ; si on ne regarde que le nombre absolu, on obtient des phrases vraies mais sans signification
Par exemple, le nombre absolu d’auditeurs de jazz aujourd’hui est peut-être supérieur à celui de l’âge d’or culturel du genre, mais ce n’est pas parce que le jazz est plus populaire : c’est parce qu’il y a plus de gens qui écoutent de la musique
Il est pourtant difficile de dire que le jazz est aujourd’hui plus important et plus influent aux États-Unis qu’à son apogée
J’aimerais aussi qu’on prenne en compte l’option
--dry-run, qui permet de montrer à l’avance ce qui va se passer sans effectuer de changement réelC’est vraiment utile pour apprendre à utiliser un outil, ou pour vérifier qu’on a bien écrit des options complexes et des globs de fichiers avant d’exécuter une opération difficile à annuler
--dry-runpar défaut et exiger un drapeau--executepour le vrai lancementJe ne sais pas comment je ferais sans, et ça m’a sauvé plusieurs fois alors que j’étais à deux doigts de tout casser
--commitpour l’exécution effectiveCette approche paraît plus sûre pour les scripts qui effectuent des opérations irréversibles ou difficiles à annuler
Je suis perdu sur la façon de le concevoir dans les outils que je développe
Si l’outil appelle une API pour récupérer des données, je ne vois pas comment en faire un dry run
Faut-il fournir un faux exemple et une fausse sortie ?
do_thing.py | dry-runIl faut voir ça comme une demande au prompt : « explique-moi étape par étape ce que tu vas faire »
Il ne s’agit pas seulement de ne pas afficher d’animations quand la sortie standard n’est pas un terminal interactif, il faudrait carrément ne jamais afficher d’animation sur stdout
Le TFA était globalement bon, mais j’ai été déçu en cherchant un passage expliquant la différence entre stderr et stdout pour constater qu’il n’y en avait pas
stderr n’est pas seulement destiné aux erreurs ; c’est aussi l’endroit où doivent aller tous les logs et les sorties informatives, et si c’est un terminal, on peut aussi y mettre des animations
stdout doit contenir la vraie sortie utile, et rester cohérent qu’il y ait ou non un terminal
Par exemple, dans
echo foo | mysed 's/oo/aa/' | cat, la sortie stdout demyseddoit êtrefaa, et les logs comme les informations de version ou les éléments trouvés doivent aller sur stderrJe n’ai pas envie de me battre avec un outil à coups de grep juste pour obtenir la vraie sortie, ni de devoir déboguer un comportement qui change dès qu’on retire
| catS’il a demandé
--help, cela doit aller sur stdout, et s’il a demandé des logs, les logs doivent aussi aller sur stdoutSi l’information n’a pas été demandée, alors stderr est le bon endroit
Si on pouvait remonter le temps, on l’aurait peut-être nommé stdin, stdout et stdext, et les gens suivraient plus facilement cette convention
Mais comme ça s’appelle stderr, les développeurs pensent logiquement « c’est pour signaler les erreurs », et mettent tout ce qui n’est pas une erreur sur stdout
Malgré cela, cette approche reste meilleure du point de vue de la structure des programmes, et même si c’est plus déroutant au début, elle permet de faire des choses plus utiles avec la sortie
Je ne considère pas non plus que la couleur soit vraiment une information
J’aimerais vraiment qu’on évite le conseil du type « utilisez des symboles et des emojis quand cela rend les choses plus claires »
yubikey-agent, pris en exemple, montre exactement ce que je n’aime pas dans certains README GitHub et certaines interfaces utilisateur trop ludiques
Techniquement, les symboles et les emojis peuvent être rendus différemment selon les terminaux, au point de rendre les messages confus
Esthétiquement aussi, la tolérance au côté joueur et pétillant varie énormément selon les personnes, donc il faut les utiliser avec beaucoup de retenue, et seulement quand on sait vraiment ce qu’on fait
J’aime aussi la sortie colorée dans le terminal, et les emojis ont des couleurs, ce qui aide à saisir rapidement l’ensemble d’une situation
J’aime aussi la coloration syntaxique, et sans elle j’ai l’impression que le code est bien plus difficile à lire
Ce n’est pas le cas de tout le monde, et tout comme je n’attends pas que mes préférences deviennent le choix par défaut, l’inverse ne devrait pas non plus être imposé comme défaut
Certaines personnes aiment, d’autres non ; il suffit donc de la désactiver par défaut tout en laissant le choix à l’utilisateur
Par exemple, un pour le succès et un pour l’échec, c’est largement suffisant
Et l’information transmise par un emoji doit toujours être répétée aussi en texte
La première fois que j’ai vu les CLI Guidelines, j’ai arrêté ma lecture à cette section
Cette fois, j’ai lu le reste aussi, et dans l’ensemble c’était plutôt bien
Je comprends que certaines CLI soient très volumineuses, comme
aws, et nécessitent une structure imbriquée, mais devoir naviguer dans une CLI imbriquée est vraiment frustrantPour la plupart des applications, il vaut mieux afficher toutes les options dans l’aide et me laisser trouver ce qu’il me faut avec
lessCette TUI est une couche purement UI/UX, optionnelle, qui transmet la configuration choisie à un fichier généré séparément ou à une fonction
Ce fichier ou cette fonction peut toujours être appelé directement depuis le terminal, et fournit aussi toutes les options et l’aide utilisées par la TUI
Cela permet donc un usage en script, et c’est utile pour des utilisateurs de niveaux variés
Si l’on connaît déjà la sous-commande nécessaire, réduire les options à ce qui est pertinent est assez utile, mais sinon cela peut devenir pénible
Parcourir une grande liste d’options avec
grepest aussi difficile, donc il faut trouver un équilibreman?--helpdes sous-commandes dans la commande parente peut être utilePar exemple,
git stash --helppourrait afficher directement l’aide degit stash, avec chaque entrée incluseL’affirmation selon laquelle « traditionnellement, les commandes UNIX étaient surtout écrites en supposant qu’elles seraient utilisées par d’autres programmes » n’est pas exacte
À l’origine, elles étaient surtout conçues pour un usage interactif dans un shell de connexion
Il y avait des programmes produisant une sortie sur stdout (
ls,cat,find,tty,who,date) et des filtres texte discrets (tr,grep,cut,uniq,sort,wc), et à cette époque on pouvait accomplir des tâches informatiques de base avec des commandes d’une seule ligneLes programmes complexes étaient écrits en C, puis, avec l’apparition de langages spécialisés comme
sedet AWK, certains programmes très orientés traitement de chaînes ont migré vers le shellLe shell n’est pas un environnement de programmation normal, et n’a jamais été conçu pour cela
Aujourd’hui, il est souvent préférable de faire les deux en Python ou Lua, mais le shell et le C restent les plus universellement installés
La divergence entre
sh,bash,kshetcshen est un bon exempleIl est tout à fait raisonnable de considérer l’ensemble standard des outils POSIX comme la bibliothèque standard de plusieurs langages de shell
Aujourd’hui, la ligne de commande Unix est d’un côté « extrêmement utile », et de l’autre cassée par conception
Son utilité est évidente quand on pense au temps qu’il faudrait pour écrire ceci en C ou en Rust :
curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1Mais pour comprendre pourquoi elle est cassée par conception, il suffit de voir https://news.ycombinator.com/item?id=29747034
Le problème est qu’une interface en ligne de commande doit être à la fois lisible pour les humains et pour les machines, et qu’il n’existe pas de manière standard de résoudre cela
Il pouvait exister au départ une solution standard au problème de devoir être lisible à la fois par les humains et les machines
Apple a créé les Human Interface Guidelines pour formaliser le sens des abstractions visuelles dans les interfaces desktop
Mais la ligne de commande n’a pas été conçue par des gens qui raisonnaient comme des designers Apple ; elle a été créée par des personnes qui pensaient plutôt « la paresse, l’impatience et l’arrogance sont des vertus, alors comment exprimer ce que je veux avec le minimum de code ? »
Ce n’était pas un mauvais choix à l’époque, et chaque octet comptait, mais cette décision de conception est maintenant figée dans une chaîne d’outils impossible à déplacer
Pour obtenir quelque chose de meilleur aujourd’hui, il faudrait sans doute pratiquement abandonner la chaîne d’outils POSIX, et il est difficile d’imaginer construire sur les fondations actuelles une UX découvrable et conceptuellement cohérente
Même au même moment, il existe de nombreuses façons de permettre des sorties différentes
Mais il faut un standard que tout le monde suive, et c’est précisément là que cela se complique
Si le problème est que presque aucune implémentation de
lsne permet de terminer les noms de fichiers par un caractère NUL au lieu d’un saut de ligne, la solution paraît si évidente qu’on a l’impression de rater quelque choseL’interface shell le refuse-t-elle pour une raison particulière ?
--jsonOn recommande de « ne pas lire les secrets depuis des variables d’environnement » et de privilégier à la place des fichiers d’identifiants, des pipes, des sockets AF_UNIX, des services de gestion des secrets et d’autres formes de communication inter-processus ; je me demande laquelle de ces options est la plus pratique et la plus portable
Je me demande aussi si les services de gestion des secrets sont utilisés uniquement au travail, ou aussi dans des projets personnels
Un article qui approfondit un peu plus la gestion des secrets en ligne de commande se trouve ici : https://smallstep.com/blog/command-line-secrets/
Les fichiers d’identifiants sont une option simple et portable
Les fichiers disposent déjà d’un modèle de permissions et ne dépendent ni d’un service externe ni d’une API propriétaire
Si un programme accepte un fichier d’identifiants, il est aussi compatible avec les systemd credentials
Les systemd credentials sont plus sûrs que des fichiers d’identifiants non chiffrés, ils sont chiffrés et peuvent même être liés au TPM, sans que le logiciel qui consomme les identifiants ait besoin de prendre en charge le TPM lui-même
Par exemple, mbsync(https://isync.sourceforge.io/mbsync.html) propose à peu près trois façons de fournir le mot de passe d’authentification IMAP
Si aucun mot de passe n’est défini, une invite apparaît à l’exécution ; on peut aussi mettre le mot de passe en clair dans le fichier de configuration, mais c’est peu pratique pour le partage
Il est aussi possible de définir une commande pour récupérer le mot de passe, ce qui permet de déléguer cela à un gestionnaire de mots de passe comme pass(1)(https://www.passwordstore.org/) ou à une invite graphique interactive
Leur documentation est généralement bonne, donc la mise en place est facile sans avoir à tout construire soi-même
Des gestionnaires de secrets comme Doppler(https://Doppler.com) ou AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) ont l’avantage de protéger les secrets dans un emplacement sûr et de limiter leur exposition
Ils peuvent même réduire l’exposition auprès des développeurs internes, ce qui aide à prévenir des fuites de données pourtant facilement évitables
Ces fuites peuvent mettre toute l’entreprise en danger et deviennent de plus en plus fréquentes
Articles liés : Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - octobre 2023, 1 commentaire
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - juin 2022, 1 commentaire
Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - décembre 2020, 5 commentaires
C’est vraiment agaçant que certains terminaux exécutent automatiquement une commande si la chaîne collée depuis le presse-papiers se termine par un caractère de fin de ligne
Personnellement, je pense qu’une interface en ligne de commande ne devrait pas faire ça
bind 'set enable-bracketed-paste on'On peut aussi désactiver ça si on trouve cela pénible
Si besoin, cela peut même être sur plusieurs lignes, et l’exécution n’a lieu qu’après avoir appuyé sur Entrée
Fish shell a souvent eu des valeurs par défaut raisonnables, et à part l’invite, je n’ai pas encore trouvé grand-chose à vouloir personnaliser
La plupart de ceux que j’ai essayés avaient une option pour supprimer les retours à la ligne du contenu du presse-papiers
#puis je colleMême si le retour à la ligne est interprété, toute la ligne devient un commentaire, donc c’est sans danger ; il suffit ensuite de supprimer le
#au début de la ligne avant la vraie exécution