2 points par GN⁺ 2024-02-07 | 1 commentaires | Partager sur WhatsApp
  • Une référence open source qui rassemble des principes de conception et des recommandations concrètes pour les programmes CLI, en réinterprétant de manière moderne la philosophie UNIX traditionnelle, avec pour public principal les développeurs qui créent des outils en ligne de commande
  • La CLI n’est plus une simple plateforme de scripting, mais a évolué en interface texte centrée sur l’humain ; les principes de conception doivent donc être mis à jour en conséquence
  • Composabilité et convivialité ne sont pas incompatibles ; en respectant les conventions UNIX comme l’entrée/sortie standard, les pipes et les codes de sortie, on peut atteindre les deux à la fois
  • Le document fournit aussi des recommandations précises sur des détails souvent négligés en pratique, comme les textes d’aide, les messages d’erreur, les formats de sortie, l’interactivité ou le système de configuration
  • La compatibilité future et la confiance des utilisateurs d’un outil CLI dépendent de la stabilité de l’interface et de la transparence des données analytiques ; ce guide en pose la ligne de base

Philosophie (Philosophy)

Conception centrée sur l’humain

  • Les commandes UNIX traditionnelles étaient surtout conçues pour être utilisées par d’autres programmes, mais aujourd’hui les CLI sont majoritairement utilisées directement par des humains ; une conception d’abord pensée pour l’humain est donc nécessaire
  • Autrefois, les CLI étaient « machine-first », mais elles ont évolué en interfaces texte « human-first »

Petits composants combinables

  • Le cœur de la philosophie UNIX consiste à assembler de petits programmes simples pour construire des systèmes plus grands, et cela reste valable aujourd’hui
  • Les stdin/stdout/stderr standard, les signaux et les codes de sortie garantissent la connexion entre programmes, tandis que JSON permet des échanges de données plus structurés
  • Un logiciel devient forcément une pièce d’un système plus vaste ; la qualité de cette intégration se décide dès la phase de conception

Cohérence

  • Les utilisateurs du terminal ont déjà intégré les conventions existantes ; il est donc recommandé que les CLI suivent les modèles établis
  • Toutefois, si la cohérence nuit à l’utilisabilité, il est possible de rompre avec une convention avec prudence

Juste niveau d’information

  • Si une commande reste en attente plusieurs minutes sans rien afficher, c’est « trop peu » d’information ; si elle déverse une masse de logs de debug, c’est « trop » d’information
  • L’équilibre de la quantité d’information est absolument essentiel pour qu’un logiciel aide réellement son utilisateur

Facilité de découverte (Ease of Discovery)

  • Les GUI affichent toutes les fonctions à l’écran, alors que les CLI sont souvent perçues à tort comme reposant uniquement sur la mémoire
  • En reprenant des techniques des GUI, comme un texte d’aide complet, des exemples riches ou des suggestions de commandes suivantes, une CLI peut elle aussi devenir facile à apprendre

La CLI comme conversation

  • L’usage d’une CLI suit une structure de dialogue fondée sur des essais et erreurs répétés ; proposer des corrections, afficher les états intermédiaires ou demander confirmation avant une opération risquée sont des techniques de conception qui exploitent cette caractéristique
  • La pire interaction est une conversation hostile qui rend l’utilisateur impuissant ; la meilleure est un échange agréable qui lui donne un sentiment d’accomplissement

Robustesse (Robustness)

  • Un logiciel doit être robuste dans les faits comme dans le ressenti
  • La gestion élégante des entrées imprévues, le maintien de l’idempotence, l’affichage de la progression et l’évitement des stack traces exposées sont des points clés
  • Réduire les cas particuliers complexes et rester simple renforce la robustesse

Empathie (Empathy)

  • Les outils CLI sont des outils créatifs pour développeurs et doivent donc être agréables à utiliser
  • Il faut concevoir en réfléchissant suffisamment au problème pour que l’utilisateur sente que l’outil est de son côté

