2 puntos por GN⁺ 2024-02-07 | 1 comentarios | Compartir por WhatsApp
  • Una referencia en forma de documento open source que organiza principios de diseño y lineamientos concretos para programas CLI como una reinterpretación moderna de la filosofía tradicional de UNIX, dirigida principalmente a desarrolladores que crean herramientas de línea de comandos
  • La CLI ya no es solo una plataforma simple para scripting, sino que ha evolucionado hacia una UI de texto centrada en humanos, y los principios de diseño también deben actualizarse en consecuencia
  • La componibilidad (composability) y la facilidad de uso para humanos no se contradicen; si se respetan las convenciones de UNIX como entrada/salida estándar, pipes y códigos de salida, es posible lograr ambas al mismo tiempo
  • Ofrece recomendaciones concretas incluso sobre detalles que suelen pasarse por alto en la práctica, como texto de ayuda, mensajes de error, formato de salida, interactividad y sistemas de configuración
  • La compatibilidad futura y la confianza del usuario en las herramientas CLI se definen por la estabilidad de la interfaz y la transparencia de los datos analíticos, y esta guía presenta esa línea base

Filosofía (Philosophy)

Diseño centrado en humanos

  • Los comandos tradicionales de UNIX se diseñaron principalmente asumiendo que serían usados por otros programas, pero hoy la mayoría de las CLI son usadas directamente por personas, por lo que se necesita un diseño con prioridad en el ser humano
  • Antes la CLI era "machine-first", pero ahora ha evolucionado hacia una UI de texto "human-first"

Pequeñas piezas componibles

  • El núcleo de la filosofía UNIX es combinar programas pequeños y simples para construir sistemas más grandes, y eso sigue siendo válido hoy
  • stdin/stdout/stderr estándar, señales y códigos de salida garantizan la conexión entre programas, y JSON permite un intercambio de datos más estructurado
  • El software inevitablemente se convierte en parte de un sistema más grande, y si será una pieza que funcione bien o no se decide en la etapa de diseño

Consistencia

  • Los usuarios de terminal ya están acostumbrados a las convenciones existentes, por lo que se recomienda que las CLI sigan los patrones ya establecidos
  • Aun así, si la consistencia perjudica la usabilidad, es posible romper con la convención de manera cuidadosa

Cantidad adecuada de información

  • Si un comando espera durante varios minutos sin mostrar nada, hay "muy poca" información; si arroja grandes cantidades de logs de depuración, hay "demasiada" información
  • El equilibrio en la cantidad de información es absolutamente clave para que el software apoye al usuario

Facilidad de descubrimiento (Ease of Discovery)

  • Las GUI muestran todas las funciones en pantalla, mientras que a la CLI suele atribuírsele erróneamente una dependencia de la memoria
  • Con texto de ayuda completo, ejemplos abundantes y sugerencias del siguiente comando, la CLI también puede hacerse fácil de aprender tomando prestadas técnicas de las GUI

La CLI como conversación

  • Usar una CLI tiene una estructura conversacional de prueba y error repetidos; sugerencias para corregir errores, mostrar estados intermedios y pedir confirmación antes de tareas riesgosas son técnicas de diseño que aprovechan esa característica
  • La peor interacción es una conversación hostil que deja al usuario impotente; la mejor es un intercambio agradable que genera sensación de logro

Robustez (Robustness)

  • El software debe ser robusto tanto en la realidad como en la percepción
  • Manejar con elegancia entradas inesperadas, mantener la idempotencia, informar del progreso y evitar exponer stack traces son puntos clave
  • Reducir casos especiales complejos y mantener la simplicidad aumenta la robustez

Empatía (Empathy)

  • Las herramientas CLI son instrumentos creativos para desarrolladores, por lo que deben ser agradables de usar
  • Diseña pensando lo suficiente en el problema para que el usuario sienta que la herramienta está de su lado

Caos (Chaos)

  • El mundo de la terminal está lleno de inconsistencias, pero ese caos también es fuente de creación libre
  • "Si un estándar es claramente perjudicial para la productividad o la satisfacción del usuario, abandona ese estándar" — Jef Raskin

Lineamientos — Lo básico (The Basics)

  • Usa una biblioteca de parseo de argumentos: entre las recomendadas por lenguaje están Go(Cobra, cli), Python(Click, Typer, Argparse), Rust(clap), Node(oclif), entre otras
  • En caso de éxito devuelve código de salida 0; en caso de fallo, un código distinto de 0 — este es el criterio que usan los scripts para distinguir éxito y error
  • La salida por defecto debe ir a stdout, y los mensajes como logs o errores a stderr

