2 pontos por GN⁺ 2024-02-07 | 1 comentários | Compartilhar no WhatsApp
  • Este guia atualiza de forma moderna os princípios tradicionais do UNIX para oferecer diretrizes de design open source voltadas à criação de CLIs boas para humanos e robustas para automação
  • Uma boa CLI deve funcionar com prioridade para pessoas, mas também seguir convenções como stdout/stderr, códigos de saída, pipes e JSON para se combinar naturalmente com outros programas
  • Ajuda, documentação, mensagens de erro e saída devem permitir que o usuário saiba qual é o próximo passo, e --help, exemplos, sugestões, indicadores de progresso e explicação de status cumprem papel central
  • Argumentos, flags, interação, configuração e variáveis de ambiente devem considerar tanto scripts quanto o uso no terminal, e por segurança é melhor não receber valores secretos diretamente por flags ou variáveis de ambiente
  • O nome da CLI, sua distribuição e até a coleta de análises não devem prejudicar a sensação de controle do usuário nem a compatibilidade de longo prazo; mesmo ao quebrar convenções, o objetivo deve ser claro

Filosofia básica do design de CLI

  • Este guia é um documento open source que, com base nos princípios tradicionais do UNIX, apresenta princípios e diretrizes concretas para o design moderno de programas de linha de comando
  • No passado, a linha de comando era um ambiente voltado à máquina, próximo de um REPL sobre uma plataforma de scripting, mas hoje a CLI tem forte caráter voltado às pessoas como uma UI baseada em texto para acessar várias ferramentas, sistemas e plataformas
  • A linha de comando tem limitações antigas e convenções peculiares, mas pode ser usada em praticamente qualquer notebook, permite tanto uso interativo quanto automação, e muda mais lentamente do que outros componentes do sistema
  • Este guia não trata de programas de terminal em tela cheia como emacs ou vim, nem depende de uma linguagem de programação ou toolchain específica

Uma CLI voltada às pessoas e combinável

  • Se a CLI é uma ferramenta usada principalmente por pessoas, então é preciso considerar as pessoas primeiro
    • Muitos programas de CLI são destinados a usuários humanos, mas ainda mantêm designs de interação herdados de um passado centrado em máquinas
  • A filosofia UNIX, em que programas pequenos funcionam juntos por meio de interfaces limpas, continua importante
    • Convenções como entrada padrão, saída padrão, erro padrão, sinais e códigos de saída tornam possível a composição entre programas
    • Texto simples em linhas é fácil de passar por pipes, e JSON é útil quando são necessários dados mais estruturados
  • A consistência é a chave para transformar o custo de aprendizado de uma CLI em eficiência de longo prazo
    • Seguir padrões existentes permite que o usuário adivinhe comandos e opções com mais facilidade
    • Quando uma convenção prejudica a usabilidade, ela pode ser quebrada com cuidado
  • Uma boa CLI não deve ser silenciosa demais nem gerar saída demais; ela deve fornecer ao usuário exatamente a informação de que ele precisa
  • Costuma-se dizer que a CLI tem menos capacidade de descoberta do que uma GUI, mas é possível aumentar a possibilidade de aprendizado com ajuda abrangente, exemplos, sugestões de próximos comandos e orientações de resposta a erros

Interação conversacional e robustez

  • O uso da linha de comando se parece mais com uma conversa, com várias tentativas e correções, do que com uma única execução
    • Para entradas incorretas, é possível sugerir correções quando viável
    • É possível mostrar com clareza o estado intermediário de tarefas em várias etapas
    • É possível pedir confirmação antes de operações perigosas
  • A CLI deve ser realmente robusta e, ao mesmo tempo, fazer o usuário sentir que ela é robusta
    • Deve lidar com entradas inesperadas de forma elegante
    • Quando possível, as operações devem ser idempotentes
    • Não deve exibir por padrão stack traces assustadores na saída
  • É importante ter empatia para que o usuário sinta que o software está do seu lado
    • Não se trata de usar emojis nem de gamificar, mas de projetar com cuidado para que o usuário tenha sucesso
  • O ecossistema de terminal tem muita inconsistência e confusão, mas suas poucas restrições permitiram novas invenções
    • Vale seguir padrões existentes, mas também é possível abandonar deliberadamente padrões que prejudiquem a produtividade ou a satisfação do usuário

Regras básicas que devem ser seguidas

  • Sempre que possível, deve-se usar uma biblioteca de parsing de argumentos de linha de comando
  • Em caso de sucesso, o código de saída deve ser 0; em caso de falha, deve retornar um valor diferente de 0
    • Scripts determinam o sucesso ou a falha do programa com base no código de saída
  • A saída principal do comando deve ir para stdout
    • Até mesmo saídas legíveis por máquina devem ir por padrão para stdout, para que os pipes funcionem corretamente
  • Mensagens para o usuário, como logs e erros, devem ir para stderr
    • Ao encadear com pipes, isso evita que as mensagens se misturem à entrada do próximo comando