Chaos

  • Le monde du terminal est rempli d’incohérences, mais ce chaos est aussi une source de création libre
  • « Si une norme nuit clairement à la productivité ou à la satisfaction utilisateur, abandonnez-la. » — Jef Raskin

Recommandations — Les bases (The Basics)

  • Utiliser une bibliothèque de parsing d’arguments : parmi les bibliothèques recommandées selon le langage figurent Go(Cobra, cli), Python(Click, Typer, Argparse), Rust(clap), Node(oclif), etc.
  • En cas de succès, renvoyer le code de sortie 0 ; en cas d’échec, un code non nul — c’est ce qui permet à un script de distinguer succès et échec
  • Envoyer la sortie normale vers stdout, et les messages comme les logs et erreurs vers stderr

Recommandations — Aide (Help)

  • Afficher un texte d’aide détaillé avec les flags -h ou --help, y compris pour les sous-commandes
  • En cas d’exécution sans argument, afficher une aide concise (description, 1 à 2 exemples, explication des flags, indication sur --help)
    • jq est cité comme un bon exemple d’implémentation
  • Prendre en charge plusieurs formes de demande d’aide comme --help, -h ou help subcommand
  • Fournir en haut du texte d’aide un lien vers la documentation web et un canal de feedback
  • Montrer les exemples d’abord — il est recommandé de construire une progression narrative allant vers des cas d’usage plus complexes
  • Placer les flags et commandes les plus fréquents en haut du texte d’aide (voir l’organisation de git)
  • Utiliser une mise en forme, par exemple des titres en gras, pour faciliter le survol visuel, tout en restant indépendant du terminal
  • Lorsqu’un utilisateur saisit quelque chose d’incorrect, il est possible d’inférer son intention et de proposer une correction — mais l’exécution automatique doit être décidée avec prudence
    • Une mauvaise saisie peut être non pas une simple faute de frappe, mais une erreur logique ; en corrigeant automatiquement, on s’impose aussi de supporter durablement cette syntaxe

Recommandations — Documentation (Documentation)

  • Fournir une documentation web — indispensable pour la recherche et le partage de liens
  • Fournir une documentation dans le terminal — synchronisée avec la version installée et accessible hors ligne
  • Envisager de fournir des pages man — elles peuvent être générées avec des outils comme ronn, et il est recommandé de les rendre accessibles via une sous-commande comme npm help ls

Recommandations — Sortie (Output)

  • Donner la priorité à la lisibilité humaine — on peut déterminer si la sortie est destinée à un humain via l’état TTY
  • Le flux texte est l’interface universelle d’UNIX ; il faut donc aussi proposer une sortie lisible par machine
  • Si une sortie conviviale pour l’humain nuit à la compatibilité avec les pipes, fournir une sortie texte brute avec le flag --plain
  • Prendre en charge une sortie au format JSON quand le flag --json est passé
  • En cas de succès, garder une sortie concise, voire aucune sortie si elle n’est pas nécessaire — pour les scripts, proposer une option -q pour la supprimer
  • Informer l’utilisateur lors d’un changement d’état — la sortie de git push sur l’état de la branche distante en est un bon exemple
  • Concevoir une sortie qui permette, comme git status, de voir facilement l’état actuel du système et de guider vers l’action suivante
  • Utiliser la couleur de manière intentionnelle, et impérativement la désactiver dans des conditions comme un pipe, NO_COLOR, TERM=dumb ou --no-color
  • Dans un environnement non TTY, ne pas afficher d’animations ni de spinners (pour éviter de polluer les logs CI)
  • N’utiliser emoji et symboles que lorsqu’ils améliorent la clarté (yubikey-agent est cité en exemple)
  • Exclure de la sortie par défaut les informations compréhensibles seulement par les développeurs, et ne les afficher qu’en mode verbose
  • Ne pas utiliser stderr comme un fichier de log — éviter par défaut les labels de niveau de log comme ERR ou WARN
  • En cas de sortie volumineuse, envisager l’usage d’un pager comme less — à activer uniquement en environnement TTY, avec recommandation des options less -FIRX

