2 points par GN⁺ 2024-02-07 | 1 commentaires | Partager sur WhatsApp
  • 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 stdout pour que les pipes fonctionnent correctement
  • 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 -h ou --help est passé, il faut afficher une aide suffisante
    • Les sous-commandes peuvent elles aussi avoir leur propre aide
    • -h ne 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 --help pour 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 jq peut proposer brew 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
  • Si une commande doit recevoir une entrée via stdin mais que stdin est un terminal interactif, elle doit immédiatement afficher l’aide et quitter, ou afficher un message sur stderr

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 git et npm, on peut accéder aux pages man via une sous-commande help

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 grep et à 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
    • --plain fournit un format tabulaire non modifié pour les scripts
  • Si --json est passé, il faut produire du JSON formaté
    • Le JSON facilite la manipulation de structures de données complexes et peut être utilisé avec jq ainsi qu’avec divers outils CLI JSON
    • Il convient aussi bien à un pipe direct vers des services web avec curl
  • 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 -q peut masquer les sorties non essentielles
  • Les commandes qui modifient l’état doivent indiquer à l’utilisateur ce qui a changé
    • git push est 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 status affiche 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_COLOR est défini, si TERM=dumb, ou si --no-color est passé
  • Il ne faut pas produire d’animations si stdout n’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 stdin ou stdout est un terminal interactif
    • less -FIRX peut constituer un ensemble d’options raisonnable

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.txt et qu’un chmod +w file.txt peut être nécessaire
  • 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.txt sont des flags
  • 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 -h et --help ensemble
    • C’est utile pour rendre l’intention explicite dans les scripts
  • 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.txt s’accorde bien avec le globbing
  • 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
  • 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 stdin n’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 y ou yes
    • En mode non interactif, on peut exiger -f ou --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"
  • Si l’outil accepte des entrées ou sorties de fichiers, il faut prendre en charge - pour stdin ou stdout
    • 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 --password peut fuiter dans la sortie de ps ou dans l’historique du shell
    • Il faut envisager une entrée par fichier comme --password-file ou via stdin

Interaction

  • Les prompts et éléments interactifs ne doivent être utilisés que si stdin est 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-input est 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 verb comme verb noun sont tous deux possibles, mais noun verb semble plus courant
  • Il faut éviter les commandes aux noms ambigus ou trop proches
    • Avoir update et upgrade ensemble peut prêter à confusion

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 pull montrent 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
  • 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 --plain ou --json dans les scripts pour obtenir une sortie stable
  • Il faut éviter les sous-commandes catch-all
    • Traiter automatiquement toute sous-commande inconnue comme run risque de casser des usages existants lorsqu’on ajoutera plus tard de nouvelles sous-commandes
  • Il ne faut pas autoriser des abréviations arbitraires des sous-commandes
    • Si install accepte i ou ins, il devient ensuite difficile d’ajouter d’autres commandes commençant par i
    • Les alias doivent être explicites et rester stables
  • 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 ~/.config afin de limiter la prolifération des dotfiles dans le répertoire personnel
  • 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
  • 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_COLOR
    • DEBUG
    • EDITOR
    • HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY
    • SHELL
    • TERM, TERMINFO, TERMCAP
    • TMPDIR
    • HOME
    • PAGER
    • LINES, 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é
  • .env ne 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

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
    • curl est un bon nom, alors que DownloadURL ne 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é par fig car il était maladroit à taper d’une seule main
  • 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

 