Design da ajuda

  • Quando -h e --help forem passados, deve-se mostrar ajuda suficiente
    • Subcomandos também podem ter sua própria ajuda
    • -h não deve ser sobrecarregado com outro significado
  • Se um comando que exige argumentos for executado sem nenhum argumento, deve-se mostrar uma ajuda concisa
    • Descrição do programa
    • Um ou dois exemplos de chamada
    • Explicação das flags
    • Orientação para usar --help para informações mais detalhadas
  • É recomendável incluir caminhos de suporte na ajuda
    • Links para site ou GitHub são comuns
  • Se houver documentação na web, a ajuda deve apontar para ela
    • Se houver uma página ou âncora específica para um subcomando, é útil ligar diretamente a ela
  • Como os usuários tendem a recorrer a exemplos antes da documentação, é bom colocar exemplos no início da ajuda
    • Pode-se mostrar primeiro casos de uso complexos, mas comuns
    • Se houver muitos exemplos, o ideal é colocá-los em um comando de cheat sheet separado ou em uma página web
  • A ajuda pode ser formatada para facilitar a leitura rápida
    • Títulos em negrito melhoram a legibilidade
    • Isso deve ser tratado de forma independente do terminal, para que caracteres de escape não apareçam literalmente
  • Se o usuário digitou algo errado e for possível inferir a intenção, deve-se sugerir uma correção
    • brew update jq pode, por exemplo, orientar o usuário para brew upgrade jq
    • Pode-se perguntar se o comando sugerido deve ser executado, mas é melhor evitar executá-lo à força
  • Se um comando precisa receber entrada por stdin, mas o stdin é um terminal interativo, ele deve mostrar ajuda imediatamente e encerrar, ou então exibir uma mensagem em stderr

Documentação

  • A ajuda fornece um resumo imediato e orientação para tarefas comuns, enquanto a documentação trata com mais detalhe do objetivo da ferramenta, do que ela não pretende fazer, de como funciona e do uso completo
  • Deve-se fornecer documentação baseada na web
    • Ela é pesquisável e permite links para partes específicas
    • A documentação web é o formato de documentação mais abrangente
  • Também deve-se fornecer documentação baseada em terminal
    • Ela é rapidamente acessível
    • Fica sincronizada com a versão instalada da ferramenta
    • Funciona mesmo sem internet
  • Pode-se considerar oferecer páginas man
    • Muitos usuários verificam primeiro man mycmd
    • Assim como git e npm, é possível acessar páginas man por meio de um subcomando help

Princípios de saída

  • A saída legível para humanos é o mais importante
    • Um critério simples para decidir se um determinado stream de saída é voltado para leitura humana é se ele é um TTY
  • Deve-se fornecer saída legível por máquina desde que isso não prejudique a usabilidade
    • Um stream de linhas em texto simples é a interface universal do UNIX
    • Os usuários esperam poder passar a saída para ferramentas como grep e que tudo funcione como esperado
  • Se uma saída amigável para humanos quebrar a saída amigável para máquinas, pode-se oferecer --plain
    • Em saídas tabulares, dividir células em várias linhas pode quebrar a expectativa de um registro por linha
    • --plain fornece uma saída em formato de tabela sem alterações para uso em scripts
  • Se --json for passado, deve-se emitir JSON formatado
    • JSON facilita lidar com estruturas de dados complexas e pode ser usado com jq e várias ferramentas CLI para JSON
    • Também é adequado para conexão direta por pipe com serviços web via curl
  • Em caso de sucesso, deve haver saída, mas ela deve ser curta
    • Comandos UNIX tradicionais muitas vezes não exibem nada quando não há problemas, mas para humanos isso pode parecer que travou
    • Se for necessário não haver saída para scripts, é possível ocultar saídas não essenciais com a opção -q
  • Comandos que alteram estado devem informar ao usuário o que foi alterado
    • git push é um exemplo que mostra o que está sendo feito e o novo estado do branch remoto
  • Deve ser fácil ver o estado atual do sistema
    • git status mostra o estado do repositório junto com dicas sobre os próximos comandos que podem ser executados
  • Operações que atravessam os limites do mundo interno do programa geralmente devem ser explícitas
    • Quando lê ou grava arquivos que o usuário não passou como argumento
    • Quando se comunica com um servidor remoto para baixar arquivos
  • As cores devem ser usadas com intenção
    • Se forem usadas demais, perdem o significado e dificultam a leitura
    • As cores devem ser desativadas se não for um TTY, se NO_COLOR estiver definido, se TERM=dumb estiver definido ou se --no-color for passado
  • Não se deve exibir animações se stdout não for um terminal interativo
    • Isso ajuda a evitar que indicadores de progresso deixem logs de CI confusos
  • Ao exibir muito texto, pode-se usar um pager como less
    • É melhor usar isso apenas quando stdin ou stdout forem terminais interativos
    • less -FIRX pode ser usado como um conjunto razoável de opções

