Una guía de código abierto que sigue los principios tradicionales de Unix, actualizada para los tiempos modernos
- Filosofía de diseño de CLI
→ Priorizar a las personas
→ Piezas simples que funcionen juntas
→ Mantener la consistencia entre programas
→ Decir solo lo necesario (ni muy poca ni demasiada salida)
→ Hacer que sea fácil de descubrir (ayuda completa, ejemplos, sugerencias del siguiente comando a ejecutar, sugerencias sobre qué hacer cuando hay errores)
→ Como una conversación normal
→ Ser robusto
→ Tener empatía con el usuario
→ Caos: si tienes que romper las reglas, deja clara la intención y el propósito
- Lineamientos para CLI
→ Fundamentos
✓ Usar una biblioteca para parsear la línea de comandos: Go(Cobra,cli), Node(oclif), Python (Click,Typer), Ruby(TTY)
✓ Devolver 0 en caso de éxito y un código distinto de 0 en caso de error
✓ La salida va a stdout
✓ Los mensajes como logs y errores van a stderr
→ Ayuda
✓ Si se ejecuta sin opciones, mostrar ayuda (-h, --help)
✓ Mostrar ayuda concisa por defecto
· Qué hace este programa
· Uno o dos ejemplos de invocación
· Explicación de flags (si no son muchos)
· `--help` para explicación adicional
✓ Con la opción -h, --help mostrar la ayuda completa
✓ Proporcionar una vía para recibir feedback/issues
✓ En la ayuda, ofrecer un enlace a la documentación en versión web
✓ Explicar con ejemplos
✓ Si hay muchos ejemplos, ponerlos en otro lugar (cheat sheet o página web)
✓ No preocuparse por las páginas man (se usan poco y tampoco funcionan en Windows)
✓ Si la ayuda es larga, pasarla a un pager
✓ Mostrar al inicio de la ayuda los flags y comandos más usados
✓ Usar formato en la ayuda (negritas)
✓ Si el usuario hizo algo mal y puedes inferir qué quiso hacer, recomiéndalo
✓ Si tu comando espera recibir algo por pipe, pero stdin es una terminal interactiva, muestra la ayuda y sal de inmediato
→ Salida
✓ La salida human-readable (legible para personas) es lo más importante
✓ Si no afecta la usabilidad, ofrecer salida machine-readable
✓ Si la salida legible para humanos impide la legible para máquinas, ofrecer una opción --plain para integrarse con grep / awk, etc.
✓ Si recibe --json, emitir salida en formato JSON
✓ En caso de éxito, es mejor no tener salida; pero si debe haberla, que sea concisa. Dar soporte a la opción -q para omitir salida innecesaria
✓ Si cambia el estado, díselo al usuario (ver la salida de git push)
✓ Hacer que el estado actual del sistema sea fácil de ver
✓ Recomendar comandos que el usuario pueda ejecutar (como cuando git status muestra git add / restore)
✓ Las acciones que van más allá del interior del programa deben ser explícitas. Por ejemplo, leer o escribir archivos que el usuario no indicó (caché), o conectarse a un servidor remoto (descargar archivos)
✓ Usar ASCII art para aumentar la densidad de información
✓ Usar color con intención. No abusar de él
✓ Desactivar el color si no es una terminal o si el usuario lo solicita
✓ Si stdout no es una terminal interactiva, no mostrar animaciones
✓ Usar símbolos/emojis solo cuando aclaren algo
✓ Por defecto, no mostrar información que solo el creador pueda entender
✓ No usar stderr como si fuera un archivo de logs (al menos no por defecto; muestra niveles como ERR, WARN en modo detallado)
✓ Si vas a mostrar mucho texto, usa una herramienta de paginación como less
→ Error
✓ Captura los errores y reescribe los mensajes para las personas
✓ La relación señal/ruido es importante. Si el mismo error ocurre varias veces, agrúpalo con un encabezado explicativo
✓ Considera cuál es lo primero que va a ver el usuario
✓ Si ocurre un error inesperado/inexplicable, ofrece información de debug/trace y explica cómo enviar ese bug a los desarrolladores
✓ Haz que enviar un bug report no requiera esfuerzo extra. (Por ejemplo, generar una URL con toda la información para que solo tenga que abrirla en el navegador y reportarlo)
→ Argumentos y flags
✓ Argumentos: parámetros posicionales. El orden importa. cp bar foo y cp foo bar son diferentes
✓ Flags: parámetros con nombre. Una letra como -r o varias como --recursive. El orden por lo general no importa.
También pueden incluir un valor del usuario. `--file foo.txt` o `--file=foo.txt`
✓ Preferir flags sobre argumentos. Requieren más escritura, pero son más claros. Si hay muchos argumentos, luego será difícil ampliar funcionalidades
✓ Tener tanto la versión corta como la completa de los flags. Si usas la versión completa en scripts, no hace falta más explicación
✓ Usar flags de una sola letra solo para los flags de uso frecuente
✓ También es válido recibir varios argumentos para una acción simple
✓ Si necesitas dos o más argumentos diferentes, quizá estés haciendo algo mal
✓ Usa nombres estándar para los flags (si ya existen)
-a `--all` , -d `--debug` , -f `--force` , -h `--help` , -o `--output` , -p `--port` , -q `--quiet` , -u `--user`
✓ El valor por defecto debe ser el adecuado para la mayoría de los usuarios
✓ Si el usuario puso un argumento/flag que requiere entrada pero no recibió un valor, pídele el dato al usuario
✓ Siempre ofrece una forma de pasar valores por argumentos/flags; no obligues a usar prompts
✓ Antes de hacer algo peligroso, pide confirmación siempre
✓ Si la entrada o salida es un archivo, da soporte a - para leer desde stdin o escribir a stdout
$ curl https://example.com/something.tar.gz | tar xvf -
✓ Si un flag puede recibir un valor adicional, permite una palabra especial como none. ssh -F none
✓ Cuando sea posible, haz que argumentos, flags y subcomandos no dependan del orden
✓ Permite que valores sensibles (como contraseñas) puedan ingresarse desde un archivo
→ Interactividad
✓ Usa prompts o funciones interactivas solo cuando stdin sea una terminal interactiva
✓ Si se pasa --no-input, no uses prompts ni ninguna función interactiva
✓ Al pedir contraseñas, no muestres lo que el usuario escribe
✓ Haz que el usuario pueda salir fácilmente (no como vim). Permite que Ctrl-C funcione. Si por la naturaleza de ejecutar programas como ssh, tmux, etc. no se puede usar Ctrl-C, indica claramente que ofreces una secuencia de escape que empieza con ~, como en SSH
→ Subcomandos
✓ Las herramientas complejas pueden ofrecer subcomandos para reducir la complejidad.
✓ Además, si hay varias herramientas muy relacionadas, se pueden agrupar bajo un solo comando para hacerlas más cómodas de usar
✓ Mantén consistencia entre subcomandos. Que el mismo flag tenga el mismo significado y formatos de salida similares
✓ Usa nombres consistentes entre varios niveles de subcomandos
✓ No pongas comandos con nombres confusos o demasiado parecidos, como update y upgrade
→ Robustez
✓ Valida toda la entrada del usuario. Verifica pronto y muestra errores comprensibles
✓ La capacidad de respuesta es más importante que la velocidad
✓ Si va a tardar, muestra el progreso
✓ Si es posible, procesa en paralelo. Pero hazlo con cuidado.
✓ Agrega timeouts
✓ Hazlo idempotente (que el resultado no cambie aunque se vuelva a ejecutar). Si hay un error, que se pueda presionar flecha arriba en el shell y continuar desde antes al volver a ejecutar
✓ Hazlo crash-only. Es el siguiente paso después de la idempotencia. Si no hace falta limpiar después del trabajo, o si la limpieza puede posponerse hasta la siguiente ejecución, el programa puede terminar inmediatamente ante una falla o interrupción.
✓ La gente va a hacer mal uso de tu programa
→ Preparación para el futuro
✓ Si es posible, haz cambios de forma aditiva. No rompas compatibilidad cambiando funciones existentes; agrega nuevos flags
✓ Si el cambio no puede ser aditivo, adviértelo primero
✓ La mayoría de los cambios en la salida para personas están bien
✓ Aunque haya un subcomando que la gente usa mucho, no crees un subcomando catch-all que lo ejecute aunque no se especifique
✓ No permitas abreviaturas arbitrarias de subcomandos
✓ No crees “bombas de tiempo” que en algún momento dejarán de funcionar bien
→ Señales y caracteres de control
✓ Si el usuario presiona Ctrl-C (señal INT), interrumpe lo más rápido posible
✓ Si el usuario presiona Ctrl-C durante una limpieza larga, ignóralo. Si lo vuelve a presionar, permite forzar la salida
^CGracefully stopping... (press Ctrl+C again to force)
→ Configuración
✓ Sigue la especificación XDG(X Desktop Group)
✓ Si vas a modificar configuración que no pertenece a tu programa, pide confirmación al usuario y deja claro qué vas a hacer
✓ Aplica los parámetros de configuración por orden de prioridad
flags > variables de entorno del shell > configuración a nivel de proyecto (`.env`) > configuración del usuario > configuración del sistema
→ Variables de entorno
✓ Las variables de entorno son para comportamientos que cambian según el contexto en que se ejecuta el comando
✓ Para maximizar la portabilidad, las variables de entorno solo deben contener mayúsculas/números/guion bajo y no deben comenzar con un número
✓ Si es posible, usa valores de una sola línea en las variables de entorno
✓ No uses nombres de uso amplio
✓ Si es posible, revisa y usa variables de entorno comunes
`NO_COLOR`, `DEBUG`, `EDITOR`, `HTTP_PROXY`, `SHELL`, `TERM`, `TERMINFO`, `HOME`, `TMPDIR`, `PAGER`, `LINES` ..
✓ Si hace falta, carga variables de entorno desde .env
✓ No uses la extensión .env para archivos de configuración
→ Nombres
✓ Usa palabras simples y fáciles de recordar
✓ Usa solo minúsculas y - (guion) solo cuando sea realmente necesario
✓ Si es posible, que sea corto
✓ Que sea fácil de escribir en el teclado
→ Distribución
✓ Si es posible, distribúyelo como un solo binario
✓ Haz que sea fácil de desinstalar
→ Analítica
✓ No envíes datos de uso ni de crashes de la herramienta sin el consentimiento del usuario
3 comentarios
Gracias por el buen contenido.
Parece que cada vez hay más buenas herramientas de línea de comandos gracias a Rust y Go, que permiten crear muy bien binarios únicos.
Colección de herramientas útiles de línea de comandos de estos días https://es.news.hada.io/topic?id=793
Utilidades de línea de comandos en Rust que mejoran la productividad https://es.news.hada.io/topic?id=2958
Crear este tipo de herramientas también se está volviendo más fácil y más potente.
Crear apps de línea de comandos con Rust https://es.news.hada.io/topic?id=972
Caporal.js - framework completo para desarrollar CLI en Node https://es.news.hada.io/topic?id=2378
create-node-cli - crear CLI fácilmente con Node.js https://es.news.hada.io/topic?id=3268
Crear GUI para cualquier lenguaje y herramienta CLI con Gooey https://es.news.hada.io/topic?id=582
ink - crear CLI con React https://es.news.hada.io/topic?id=2041
Aprendí mucho mientras hacía una traducción sencilla. Después de terminarla... también pensé que quizá habría sido mejor traducir el repo completo. ^^;;