Lineamientos — Ayuda (Help)

  • Muestra texto de ayuda detallado con la bandera -h o --help, y aplica lo mismo a los subcomandos
  • Si se ejecuta sin argumentos, muestra una ayuda breve (incluyendo descripción, 1 o 2 ejemplos, explicación de banderas y referencia a --help)
    • Se menciona a jq como un buen ejemplo de esta implementación
  • Soporta distintas formas de pedir ayuda como --help, -h o help subcommand
  • Proporciona en la parte superior del texto de ayuda un enlace a la documentación web y una vía para enviar feedback
  • Muestra primero los ejemplos — se recomienda construir una narrativa que avance gradualmente hacia casos de uso más complejos
  • Coloca las banderas y comandos más usados al inicio del texto de ayuda (tomando como referencia la organización de git)
  • Usa formato como títulos en negrita para que sea fácil de escanear, pero de forma independiente del terminal
  • Cuando el usuario escriba algo incorrecto, puedes inferir su intención y sugerir una corrección; aun así, decidir ejecutar automáticamente esa corrección requiere cautela
    • Una entrada incorrecta puede no ser solo un typo sino un error lógico, y la autocorrección también implica la carga de mantener ese comportamiento de forma permanente

Lineamientos — Documentación (Documentation)

  • Proporciona documentación basada en web — es esencial para la capacidad de búsqueda y para compartir enlaces
  • Proporciona documentación en terminal — se mantiene sincronizada con la versión instalada y sigue siendo accesible sin conexión
  • Considera ofrecer páginas man — pueden generarse con herramientas como ronn, y se recomienda permitir acceso por subcomando, como en npm help ls

Lineamientos — Salida (Output)

  • La legibilidad humana es la prioridad — usa si es TTY o no para determinar si quien lee es una persona
  • Los flujos de texto son la interfaz universal de UNIX, así que también debe haber salida legible por máquina
  • Si la salida amigable para humanos perjudica la compatibilidad con pipes, ofrece salida de texto plano con la bandera --plain
  • Si se pasa la bandera --json, soporta salida en formato JSON
  • En caso de éxito, la salida debe ser concisa; si no hace falta, no imprimas nada — para scripts, ofrece la opción -q para suprimir salida
  • Informa al usuario cuando cambie el estado — que git push muestre el estado de la rama remota es un buen ejemplo
  • Diseña la salida para que, como en git status, sea fácil ver el estado actual del sistema y también el siguiente paso a realizar
  • Usa color de forma intencional, y desactívalo obligatoriamente en condiciones como pipes, NO_COLOR, TERM=dumb o --no-color
  • En entornos que no sean TTY, no muestres animaciones ni spinners (para evitar contaminar logs de CI)
  • Usa emoji o símbolos solo cuando realmente mejoren la claridad (yubikey-agent se presenta como ejemplo)
  • La información que solo entiende el desarrollador debe quedar fuera de la salida por defecto y mostrarse solo en modo verbose
  • No uses stderr como si fuera un archivo de logs — en general evita imprimir etiquetas de nivel como ERR o WARN
  • Si la salida es muy extensa, considera usar un pager como less — actívalo solo en entornos TTY y se recomiendan opciones less -FIRX

Lineamientos — Errores (Errors)

  • Reescribe los errores previsibles en mensajes comprensibles para humanos (por ejemplo, "necesitas ejecutar chmod +w file.txt")
  • Mantén una buena relación señal/ruido — agrupa errores del mismo tipo bajo un solo encabezado
  • Coloca la información importante al final de la salida — el texto rojo debe usarse con intención y rara vez
  • Si ocurre un error inesperado, incluye información de depuración y cómo enviar un reporte de bug
  • Diseña la URL del reporte de bug para que la información se rellene automáticamente y sea fácil de enviar

Lineamientos — Argumentos y banderas (Arguments and Flags)

  • Los argumentos (args) son posicionales y las banderas (flags) tienen nombre — prefiere banderas por encima de argumentos
  • Proporciona una versión con nombre completo para todas las banderas (por ejemplo, soporte simultáneo de -h y --help)
  • Limita las banderas de un solo carácter a las que se usan con frecuencia
  • Cuando exista un estándar, usa nombres de banderas estándar (-f/--force, -q/--quiet, -v, --json, etc.)
  • Configura valores por defecto que sean adecuados para la mayoría de los usuarios
  • Si no se pasan argumentos o banderas, solicita la entrada mediante prompt, pero nunca fuerces prompts en entornos no interactivos
  • Antes de operaciones peligrosas, pide confirmación — según el nivel de riesgo, usa confirmación y/n, ofrece un dry-run o exige escribir texto explícitamente
    • Se distingue el riesgo como mild (borrar un archivo), moderate (borrar un directorio, modificar recursos remotos) y severe (borrar un servidor completo)
  • En entrada/salida de archivos, soporta leer/escribir desde stdin/stdout usando - (por ejemplo, curl ... | tar xvf -)
  • No recibas secretos directamente en banderas — se recomienda una bandera como --password-file o usar stdin (por el riesgo de exposición en salida de ps o historial del shell)