Mensagens de erro

  • Se você tratar erros como documentação, poderá reduzir o tempo que os usuários gastam procurando a documentação
  • Erros previsíveis devem ser capturados e reescritos para que humanos consigam entendê-los
    • Ex.: informar que não é possível gravar em file.txt e que talvez seja necessário chmod +w file.txt
  • A relação sinal-ruído é importante
    • Quanto mais saída irrelevante houver, mais difícil será para o usuário identificar o problema
    • Se houver vários erros do mesmo tipo, eles podem ser agrupados sob um único cabeçalho explicativo
  • É melhor colocar a informação mais importante no fim da saída
    • O olhar do usuário é atraído por texto vermelho, então isso deve ser usado com moderação e propósito
  • Se o erro for inesperado ou difícil de explicar, deve-se fornecer informações de debug/traceback e uma forma de enviar um bug report
    • Para não sobrecarregar o usuário, os logs de debug podem ser gravados em um arquivo em vez de no terminal
  • Deve ser fácil enviar um bug report
    • Um exemplo é fornecer uma URL com as informações possíveis já preenchidas

Argumentos e flags

  • Argumentos são parâmetros posicionais, e flags são parâmetros nomeados
    • Em cp foo bar, os caminhos de arquivo são argumentos
    • -r, --recursive, --file foo.txt são flags
  • Sempre que possível, deve-se preferir flags a argumentos
    • Exigem mais digitação, mas o significado é claro
    • Fica mais fácil mudar a forma de entrada no futuro
  • Toda flag deve ter um nome longo
    • Como ter -h e --help juntos
    • Isso é útil para deixar o significado explícito em scripts
  • Flags de uma letra devem ser usadas apenas para opções frequentes
    • Isso ajuda a preservar o espaço de nomes curtos para flags que forem adicionadas depois
  • Quando a mesma operação simples é aplicada a vários arquivos, vários argumentos são apropriados
    • Um formato como rm file1.txt file2.txt file3.txt combina bem com globbing
  • Se houver dois ou mais argumentos com significados diferentes, é bem provável que o design esteja errado
    • Como exceção, ações comuns e centrais como cp <source> <destination> são tão curtas que vale a pena memorizá-las
  • Se houver nomes de flags padronizados, eles devem ser seguidos
    • -a, --all
    • -d, --debug
    • -f, --force
    • --json
    • -h, --help
    • -n, --dry-run
    • --no-input
    • -o, --output
    • -p, --port
    • -q, --quiet
    • -u, --user
    • --version
  • Os valores padrão devem corresponder ao comportamento correto para a maioria dos usuários
    • Se você presumir que os usuários vão encontrar, lembrar e usar a flag certa toda vez, a experiência da maioria piora
  • Se não houver entrada, pode-se perguntar por prompt, mas o prompt não deve ser obrigatório
    • Sempre deve haver uma forma de fornecer a entrada por flag ou argumento
    • Se stdin não for um terminal interativo, o prompt deve ser ignorado e as flags ou argumentos necessários devem ser exigidos
  • Operações perigosas devem pedir confirmação antes
    • Em execuções interativas, pode-se pedir que o usuário digite y ou yes
    • Em execuções não interativas, pode-se exigir -f ou --force
    • Em operações graves, pode-se exigir que o nome do item a ser apagado seja digitado manualmente ou fornecer uma flag como --confirm="name-of-thing"
  • Se aceitar entrada/saída de arquivos, deve-se oferecer suporte a - para stdin ou stdout
    • Isso permite conectar a saída de outros comandos sem arquivos temporários
  • Sempre que possível, argumentos, flags e subcomandos devem ser processados independentemente da ordem
    • Porque é comum o usuário adicionar opções ao fim de um comando anterior e executá-lo novamente
  • Valores secretos não devem ser lidos diretamente por flag
    • Um valor de --password pode aparecer na saída de ps ou no histórico do shell
    • Deve-se considerar entrada por arquivo, como --password-file, ou por stdin

Interação

  • Prompts ou elementos interativos devem ser usados apenas quando stdin for um TTY
    • Durante execução em scripts ou entrada por pipe, prompts não funcionam, então deve-se informar por erro quais flags precisam ser passadas
  • Se --no-input for passado, não deve haver prompts nem comportamento interativo
    • Se a entrada for necessária, a execução deve falhar e orientar como passá-la por flag
  • Ao receber uma senha, o valor digitado pelo usuário não deve ser exibido
    • Isso é feito desativando o echo do terminal
  • O usuário deve conseguir sair
    • Mesmo se estiver parado em I/O de rede etc., Ctrl-C deve funcionar
    • Se for um wrapper como SSH, tmux ou telnet, que não pode ser encerrado com Ctrl-C, a forma de sair deve ser informada claramente