Recommandations — Erreurs (Errors)

  • Réécrire les erreurs prévisibles sous forme de messages compréhensibles par un humain (ex. : « vous devez exécuter chmod +w file.txt »)
  • Maintenir un bon rapport signal/bruit — regrouper les erreurs de même type sous un seul en-tête
  • Placer les informations importantes en fin de sortie — le texte rouge doit être utilisé de manière délibérée et rare
  • En cas d’erreur inattendue, inclure des informations de debug et indiquer comment soumettre un bug report
  • Concevoir l’URL de bug report pour préremplir automatiquement les informations et faciliter l’envoi

Recommandations — Arguments et flags (Arguments and Flags)

  • Les arguments (args) sont positionnels, les flags sont nommés — il faut préférer les flags aux arguments
  • Fournir pour tous les flags une version longue complète (par ex. prise en charge simultanée de -h et --help)
  • Réserver les flags à un seul caractère aux options fréquemment utilisées
  • Lorsqu’un standard existe, utiliser les noms de flags standard (-f/--force, -q/--quiet, -v, --json, etc.)
  • Définir des valeurs par défaut adaptées à la majorité des utilisateurs
  • Si un argument ou un flag n’est pas fourni, demander la saisie via un prompt, mais sans jamais l’imposer en environnement non interactif
  • Demander confirmation avant les opérations risquées — selon le niveau de risque, utiliser une confirmation y/n, proposer un dry-run ou exiger la saisie manuelle d’un texte précis
    • Les risques sont distingués en mild (suppression de fichier), moderate (suppression de répertoire, modification de ressources distantes) et severe (suppression d’un serveur entier)
  • Pour les entrées/sorties de fichiers, prendre en charge - pour lire depuis stdin et écrire vers stdout (ex. : curl ... | tar xvf -)
  • Ne pas recevoir directement des secrets via les flags — préférer un flag --password-file ou l’usage de stdin (risque d’exposition via ps ou l’historique shell)

Recommandations — Interactivité (Interactivity)

  • N’afficher prompts et éléments interactifs que lorsque stdin est un TTY
  • Si --no-input est passé, désactiver tous les prompts
  • Lors de la saisie d’un mot de passe, désactiver l’écho (ne pas afficher ce qui est tapé à l’écran)
  • Expliquer clairement comment quitter à tout moment — Ctrl-C doit toujours fonctionner

Recommandations — Sous-commandes (Subcommands)

  • Maintenir la cohérence des noms de flags et des formats de sortie entre sous-commandes
  • Pour les outils complexes, utiliser une structure à sous-commandes en deux niveaux sous forme noun verb ou verb noun (ex. : docker container create)
  • Éviter les sous-commandes ambiguës ou aux noms trop proches (par ex. éviter d’utiliser à la fois update et upgrade)

Recommandations — Robustesse (Robustness Guidelines)

  • Effectuer la validation des entrées tôt, et quitter rapidement avec une erreur claire en cas de données invalides
  • La réactivité est plus importante que la vitesse — afficher quelque chose en moins de 100 ms
  • Pour les opérations longues, fournir une barre de progression (progress bar) — avec par exemple les bibliothèques Python(tqdm), Go(schollz/progressbar), Node(node-progress)
  • En traitement parallèle, veiller à ce que la sortie ne se mélange pas
  • Définir des timeouts réseau — avec des valeurs par défaut, pour éviter les attentes infinies
  • Après une erreur temporaire, concevoir le système pour qu’un retry reprenne depuis l’état précédent
  • Conception crash-only — garantir l’idempotence avec une structure capable de s’arrêter immédiatement sans phase de nettoyage