GN⁺ 2024-02-07
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

    • Pour découper des monolithes, on ajoute des outils en ligne de commande à certaines parties de l’application
      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
    • Si on remplace « gens » par « utilisateurs d’ordinateurs », l’idée de l’auteur est juste
      Ce que pense du terminal une personne vivant dans un village isolé de la civilisation amazonienne n’a rien à voir avec le sujet
    • Il faut distinguer nombre absolu et proportion
      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
    • La vraie question est de savoir si c’était aussi le cas, en proportion, parmi les utilisateurs d’ordinateurs des années 1980
  • 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éel
    C’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

    • Si une commande est irréversible et a de gros effets de bord, il vaut mieux mettre --dry-run par défaut et exiger un drapeau --execute pour le vrai lancement
    • Le WhatIf de PowerShell est l’une de mes fonctionnalités préférées
      Je ne sais pas comment je ferais sans, et ça m’a sauvé plusieurs fois alors que j’étais à deux doigts de tout casser
    • À l’inverse, je préfère que le comportement par défaut ne fasse aucun changement réel, et qu’il faille passer --commit pour l’exécution effective
      Cette approche paraît plus sûre pour les scripts qui effectuent des opérations irréversibles ou difficiles à annuler
    • Je serais curieux d’avoir des exemples d’outils en ligne de commande avec un drapeau dry run
      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 ?
    • À mon avis, dry-run devrait être une fonction qu’on peut chaîner par pipe à n’importe quelle commande : do_thing.py | dry-run
      Il 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 de mysed doit être faa, et les logs comme les informations de version ou les éléments trouvés doivent aller sur stderr
    Je 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 | cat

    • La règle « stdout est la sortie utile » peut être un peu affinée : stdout doit contenir ce que l’utilisateur a demandé
      S’il a demandé --help, cela doit aller sur stdout, et s’il a demandé des logs, les logs doivent aussi aller sur stdout
      Si l’information n’a pas été demandée, alors stderr est le bon endroit
    • C’est une bonne règle, mais le nom prête à confusion
      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
    • Du coup, où devrait-on afficher les animations ?
      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

    • Moi, j’aime bien ce genre de sortie
      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
    • En fonction optionnelle, ça me va
      Certaines personnes aiment, d’autres non ; il suffit donc de la désactiver par défaut tout en laissant le choix à l’utilisateur
    • Comme ligne directrice, il vaudrait mieux limiter à deux emojis au maximum le vocabulaire d’emojis utilisé dans la sortie
      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
    • Tout à fait d’accord
      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 frustrant
    Pour la plupart des applications, il vaut mieux afficher toutes les options dans l’aide et me laisser trouver ce qu’il me faut avec less

    • Je crée beaucoup d’applications TUI, et j’ajoute à chacune un bon frontend TUI pour des utilisateurs moins techniques, par exemple en QA
      Cette 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
    • Cela dépend du nombre de sous-commandes et de leur clarté
      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 grep est aussi difficile, donc il faut trouver un équilibre
    • N’est-ce pas justement le rôle des pages man ?
    • Une approche consistant à déplier le --help des sous-commandes dans la commande parente peut être utile
      Par exemple, git stash --help pourrait afficher directement l’aide de git stash, avec chaque entrée incluse
  • L’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 ligne
    Les programmes complexes étaient écrits en C, puis, avec l’apparition de langages spécialisés comme sed et AWK, certains programmes très orientés traitement de chaînes ont migré vers le shell
    Le shell n’est pas un environnement de programmation normal, et n’a jamais été conçu pour cela

    • Il arrive qu’un programme C de 10 000 lignes puisse être remplacé par 10 lignes de shell, et qu’à l’inverse un programme shell de 1 000 lignes aurait tenu en 100 lignes de C
      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
    • Normal ou non, le shell est un langage de programmation, et c’était encore plus évident aux débuts d’Unix
      La divergence entre sh, bash, ksh et csh en est un bon exemple
      Il 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 -1
    Mais 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

    • Cet exemple montre justement très bien pourquoi c’est cassé par conception
      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
    • Au fond, les ordinateurs sont là pour nous servir, donc la solution ultime devrait être de faire en sorte que les machines lisent aussi bien que les humains
    • En pratique, ce n’est pas vraiment « en même temps » : généralement, humains et machines utilisent la même commande à des moments différents
      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
    • Cet exemple ne prouve pas que c’est cassé par conception ; il semble même montrer une solution relativement simple
      Si le problème est que presque aucune implémentation de ls ne 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 chose
      L’interface shell le refuse-t-elle pour une raison particulière ?
    • L’approche traitée dans l’article consiste à proposer un mode de sortie lisible par machine, comme --json
  • On 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

    • Je suis l’un des auteurs de CLI Guidelines
      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
    • Ce serait bien de permettre au programme de spécifier une commande pour récupérer le secret
      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
    • Les services de gestion des secrets sont probablement l’option la plus pratique
      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

    • Dans Bash, il suffit d’utiliser bind 'set enable-bracketed-paste on'
    • Sur macOS, iTerm demande une confirmation quand on essaie de coller une chaîne contenant un retour à la ligne
      On peut aussi désactiver ça si on trouve cela pénible
    • Fish shell se comporte correctement par défaut : il insère simplement le contenu et permet de modifier la commande avant exécution
      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
    • Cela peut valoir le coup d’utiliser un gestionnaire de presse-papiers
      La plupart de ceux que j’ai essayés avaient une option pour supprimer les retours à la ligne du contenu du presse-papiers
    • Quand je sais qu’il n’y aura au plus qu’un seul retour à la ligne, je tape d’abord # puis je colle
      Mê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