Subcomandos

  • Se a ferramenta for complexa o suficiente, um conjunto de subcomandos pode reduzir a complexidade
    • Agrupar várias ferramentas relacionadas em um único comando pode facilitar o uso e a descoberta
    • Também é bom para compartilhar flags globais, ajuda, configuração e mecanismos de armazenamento
  • É necessário haver consistência entre subcomandos
    • O mesmo significado deve usar o mesmo nome de flag
    • O formato de saída também deve ser parecido
  • Para subcomandos com vários níveis, deve-se usar um esquema de nomes consistente
    • Um padrão comum é ter duas etapas, com substantivo e verbo, como em docker container create
    • Tanto substantivo verbo quanto verbo substantivo são possíveis, mas substantivo verbo parece ser mais comum
  • Devem ser evitados comandos com nomes ambíguos ou muito parecidos
    • Ter update e upgrade ao mesmo tempo pode causar confusão

Robustez

  • Todos os dados inseridos pelo usuário devem ser validados
    • Como dados inválidos podem ser fornecidos, é preciso verificar cedo e encerrar com um erro compreensível
  • Responsividade é mais importante do que rapidez
    • É bom mostrar algo ao usuário em até 100 ms
    • Também é preciso exibir uma mensagem antes de fazer uma requisição de rede para não parecer que travou
  • Operações demoradas devem mostrar o progresso
    • Se não houver saída por algum tempo, o programa pode parecer quebrado
    • Se o indicador de progresso ficar parado por muito tempo, você pode mostrar uma estimativa de tempo restante ou uma animação para indicar que o trabalho continua
  • Faça processamento paralelo quando possível, mas com cuidado
    • Mostrar o progresso de tarefas paralelas no shell é difícil, e a saída misturada causa confusão
    • As várias barras de progresso de docker pull são um exemplo de como mostrar o que está acontecendo
    • Mesmo que, em funcionamento normal, os logs fiquem escondidos atrás da barra de progresso, em caso de erro os logs devem ser exibidos para permitir depuração
  • Operações de rede devem ter timeout
    • O timeout deve ser configurável e precisa ter um valor padrão razoável
  • Deve ser possível se recuperar após falhas
    • Após uma falha temporária, ao executar novamente com <up> e <enter>, deve ser possível continuar do ponto em que parou
  • Sempre que possível, o modelo crash-only é preferível
    • Em caso de falha ou interrupção, deve ser possível encerrar imediatamente, deixando a limpeza para a próxima execução
  • Os usuários podem usar a ferramenta de formas inesperadas
    • Eles podem envolvê-la em scripts, usá-la em uma internet instável, executar várias instâncias ao mesmo tempo ou rodá-la em ambientes não testados

Compatibilidade futura

  • Subcomandos, argumentos, flags, arquivos de configuração e variáveis de ambiente são todos interfaces e devem continuar funcionando por muito tempo
  • Sempre que possível, as mudanças devem ser aditivas
    • Em vez de quebrar o comportamento de uma flag existente, é possível adicionar uma nova flag
  • Se uma mudança não aditiva for necessária, deve haver aviso prévio
    • Ao usar uma flag descontinuada, deve-se avisar sobre a mudança futura e também informar a forma compatível com o futuro que já pode ser usada agora
  • Saída para humanos geralmente pode mudar sem grandes problemas
    • Os usuários devem ser orientados a usar --plain ou --json em scripts para obter saída estável
  • Evite subcomandos catch-all
    • Tratar automaticamente qualquer subcomando desconhecido como run pode quebrar usos existentes quando novos subcomandos forem adicionados no futuro
  • Não permita abreviações arbitrárias de subcomandos
    • Se install também aceitar i ou ins, depois fica difícil adicionar outros comandos que comecem com i
    • Aliases devem ser explícitos e mantidos de forma estável
  • Considere se o comando ainda será executado da mesma forma daqui a 20 anos
    • Não crie uma bomba-relógio em que dependências externas da internet mudam ou desaparecem e fazem o comando parar de funcionar

Sinais e caracteres de controle

  • Se o usuário enviar Ctrl-C, ou seja, o sinal INT, o programa deve encerrar o mais rápido possível
    • Deve informar algo imediatamente, antes de começar qualquer limpeza
    • O código de limpeza deve ter timeout
  • Se Ctrl-C for pressionado novamente durante uma limpeza demorada, deve ser possível pular a limpeza
    • O Docker Compose informa que, ao pressionar Ctrl-C mais uma vez durante o encerramento, é possível forçar a parada imediata dos contêineres
  • O programa deve assumir que pode iniciar sem que a limpeza anterior tenha sido executada