Recommandations — Compatibilité future (Future-proofing)

  • Maintenir les changements sous une forme additive et rétrocompatible
  • Avant tout changement cassant, afficher un avertissement préalable dans le programme
  • Les changements dans la sortie destinée aux humains sont généralement acceptables — pour les scripts, encourager l’usage de --plain et --json
  • Interdire les sous-commandes catch-all — elles empêchent d’ajouter plus tard une sous-commande portant ce nom
  • Ne pas autoriser automatiquement les abréviations de sous-commandes — n’accepter que des alias explicites, maintenus de façon stable
  • Interdire les “bombes à retardement” — minimiser les dépendances externes pour que le programme puisse encore fonctionner dans 20 ans

Recommandations — Signaux et caractères de contrôle (Signals)

  • À la réception de Ctrl-C (signal INT), quitter immédiatement, avec un timeout sur les opérations de nettoyage
  • Pendant le nettoyage, indiquer qu’une nouvelle pression sur Ctrl-C peut forcer l’arrêt (voir l’exemple de Docker Compose)
  • Concevoir le programme en supposant qu’il peut démarrer alors qu’un nettoyage précédent n’est pas terminé

Recommandations — Configuration (Configuration)

Ordre de priorité d’application de la configuration (du plus élevé au plus faible) :

  • Flags → variables d’environnement du shell courant → configuration au niveau du projet (.env) → configuration au niveau utilisateur → configuration système globale

Recommandations selon le type de configuration :

  • Configuration qui change à chaque appel (niveau de debug, dry-run) : utiliser des flags

  • Configuration qui diffère selon le projet ou la machine (chemins, couleur, proxy HTTP) : combiner flags + variables d’environnement

  • Configuration partagée par tout un projet (type Makefile, package.json) : utiliser des fichiers versionnés

  • Respecter la spécification XDG Base Directory — recommander des chemins de configuration basés sur ~/.config (pris en charge par yarn, fish, neovim, tmux, etc.)

  • En cas de modification automatique du fichier de configuration d’un autre programme, obtenir impérativement l’accord de l’utilisateur


Recommandations — Variables d’environnement (Environment Variables)

  • Les variables d’environnement conviennent aux comportements qui varient selon le contexte d’exécution
  • Le nom doit utiliser uniquement des majuscules, des chiffres et des underscores, sans commencer par un chiffre
  • Il est recommandé d’utiliser des valeurs sur une seule ligne — le multiligne pose des problèmes de compatibilité avec la commande env
  • Vérifier en priorité les variables d’environnement génériques comme NO_COLOR, DEBUG, EDITOR, HTTP_PROXY, SHELL, TMPDIR, HOME, PAGER
  • Il est recommandé de prendre en charge la lecture de fichiers .env propres au projet — mais .env ne remplace pas un vrai fichier de configuration
    • Limites de .env : absent du contrôle de version, sans historique, type unique chaîne de caractères, sensible aux problèmes d’encodage
  • Ne pas lire les secrets depuis les variables d’environnement — elles se propagent à tous les processus, peuvent fuiter dans les logs et être exposées via Docker inspect ou systemctl show
    • Les secrets ne doivent être reçus que via un fichier de credentials, un pipe, une socket AF_UNIX ou un service de gestion des secrets

Recommandations — Nommage (Naming)

  • Utiliser des mots simples et faciles à retenir — s’ils sont trop génériques, ils risquent d’entrer en conflit avec d’autres commandes
  • Utiliser uniquement des minuscules et, si nécessaire, des tirets (curl est un bon exemple, DownloadURL un mauvais)
  • Garder des noms courts, mais réserver les noms extrêmement courts comme cd, ls, ps aux utilitaires génériques
  • Le cas de renommage de Docker Compose, passé de plum à fig puis à docker compose, montre concrètement que la facilité de saisie est un critère important de naming

Recommandations — Distribution (Distribution)

  • Distribuer si possible sous forme de binaire unique — avec par exemple PyInstaller
  • Si ce n’est pas possible, utiliser un installeur natif à la plateforme
  • Indiquer la méthode de désinstallation en bas des instructions d’installation