Lineamientos — Interactividad (Interactivity)

  • Los prompts y elementos interactivos deben mostrarse solo cuando stdin sea un TTY
  • Si se pasa --no-input, desactiva todos los prompts
  • Al ingresar contraseñas, desactiva el eco (no mostrar en pantalla lo escrito)
  • Indica con claridad cómo el usuario puede salir en cualquier momento — Ctrl-C debe seguir funcionando siempre

Lineamientos — Subcomandos (Subcommands)

  • Mantén consistencia en nombres de banderas y formatos de salida entre subcomandos
  • En herramientas complejas, usa una estructura de subcomandos de dos niveles tipo noun verb o verb noun (por ejemplo, docker container create)
  • Evita subcomandos con nombres ambiguos o demasiado parecidos (por ejemplo, no usar a la vez update y upgrade)

Lineamientos — Robustez (Robustness Guidelines)

  • Realiza la validación de entrada desde el inicio y termina pronto con un error fácil de entender si los datos no son válidos
  • La capacidad de respuesta importa más que la velocidad — muestra algo en menos de 100 ms
  • En tareas largas, ofrece una barra de progreso (progress bar) — pueden usarse bibliotecas como Python(tqdm), Go(schollz/progressbar), Node(node-progress)
  • En procesamiento en paralelo, cuida que la salida no se mezcle
  • Configura timeouts de red — incluyendo valores por defecto para evitar esperas infinitas
  • Tras errores temporales, diseña el sistema para que pueda reanudar desde el estado anterior al reintentar
  • Diseño crash-only — estructura que pueda terminar de inmediato sin tareas de limpieza para asegurar idempotencia

Lineamientos — Compatibilidad futura (Future-proofing)

  • Mantén los cambios de forma aditiva y compatible hacia atrás
  • Antes de cambios que rompan compatibilidad, muestra una advertencia previa dentro del programa
  • En general se permiten cambios en la salida para humanos — para scripts, orienta a usar --plain o --json
  • Prohíbe subcomandos catch-all — luego impiden agregar un subcomando real con ese nombre
  • No permitas abreviaturas automáticas de subcomandos — solo aliases explícitos y mantenidos de forma estable
  • Prohíbe las "bombas de tiempo" — minimiza dependencias externas para que siga funcionando incluso dentro de 20 años

Lineamientos — Señales y caracteres de control (Signals)

  • Al recibir Ctrl-C (señal INT), termina de inmediato y pon timeout a las tareas de limpieza
  • Si el usuario pulsa Ctrl-C otra vez durante la limpieza, indícale que puede forzar la salida (véase el ejemplo de Docker Compose)
  • Diseña el programa asumiendo que puede iniciarse con tareas de limpieza incompletas

Lineamientos — Configuración (Configuration)

Prioridad para aplicar configuración (alta → baja):

  • banderas → variables de entorno del shell actual → configuración a nivel proyecto (.env) → configuración a nivel usuario → configuración a nivel sistema

Recomendaciones por tipo de configuración:

  • Configuración que cambia en cada invocación (nivel de debug, dry-run): usar banderas

  • Configuración que cambia por proyecto o por máquina (rutas, color, proxy HTTP): combinación de banderas + variables de entorno

  • Configuración compartida de todo el proyecto (tipo Makefile, package.json): usar archivos versionados

  • Cumple con la especificación XDG Base Directory — se recomiendan rutas de configuración basadas en ~/.config (compatibles con yarn, fish, neovim, tmux, etc.)

  • Si modificas automáticamente archivos de configuración de otros programas, es obligatorio obtener el consentimiento del usuario


Lineamientos — Variables de entorno (Environment Variables)

  • Las variables de entorno son apropiadas para comportamientos que cambian según el contexto de ejecución
  • En los nombres usa solo mayúsculas, números y guion bajo, y no empieces con un número
  • Se recomiendan valores de una sola línea — los valores multilínea causan problemas de compatibilidad con el comando env
  • Revisa primero variables de entorno universales como NO_COLOR, DEBUG, EDITOR, HTTP_PROXY, SHELL, TMPDIR, HOME, PAGER
  • Se recomienda soportar lectura de archivos .env por proyecto — pero .env no reemplaza un archivo de configuración formal
    • Limitaciones de .env: no forma parte del control de versiones, no tiene historial, solo admite el tipo string y es vulnerable a problemas de codificación
  • No leas secretos desde variables de entorno — se propagan a todos los procesos, pueden filtrarse en logs y quedar expuestos mediante Docker inspect o systemctl show
    • Los secretos deben recibirse solo mediante archivos de credenciales, pipes, sockets AF_UNIX o servicios de gestión de secretos