Configuração e variáveis de ambiente

  • A forma de configuração deve variar conforme especificidade, estabilidade e complexidade
    • Configurações que mudam com frequência a cada execução são mais adequadas para flags
    • Configurações relativamente estáveis que variam por usuário, projeto ou máquina são adequadas para flags e variáveis de ambiente
    • Configurações estáveis para todos os usuários dentro de um projeto são adequadas para arquivos de configuração específicos do comando e versionados
  • É recomendável seguir a XDG Base Directory Specification
    • O objetivo é oferecer suporte a locais de configuração de uso geral, como ~/.config, reduzindo a proliferação de dotfiles no diretório home
  • Se o programa modificar automaticamente arquivos que não sejam sua própria configuração, deve pedir consentimento ao usuário e informar exatamente o que fará
    • Em vez de acrescentar conteúdo a um arquivo de configuração de sistema existente, é melhor criar um novo arquivo de configuração
  • A precedência de configuração deve ser aplicada da mais alta para a mais baixa
    • Flags
    • Variáveis de ambiente do shell em execução
    • Configuração no nível do projeto
    • Configuração no nível do usuário
    • Configuração de todo o sistema
  • Variáveis de ambiente são adequadas para comportamentos que mudam conforme o contexto em que o comando é executado
  • Nomes de variáveis de ambiente devem usar apenas letras maiúsculas, números e sublinhados para máxima portabilidade, e não devem começar com número
  • Sempre que possível, os valores devem ter uma única linha
    • Valores com várias linhas causam problemas de usabilidade quando usados com o comando env
  • Não se deve ocupar de forma imprudente nomes amplamente usados
    • Você pode consultar a lista de variáveis de ambiente padrão do POSIX
  • Sempre que possível, verifique variáveis de ambiente genéricas
    • NO_COLOR, FORCE_COLOR
    • DEBUG
    • EDITOR
    • HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY
    • SHELL
    • TERM, TERMINFO, TERMCAP
    • TMPDIR
    • HOME
    • PAGER
    • LINES, COLUMNS
  • Quando apropriado, é possível ler variáveis de ambiente de .env
    • Isso é útil para variáveis que quase nunca mudam enquanto se trabalha em um diretório específico
  • .env não substitui um arquivo de configuração formal
    • Muitas vezes ele não é armazenado no controle de código-fonte
    • Só há tipo string
    • Tende a ficar desorganizado
    • Está mais sujeito a problemas de codificação
    • É fácil conter credenciais sensíveis e material de chaves
  • Valores secretos não devem ser lidos de variáveis de ambiente
    • Variáveis de ambiente podem ser expostas por processos, logs, docker inspect, systemctl show etc.
    • Segredos devem ser recebidos por arquivo de credenciais, pipe, socket AF_UNIX, serviço de gerenciamento de segredos ou outro mecanismo de IPC

Nomes, distribuição e coleta de métricas

  • O nome de um programa CLI deve ser simples e fácil de lembrar, porque os usuários o digitam com frequência
    • Se for genérico demais, pode entrar em conflito com outros comandos ou confundir os usuários
  • É recomendável que o nome use apenas letras minúsculas e hífens quando necessário
    • curl é um bom nome, enquanto DownloadURL é um exemplo ruim
  • O nome deve ser curto, mas nomes curtos demais combinam com utilitários muito comuns, como cd, ls e ps
  • Deve ser um nome fácil de digitar
    • Há o caso do nome inicial do Docker Compose, plum, que foi mudado para fig porque era desconfortável de digitar com uma mão só
  • Sempre que possível, deve ser distribuído como um binário único
    • Se um binário único for difícil, deve ser distribuído de forma fácil de remover usando o instalador de pacotes padrão da plataforma
    • Ferramentas específicas de linguagem, por exemplo linters de código, podem presumir que o usuário tem o interpretador dessa linguagem
  • Deve ser fácil de remover
    • Como é comum querer removê-lo logo após a instalação, convém colocar as instruções de remoção abaixo das instruções de instalação
  • Métricas de uso podem ajudar a melhorar a ferramenta, mas usuários de CLI esperam controlar o próprio ambiente
  • Não envie dados de uso ou de falhas sem consentimento
    • Deve ficar claro o que é coletado, por que é coletado, quanto é anonimizado, como é anonimizado e por quanto tempo é armazenado
    • Idealmente, o melhor é opt-in; se optar por coleta padrão com opt-out, isso deve ser informado claramente no site ou na primeira execução, e deve ser fácil de desativar
  • Também é possível considerar alternativas à coleta analítica
    • Instrumentar a documentação web
    • Instrumentar downloads
    • Conversar diretamente com usuários e incentivar feedback e pedidos de funcionalidades na documentação e no repositório