Recommandations — Données analytiques (Analytics)

  • Ne pas envoyer de données d’usage ni de crash sans consentement utilisateur
  • En cas de collecte, exposer clairement les données collectées, la raison, la méthode d’anonymisation et la durée de conservation
  • Recommander l’opt-in par défaut — en cas d’opt-out, l’indiquer clairement au premier lancement ou sur le site web
    • Trois cas sont présentés : Angular.js (opt-in explicite), Homebrew (Google Analytics, FAQ publique), Next.js (statistiques anonymes activées par défaut)
  • Comme alternatives à l’analytics, on peut utiliser l’instrumentation de la documentation web, la mesure des téléchargements ou des entretiens directs avec les utilisateurs

1 commentaires

 
GN⁺ 2024-02-07
Commentaires Hacker News
  • Beaucoup de gens aujourd’hui ne savent pas ce qu’est la ligne de commande et ne se demandent même pas pourquoi ils devraient l’utiliser.

    • C’était déjà le cas dans les années 1980, mais aujourd’hui il y a plus de personnes qui connaissent la ligne de commande qu’à n’importe quelle autre époque. On peut parler d’un âge d’or de la CLI (interface en ligne de commande).
  • Dans les scripts, n’autorisez pas d’abréviations arbitraires des sous-commandes. Par exemple, si vous autorisez mycmd ins ou mycmd i au lieu de mycmd install, vous ne pourrez plus ajouter de nouvelle commande commençant par i.

    • Il faut éviter les arguments courts dans les scripts. Les arguments courts sont pratiques pour réduire la frappe lors d’un usage humain, mais dans un script, les écrire explicitement coûte peu, et c’est préférable si l’on tient compte du ratio lecture/écriture.
  • Pensez à l’option --dry-run. Une fonctionnalité qui montre à l’avance ce qui sera exécuté sans effectuer de modification réelle est très utile pour apprendre à utiliser un outil et vérifier que des options complexes ont été correctement configurées.

  • Si stdout n’est pas un terminal interactif, n’affichez pas d’animations. Cela évite que les journaux CI transforment les barres de progression en sapin de Noël.

    • N’affichez jamais d’animations sur stdout. stderr sert au logging, aux informations, etc., et stdout doit fournir une sortie utile, qu’il s’agisse ou non d’un tty.
  • N’utilisez des symboles et des emojis que lorsqu’ils améliorent la clarté.

    • Le rendu des symboles et des emojis peut varier selon les terminaux, et leur usage peut diviser selon les préférences des utilisateurs ; il faut donc les employer avec beaucoup de prudence.
  • La ligne de commande Unix est à la fois « étonnamment utile » et porteuse de « défauts de conception ».

    • On comprend à quel point la ligne de commande Unix est utile si l’on pense au temps qu’il faudrait pour faire la même chose en C ou en Rust.
    • Les défauts de conception viennent du fait qu’une interface en ligne de commande doit être lisible à la fois par les humains et par les machines. Il n’existe pas de solution canonique à ce problème.
  • Sauf dans les cas où la CLI est très vaste et nécessite une imbrication importante (comme aws), la plupart des applications devraient afficher toutes les options dans l’aide et laisser l’utilisateur trouver ce dont il a besoin avec less.

  • Traditionnellement, les commandes UNIX étaient écrites en partant du principe qu’elles seraient principalement utilisées par d’autres programmes.

    • En réalité, elles étaient destinées à un usage interactif dans un shell de connexion. On distinguait les programmes qui génèrent une sortie et les filtres textuels « silencieux », tandis que les programmes complexes étaient écrits en C.
  • Ne lisez pas les mots de passe depuis des variables d’environnement.

    • Les mots de passe ne devraient être reçus qu’au moyen de fichiers d’identification, de pipes, de sockets AF_UNIX, de services de gestion des secrets ou d’autres mécanismes IPC.
  • Le livre le plus complet sur les guidelines CLI est celui d’Eric Raymond.

    • Il date un peu, mais en parcourant clig.dev, on voit que les opinions ont beaucoup évolué avec le temps.