Lineamientos — Nombres (Naming)

  • Usa palabras simples y fáciles de recordar — si son demasiado genéricas, existe riesgo de conflicto con otros comandos
  • Usa solo minúsculas y, si hace falta, guiones (curl es un buen ejemplo; DownloadURL es un mal ejemplo)
  • Mantén el nombre corto, pero nombres extremadamente breves como cd, ls, ps quedan reservados para utilidades de uso general
  • El caso de renombrado del antecesor de Docker Compose, plumfigdocker compose, muestra en la práctica que la facilidad de escritura es un criterio importante al nombrar

Lineamientos — Distribución (Distribution)

  • Siempre que sea posible, distribuye como binario único — usando herramientas como PyInstaller
  • Si no es posible un binario único, usa instaladores de paquetes nativos de la plataforma
  • Indica cómo desinstalar al final de las instrucciones de instalación

Lineamientos — Analítica (Analytics)

  • No envíes datos de uso ni datos de fallos sin consentimiento del usuario
  • Si recopilas datos, publica claramente qué se recopila, por qué, cómo se anonimiza y cuánto tiempo se conserva
  • Se recomienda opt-in por defecto — si usas opt-out, notifícalo claramente en la primera ejecución o en el sitio web
    • Se presentan tres casos: Angular.js (opt-in explícito), Homebrew (Google Analytics, FAQ pública) y Next.js (estadísticas anónimas activadas por defecto)
  • Como alternativas a la analítica, pueden usarse medición de documentación web, medición de descargas y entrevistas directas con usuarios

1 comentarios

 
GN⁺ 2024-02-07
Comentarios de Hacker News
  • Actualmente mucha gente no sabe qué es la línea de comandos, ni le interesa por qué debería usarla.

    • En los años 80 la situación era la misma, pero hoy hay más gente que conoce la línea de comandos que nunca. Podría decirse que es una edad de oro de la CLI (interfaz de línea de comandos).
  • En scripts, no permitas abreviaciones arbitrarias de subcomandos. Por ejemplo, si permites mycmd ins o mycmd i en lugar de mycmd install, ya no podrás agregar un comando nuevo que empiece con i.

    • En scripts se debe evitar usar argumentos cortos. Los argumentos cortos ofrecen comodidad para reducir lo que una persona tiene que teclear, pero en scripts el costo de escribirlos de forma explícita es menor y, considerando la proporción entre lectura y escritura, es más deseable.
  • Considera una opción --dry-run. La función de mostrar por adelantado qué trabajo se realizará sin hacer cambios reales es muy útil para aprender a usar una herramienta y verificar que las opciones complejas estén configuradas correctamente.

  • Cuando stdout no sea una terminal interactiva, no muestres animaciones. Esto evita que las barras de progreso conviertan la salida de logs de CI en un árbol de Navidad.

    • Nunca muestres animaciones en stdout. stderr es para logging, información, etc., y stdout debe ofrecer una salida útil independientemente de si es tty o no.
  • Usa símbolos y emojis solo cuando mejoren la claridad.

    • Los símbolos y emojis pueden renderizarse de forma inconsistente entre terminales, y además pueden gustar o no según las preferencias del usuario, por lo que deben usarse con mucho cuidado.
  • La línea de comandos actual de Unix es, por un lado, "sorprendentemente útil" y, por otro, tiene "defectos de diseño".

    • La utilidad de la línea de comandos de Unix se entiende si piensas en el tiempo que tomaría hacer el mismo trabajo en C o Rust.
    • Los defectos de diseño surgen de que la interfaz de línea de comandos debe ser legible al mismo tiempo para humanos y máquinas. No existe una forma canónica de resolver este problema.
  • Salvo cuando la CLI es muy grande y necesita anidamiento (por ejemplo, aws), la mayoría de las apps prefieren mostrar todas las opciones en la ayuda y dejar que el usuario use less para encontrar lo que necesita.

  • Tradicionalmente, los comandos de UNIX se escribían bajo la suposición de que serían usados principalmente por otros programas.

    • En realidad, estaban pensados para usarse de forma interactiva dentro de un login shell interactivo. Se dividían entre programas que generan salida y filtros de texto "silenciosos", y los programas complejos se escribían en C.
  • No leas contraseñas desde variables de entorno.

    • Las contraseñas solo deberían recibirse mediante archivos de credenciales, pipes, sockets AF_UNIX, servicios de gestión de secretos u otros mecanismos de IPC.
  • El libro más completo sobre lineamientos para CLI es el de Eric Raymond.

    • Ha pasado mucho tiempo, pero al revisar clig.dev se puede ver que las opiniones han cambiado bastante con el tiempo.