1 comentários

 
GN⁺ 2024-02-07
Opiniões do Hacker News
  • A frase “hoje em dia, a maioria nem sabe o que é uma linha de comando” está certa, mas isso também era verdade nos anos 1980, que é o período que o TFA toma como referência
    A diferença é que hoje há mais pessoas do que nunca que conhecem e conseguem usar a linha de comando; o número cresceu pelo menos em uma ordem de grandeza, talvez em duas, então dá para chamar de era de ouro da CLI

    • Estou anexando ferramentas de linha de comando a partes de apps para quebrar monólitos
      Estado global e dependências atrapalham o raciocínio e, no fim, também dificultam depuração e otimização de desempenho
      Quando você extrai códigos de 500, 1.000, 5.000, 10.000 linhas, às vezes 50 mil linhas, para que rodem separadamente, muita coisa fica bem mais clara
      Se for bem feito, isso também pode induzir uma estrutura de Functional Core, Imperative Shell, porque uma linha de comando alinhada à filosofia Unix deve ter muitas operações sem efeitos colaterais e poucas com efeitos colaterais
      Você pode criar pequenos comandos que geram e exibem o estado do sistema imediatamente antes de uma decisão ruim ser tomada, e que são efetivamente seguros para rodar até em produção
      Assim fica mais fácil entregar essas ferramentas também às pessoas que precisam estar incluídas no bus factor e envolvê-las
    • Se você trocar “pessoas” por “usuários de computador”, o ponto do autor está correto
      O que alguém de uma aldeia de uma civilização isolada na Amazônia pensa de um terminal não é relevante
    • É preciso distinguir números absolutos e proporções
      Para julgar uma mudança qualitativa em algo cuja quantidade cresceu, é preciso olhar para a proporção; olhar só para números absolutos produz uma frase verdadeira, mas sem significado
      Por exemplo, o número absoluto de ouvintes de jazz hoje talvez seja maior do que no auge cultural do gênero, mas isso não é porque o jazz é mais popular, e sim porque há mais gente ouvindo música
      Ainda assim, é difícil dizer que o jazz é mais importante e influente nos EUA hoje do que foi no auge
    • O ponto essencial é se isso também era verdade em termos de proporção entre usuários de computador nos anos 1980
  • Gostaria que também considerassem uma opção --dry-run, que mostra antecipadamente o que aconteceria sem fazer mudanças de fato
    É realmente útil para aprender uma ferramenta ou verificar se você usou corretamente opções complexas e globs de arquivos antes de executar algo difícil de desfazer

    • Se um comando não puder ser desfeito e tiver efeitos colaterais grandes, é melhor deixar --dry-run como padrão e exigir a flag --execute para a execução real
    • O WhatIf do PowerShell é um dos meus recursos favoritos
      Não sei como eu teria feito sem ele, e ele me salvou várias vezes de situações em que eu quase destruí tudo completamente
    • Por outro lado, prefiro que o comportamento padrão não faça alterações reais e que seja necessário passar --commit para executar de verdade
      Para scripts que fazem operações irreversíveis ou difíceis de reverter, esse jeito parece mais seguro
    • Fico curioso sobre exemplos de ferramentas de linha de comando com flag de dry run
      Estou confuso sobre como projetar isso nas ferramentas que estou criando
      Quando você chama uma API para buscar dados, não sei como transformar isso em dry run
      Devo fornecer exemplos fictícios e saídas fictícias?
    • Acho que dry-run deveria ser uma função que pudesse ser encadeada por pipe a qualquer comando: do_thing.py | dry-run
      É como pedir no prompt “explique o processo passo a passo”
  • Não é apenas uma questão de não mostrar animações quando a saída padrão não for um terminal interativo; nunca se deve mostrar animações em stdout
    O TFA era bom no geral, mas fiquei decepcionado ao procurar a parte em que explicaria a diferença entre stderr e stdout e ver que isso não acontecia
    stderr não é só para erros; é o lugar para logs e para toda saída informativa, e, se for um terminal, também é a área onde animações podem aparecer
    stdout deve ser a saída real e útil, e deve ser consistente independentemente de ser um terminal ou não
    Por exemplo, em echo foo | mysed 's/oo/aa/' | cat, o stdout de mysed é faa, e stderr deve receber logs como informações de versão ou o que foi encontrado
    Não quero brigar com a ferramenta usando grep só para obter a saída real, nem quero que o comportamento mude quando removo | cat, dificultando a depuração

    • Refinando um pouco a regra “stdout é a saída útil”, stdout deve ser aquilo que o usuário pediu
      Se ele pediu --help, deve ir para stdout; se pediu logs, os logs também devem ir para stdout
      Se for informação não solicitada, stderr é o lugar certo
    • É uma boa regra, mas o nome é uma pena
      Se desse para voltar no tempo, nomes como stdin, stdout e stdext talvez tivessem feito as pessoas seguirem essa convenção
      Mas como o nome é stderr, desenvolvedores pensam, de forma razoável, que é “para relatar erros”, e colocam em stdout tudo que não é erro
      Ainda assim, como estrutura de programa, esse jeito é melhor; mesmo que confunda mais no começo, permite fazer coisas mais úteis com a saída, então vale a pena
    • Nesse caso, fico curioso: onde as animações deveriam ser exibidas?
      Também acho difícil considerar cores como informação
  • Espero que evitem o conselho “use símbolos e emojis onde eles tornarem as coisas mais claras”
    O yubikey-agent citado como exemplo mostra exatamente o que eu detesto em READMEs do GitHub e interfaces de usuário brincalhonas
    Tecnicamente, símbolos e emojis podem ser renderizados de formas diferentes em cada terminal, tornando as mensagens confusas
    Esteticamente, a tolerância a brincadeira e vivacidade varia muito de pessoa para pessoa, então devem ser usados com muita moderação e só quando você realmente souber o que está fazendo

    • Eu gosto desse tipo de saída
      Também gosto de saída colorida no terminal, e emojis têm cores, o que ajuda a captar rapidamente o panorama da situação
      Também gosto de realce de sintaxe; sem ele, acho código muito mais difícil de ler
      Nem todo mundo é assim e, assim como não espero que meu gosto seja o padrão, gostos opostos também não deveriam ser impostos como padrão
    • Se for um recurso opcional, acho bom
      Há quem goste e quem não goste, então basta deixar desligado por padrão e permitir que o usuário escolha
    • Como diretriz, seria bom limitar o vocabulário de emojis usado na saída a no máximo dois
      Por exemplo, um para sucesso e um para falha já basta
      E a informação transmitida pelo emoji deve obrigatoriamente ser repetida também em texto
    • Concordo fortemente
      Na primeira vez que vi o CLI Guidelines, parei de ler naquela seção
      Desta vez li o restante também, e no geral achei bem decente
  • Entendo que algumas CLIs sejam muito grandes, como aws, e precisem de aninhamento, mas é realmente frustrante seguir por uma CLI aninhada
    Para a maioria dos apps, prefiro que o help despeje todas as opções e me deixe encontrar o que preciso com less

    • Tenho criado muitos apps TUI, e coloco em todos eles um bom front-end TUI que pessoas menos técnicas, como QA, possam usar
      Essa TUI é uma UI/UX pura, opcional, e passa as configurações selecionadas para um arquivo gerado separado ou para uma função
      Esse arquivo ou função sempre pode ser chamado diretamente pelo terminal, e também oferece todas as opções e ajudas usadas pela TUI
      Assim, também pode ser usado em scripts e é útil para usuários de vários níveis de experiência
    • Depende da quantidade de subcomandos e de quão claros eles são
      Se você sabe qual subcomando precisa, reduzir as opções apenas ao necessário é bem útil, mas, se não sabe, pode ser doloroso
      Vasculhar uma lista grande de opções com grep também é difícil, então é preciso equilíbrio
    • Não é esse o papel das páginas man?
    • Uma abordagem em que o --help dos subcomandos é expandido no comando pai pode ajudar
      Por exemplo, git stash --help mostrar exatamente a ajuda de git stash, incluindo cada item
  • A explicação de que “tradicionalmente, comandos UNIX eram escritos principalmente partindo do pressuposto de que seriam usados por outros programas” não é precisa
    Originalmente, eles eram pensados sobretudo para uso interativo dentro de um shell de login
    Havia programas que produziam saída em stdout (ls, cat, find, tty, who, date) e filtros de texto silenciosos (tr, grep, cut, uniq, sort, wc), e naquela época era possível fazer tarefas básicas de computação com comandos de uma linha
    Programas complexos eram escritos em C e, depois que surgiram linguagens específicas de domínio como sed e AWK, alguns programas com bastante processamento de strings foram movidos para o shell
    Shell não é um ambiente de programação adequado, e nunca foi pensado para esse uso

    • Há casos em que um programa de 10 mil linhas em C vira 10 linhas de shell, e casos em que um programa de shell de mil linhas poderia ter sido 100 linhas em C
      Hoje em dia, muitas vezes é melhor fazer ambos em Python ou Lua, mas shell e C são os que estão instalados de forma mais universal
    • Adequado ou não, shell é uma linguagem de programação, e isso era muito mais evidente no Unix inicial
      A divisão em sh, bash, ksh, csh é um bom exemplo
      Acho bastante razoável ver o conjunto padrão de ferramentas POSIX como a biblioteca-padrão de várias linguagens de shell
  • A linha de comando Unix atual, por um lado, é “incrivelmente útil” e, por outro, é quebrada por design
    A utilidade fica clara quando se pensa em quanto tempo levaria para escrever o exemplo abaixo em C ou Rust:
    curl -sS https://go.dev/doc/devel/release | html2text | grep -o -P '\bgo\d+\.\d+\.\d+\b' | sort -V | uniq | tail -1
    Mas, para ver por que ela é quebrada por design, basta olhar https://news.ycombinator.com/item?id=29747034
    O problema é que interfaces de linha de comando precisam ser legíveis por humanos e, ao mesmo tempo, legíveis por máquinas, e não há uma forma padrão de resolver isso

    • Esse exemplo em si mostra bem por que é quebrado por design
      Talvez pudesse ter havido originalmente uma solução padrão para o problema de precisar ser legível por humanos e máquinas ao mesmo tempo
      A Apple criou as Human Interface Guidelines para concretizar o significado das abstrações visuais da UI de desktop
      Mas a linha de comando não foi criada por pessoas que pensavam como designers da Apple; foi criada por pessoas que pensavam “preguiça, impaciência e arrogância são virtudes, então como expresso o que quero com o mínimo de código?”
      Na época, não foi uma escolha errada, e cada byte importava, mas essa decisão de design agora ficou cravada em uma cadeia de ferramentas que não se pode mover
      Para conseguir algo melhor do que temos hoje, parece que seria preciso praticamente abandonar a cadeia de ferramentas POSIX, e é difícil imaginar construir, sobre a base atual, uma UX descobrível e conceitualmente consistente
    • Como computadores existem, no fim das contas, para nos servir, a solução final deveria ser fazer as máquinas lerem tão bem quanto as pessoas
    • Na prática, normalmente não é “ao mesmo tempo”; humanos e máquinas costumam usar o mesmo comando em momentos diferentes
      Mesmo no mesmo momento, há muitas formas de permitir saídas diferentes
      Só que precisa haver um padrão que todos sigam, e é exatamente aí que a coisa se complica
    • Esse exemplo não só não prova que é quebrado por design como também parece mostrar uma solução relativamente fácil
      Se o problema é que quase nenhuma implementação de ls permite terminar nomes de arquivos com caractere NUL em vez de quebra de linha, a solução parece tão óbvia que sinto que estou deixando passar algo
      A interface do shell rejeita isso por algum motivo?
    • A abordagem tratada no texto é um modo de saída legível por máquinas, como --json
  • Ao dizer “não leia segredos de variáveis de ambiente”, o guia sugere arquivos de credenciais, pipes, sockets AF_UNIX, serviços de gerenciamento de segredos e outras formas de comunicação entre processos; fico curioso sobre qual dessas opções é a mais conveniente e portátil
    Também fico curioso se serviços de gerenciamento de segredos são usados só no trabalho ou também em projetos pessoais

    • Sou um dos autores do CLI Guidelines
      Há um texto um pouco mais aprofundado sobre como lidar com segredos na linha de comando em https://smallstep.com/blog/command-line-secrets/
      Arquivos de credenciais são uma opção simples e portátil
      Arquivos já têm um modelo de permissões e não dependem de serviços externos nem de APIs proprietárias
      Quando um programa aceita um arquivo de credenciais, ele também fica compatível com systemd credentials
      systemd credentials é mais seguro do que arquivos de credenciais não criptografados, é criptografado e também pode ser vinculado ao TPM, mas o software que usa as credenciais não precisa oferecer suporte próprio a TPM
    • Seria bom permitir que o programa especificasse um comando para obter o segredo
      Por exemplo, o mbsync(https://isync.sourceforge.io/mbsync.html) tem cerca de três maneiras de fornecer a senha de autenticação IMAP
      Se você não configurar uma senha, aparece um prompt na execução; também é possível colocar uma senha em texto claro no arquivo de configuração, mas isso é inconveniente para compartilhar
      Também é possível configurar um comando para obter a senha, delegando o processamento a um gerenciador de senhas como o pass(1)(https://www.passwordstore.org/) ou a um prompt gráfico interativo
    • Um serviço de gerenciamento de segredos provavelmente seria o mais conveniente
      Como é bem documentado, dá para configurar facilmente sem precisar criar algo extra por conta própria
      Gerenciadores de segredos como Doppler(https://Doppler.com) ou AWS Secrets Manager(https://aws.amazon.com/secrets-manager/) têm a vantagem de proteger segredos em um local seguro e minimizar sua exposição
      Eles podem até reduzir a exposição para desenvolvedores internos, ajudando a evitar vazamentos de dados que poderiam ter sido facilmente evitados
      Esses vazamentos podem colocar toda a empresa em risco e estão se tornando cada vez mais comuns
  • Posts relacionados: Command Line Interface Guidelines - https://news.ycombinator.com/item?id=38053692 - outubro de 2023, 1 comentário
    Command Line Interface Guidelines - https://news.ycombinator.com/item?id=31651161 - junho de 2022, 1 comentário
    Command Line Interface Guidelines - https://news.ycombinator.com/item?id=25492119 - dezembro de 2020, 5 comentários

  • É muito irritante quando alguns terminais executam automaticamente um comando se houver um caractere de nova linha no fim da string colada da área de transferência
    Pessoalmente, acho que uma interface de linha de comando não deveria fazer isso

    • No Bash, você pode usar bind 'set enable-bracketed-paste on'
    • O iTerm no macOS pede confirmação quando você tenta colar uma string que contém quebras de linha
      Se for incômodo, também dá para desativar
    • O Fish shell, por padrão, apenas insere o texto como esperado e permite editar o comando antes de executá-lo
      Se necessário, várias linhas também são possíveis, e ele só executa quando você pressiona Enter
      O Fish shell muitas vezes tem padrões sensatos, e, fora o prompt, ainda não encontrei nada que eu realmente queira customizar
    • Vale a pena experimentar um gerenciador de área de transferência
      A maioria dos que usei tinha uma opção para remover quebras de linha do conteúdo da área de transferência
    • Se eu sei que haverá no máximo uma quebra de linha, primeiro digito # e depois colo
      Mesmo que a quebra de linha seja interpretada, a linha inteira vira um comentário, então é seguro; antes de executar de fato, basta apagar o # no início da linha