2 ポイント 投稿者 GN⁺ 2024-02-07 | 1件のコメント | WhatsAppで共有
  • 伝統的な UNIX 哲学を現代的に再解釈した CLI プログラム設計原則と具体的ガイドライン を、オープンソース文書として整理したリファレンスであり、コマンドラインツールを作る開発者を主な読者としている
  • CLI は単なるスクリプティングプラットフォームではなく 人間中心のテキスト UI へと進化しており、この変化に合わせて設計原則も更新されるべきである
  • コンポーザビリティ(composability)と人間親和性 は相反するものではなく、標準入出力・パイプ・終了コードなど UNIX の慣習を守れば両立できる
  • ヘルプテキスト、エラーメッセージ、出力形式、インタラクティビティ、設定体系など、実務で見落とされがちな細部まで具体的な推奨事項として提供
  • CLI ツールの 将来互換性とユーザーの信頼 は、インターフェイスの安定性と分析データの透明性で決まり、このガイドはその基準線を示している

哲学 (Philosophy)

人間中心設計

  • 伝統的な UNIX コマンドは主に他のプログラムが使う前提で設計されていたが、現在の CLI は人間が直接使う場合がほとんどであるため、人間優先の設計 が必要
  • かつての CLI は "machine-first" だったが、現在は "human-first" のテキストベース UI へと進化した

組み合わせ可能な小さな部品

  • UNIX 哲学の核心は 小さく単純なプログラムを組み合わせてより大きなシステムを構成すること であり、今でも有効
  • 標準の stdin/stdout/stderr、シグナル、終了コードがプログラム間の接続を保証し、JSON はより構造化されたデータ交換を支援する
  • ソフトウェアは必ずより大きなシステムの部品になるため、うまく機能する部品になれるかどうかは設計段階で決まる

一貫性

  • ターミナル利用者は既存の慣習に手が慣れているため、CLI は 既存のパターンに従うこと が推奨される
  • ただし、一貫性が使いやすさを損なう場合は、慎重に慣例を破ることもできる

適切な情報量

  • コマンドが数分間まったく出力せずに待機すれば「少なすぎる」情報であり、大量のデバッグログを吐き出せば「多すぎる」情報である
  • 情報量のバランス は、ソフトウェアがユーザーを支援するうえで絶対的に重要である

発見しやすさ (Ease of Discovery)

  • GUI は画面上に機能をすべて広げて見せるが、CLI は記憶に依存するものだと誤解されがちである
  • 包括的なヘルプテキスト、豊富な例、次のコマンド提案 など GUI の手法を取り入れることで、CLI も学びやすくできる

対話としての CLI

  • CLI の利用は反復的な試行錯誤による 対話構造 を持ち、エラー修正の提案・中間状態の表示・危険な作業前の確認などがこの特性を活かす設計手法である
  • 最悪の相互作用はユーザーを無力に感じさせる敵対的な対話であり、最善は達成感を与える快いやり取りである

堅牢性 (Robustness)

  • ソフトウェアは 実際にも、感覚としても 堅牢であるべきだ
  • 予期しない入力の優雅な処理、冪等性(idempotence)の維持、進行状況の案内、スタックトレースの露出抑制が重要
  • 複雑な特殊ケースを減らして単純さを保てば、堅牢性は高まる

共感 (Empathy)

  • CLI ツールは開発者の創造的な道具であるため、楽しく使えるべきだ
  • ユーザーが 自分の味方だと感じられるように、問題を十分に考え抜いて設計すること

混沌 (Chaos)

  • ターミナルの世界は不一致に満ちているが、その混沌こそが自由な創造の源でもある
  • "標準が生産性やユーザー満足度に明らかに害を及ぼすなら、その標準は捨てよ" — Jef Raskin

ガイドライン — 基本 (The Basics)

  • 引数パースライブラリ を使うこと: 言語別の推奨ライブラリには Go(Cobra, cli)、Python(Click, Typer, Argparse)、Rust(clap)、Node(oclif) などがある
  • 成功時は 終了コード 0、失敗時は 0 以外のコード を返す — スクリプトが成功・失敗を判定する基準
  • 既定の出力は stdout、ログ・エラーなどのメッセージは stderr に送る

ガイドライン — ヘルプ (Help)

  • -h または --help フラグで 詳細なヘルプテキスト を表示し、サブコマンドにも同様に適用
  • 引数なしで実行した場合は 簡潔なヘルプ を表示する (説明、1〜2 個の例、フラグ説明、--help 案内を含む)
    • jq がこれをうまく実装した例として挙げられている
  • --help / -h / help subcommand など さまざまな形式のヘルプ要求 をすべてサポート
  • ヘルプテキスト上部に Web ドキュメントへのリンクとフィードバック経路 を提供
  • まず例を見せること — 複雑な活用例へ段階的につながるストーリー構成が推奨される
  • よく使うフラグとコマンド をヘルプテキスト上部に配置する (git の構成方式を参照)
  • 太字の見出しなどの書式 を活用してスキャンしやすく構成するが、ターミナル非依存の方法を使う
  • ユーザーが誤入力したときは 意図を推測して修正案を提示 できる — ただし自動実行するかどうかは慎重に判断すること
    • 誤入力が単なるタイプミスではなく論理的なミスである可能性があり、自動修正するとその構文を恒久的にサポートしなければならない負担が生じる

ガイドライン — ドキュメント (Documentation)

  • Web ベースのドキュメント を提供する — 検索性とリンク共有のために必須
  • ターミナルベースのドキュメント を提供する — インストール済みバージョンと同期し、オフラインでもアクセスできる
  • man ページ の提供も検討する — ronn のようなツールで生成でき、npm help ls のようにサブコマンド経由でアクセスできるようにするのが望ましい

ガイドライン — 出力 (Output)

  • 人間にとっての読みやすさを最優先 — TTY かどうかで人が読んでいるかを判定
  • テキストストリームは UNIX の普遍的インターフェイスであり、機械可読な出力 もサポートすること
  • 人間向けの出力がパイプ互換性を損なう場合は、--plain フラグで プレーンテキスト出力 を提供
  • --json フラグが渡されたら JSON 形式の出力 をサポート
  • 成功時の出力は簡潔にし、不要なら無出力で — スクリプト用に -q オプションで出力抑制もサポート
  • 状態が変わったときはユーザーに知らせるgit push がリモートブランチ状態を出力するのは良い例
  • git status のように 現在のシステム状態を簡単に確認 でき、次の作業も案内する出力構成にする
  • 色は意図的に使い、パイプ状態・NO_COLORTERM=dumb--no-color などの条件では 色を無効化 することが必須
  • TTY でない環境では アニメーションやスピナーを表示しない (CI ログ汚染防止)
  • 絵文字や記号は明確さを高める場合にのみ使う (yubikey-agent が例として示されている)
  • 開発者にしか分からない情報は既定の出力から除外し、verbose モード でのみ表示
  • stderr をログファイルのように使わないこと — 既定ではログレベルラベル (ERR, WARN) の出力を控える
  • 大量出力では less など ページャの使用 も検討する — TTY 環境でのみ有効化し、less -FIRX オプションが推奨される

ガイドライン — エラー (Errors)

  • 予測可能なエラーは 人間が理解できるメッセージに書き換える (例: "chmod +w file.txt を実行する必要があります")
  • S/N 比 を維持する — 同種のエラーは単一のヘッダーにまとめて出力
  • 重要な情報は 出力の末尾に配置 — 赤いテキストは意図的に、まれに使う
  • 予期しないエラーが発生した場合は、デバッグ情報と バグレポート提出方法の案内 を含める
  • バグレポート URL に情報を 自動入力して提出しやすく 構成する

ガイドライン — 引数とフラグ (Arguments and Flags)

  • 引数(args)は位置ベース、フラグ(flags)は名前ベース — 引数よりフラグを優先 すること
  • すべてのフラグに 完全名のバージョン を提供する (例: -h--help を同時サポート)
  • 単一文字フラグ は頻繁に使うフラグだけに限定
  • 標準がある場合は 標準フラグ名 を使う (-f/--force, -q/--quiet, -v, --json など)
  • 既定値は 大半のユーザーに適した値 に設定する
  • 引数やフラグが渡されない場合は プロンプトで入力を求める、ただし非対話環境ではプロンプトを強制しない
  • 危険な作業の前には 確認を求める — 危険度に応じて y/n 確認、dry-run の提供、または直接テキスト入力を要求する
    • mild(ファイル削除)、moderate(ディレクトリ削除、リモートリソース変更)、severe(サーバー全体削除) に分けて危険度を区別
  • ファイル入出力では - による stdin/stdout の読み書き をサポートする (例: curl ... | tar xvf -)
  • フラグで シークレットを直接受け取らない--password-file フラグや stdin の使用を推奨 (ps 出力やシェル履歴に露出する危険があるため)

ガイドライン — インタラクティビティ (Interactivity)

  • プロンプトやインタラクティブ要素は stdin が TTY のときだけ表示 する
  • --no-input が渡されたらすべてのプロンプトを無効化
  • パスワード入力時は エコーを無効化 (入力内容を画面に表示しない)
  • ユーザーがいつでも 抜け出せるよう明確に案内 し、Ctrl-C は常に機能するように保つ

ガイドライン — サブコマンド (Subcommands)

  • サブコマンド間で フラグ名・出力形式の一貫性 を保つ
  • 複雑なツールでは noun verb または verb noun 形式の 2 段階サブコマンド構造 を使う (例: docker container create)
  • 曖昧または類似した名前のサブコマンド は避ける (例: update と upgrade の同時使用は推奨しない)

ガイドライン — 堅牢性 (Robustness Guidelines)

  • 入力検証 を早い段階で行い、不正なデータは分かりやすいエラーとともに早期終了する
  • 応答性は速度より重要 — 100ms 以内に何かを出力 すること
  • 時間のかかる作業には 進捗バー(progress bar) を提供する — Python(tqdm)、Go(schollz/progressbar)、Node(node-progress) のライブラリが利用できる
  • 並列処理時は出力が 混ざらないように 注意する
  • ネットワークタイムアウトを設定 する — 既定値を含め、永遠に待たないようにする
  • 一時的なエラー発生後の 再試行時に前の状態から再開 できるよう設計する
  • crash-only 設計 — 後始末なしで即時終了できる構造にして冪等性を確保する

ガイドライン — 将来互換性 (Future-proofing)

  • 変更は 後方互換な追加(additive) 方式で維持する
  • 互換性を壊す変更の前には プログラム内で事前警告 を表示する
  • 人間向け出力の変更は一般に許容される — スクリプト用には --plain--json の使用を促す
  • catch-all サブコマンドを禁止 — 後からその名前のサブコマンドを追加できなくなる問題が生じる
  • サブコマンド略称の自動許可を禁止 — 明示的な alias のみ許可し、安定して維持する
  • 「時限爆弾」を避ける — 20 年後でも動作できるよう外部依存を最小化する

ガイドライン — シグナルと制御文字 (Signals)

  • Ctrl-C(INT シグナル)を受けたら 即座に終了 し、クリーンアップ処理にはタイムアウトを設定する
  • クリーンアップ中に Ctrl-C が再入力された場合は強制終了できるよう案内する (Docker Compose の例を参照)
  • プログラムは クリーンアップ処理が完了していない状態で開始される可能性 を前提に設計する

ガイドライン — 設定 (Configuration)

設定の適用優先順位 (高 → 低):

  • フラグ → 現在のシェル環境変数 → プロジェクトレベル設定(.env) → ユーザーレベル設定 → システム全体設定

設定種別ごとの推奨:

  • 呼び出しごとに変わる設定 (デバッグレベル、dry-run): フラグを使う

  • プロジェクト・マシンごとに異なる設定 (パス、色、HTTP プロキシ): フラグ + 環境変数の組み合わせ

  • プロジェクト全体で共有する設定 (Makefile、package.json 型): バージョン管理ファイルを使う

  • XDG Base Directory 仕様 に従う — ~/.config ベースの設定パスを推奨 (yarn、fish、neovim、tmux などが対応)

  • 他プログラムの設定ファイルを自動変更する場合は ユーザーの同意取得 が必須


ガイドライン — 環境変数 (Environment Variables)

  • 環境変数は 実行コンテキストによって変わる動作 に適している
  • 名前には 大文字・数字・アンダースコア のみを使い、数字で始めない
  • 1 行の値 を推奨 — 複数行だと env コマンドとの互換性問題が発生する
  • NO_COLOR, DEBUG, EDITOR, HTTP_PROXY, SHELL, TMPDIR, HOME, PAGER などの 汎用環境変数 を優先的に確認する
  • プロジェクト別 .env ファイルの読み込みサポートを推奨 — ただし .env は正式な設定ファイルの代替ではない
    • .env の限界: バージョン管理に含まれない、履歴がない、文字列の単一型のみ、エンコーディング問題に弱い
  • 環境変数からシークレットを読まない — すべてのプロセスへ伝播し、ログ漏えい、Docker inspect・systemctl show で露出する危険がある
    • シークレットは認証情報ファイル、パイプ、AF_UNIX ソケット、シークレット管理サービスを通じてのみ受け取る

ガイドライン — ネーミング (Naming)

  • 単純で覚えやすい単語 を使う — あまりに一般的すぎると他のコマンドと衝突する危険がある
  • 小文字と必要に応じたダッシュのみ を使う (curl は良い例、DownloadURL は悪い例)
  • 短く保つが、cdlsps のような極端に短い名前は汎用ユーティリティ用に予約されている
  • Docker Compose の前身である plumfigdocker compose への改名事例は、タイピングのしやすさがネーミングの重要な基準であることを示す実例である

ガイドライン — 配布 (Distribution)

  • 可能なら 単一バイナリで配布 する — PyInstaller などを活用
  • 単一バイナリが不可能な場合は プラットフォームネイティブのパッケージインストーラ を使う
  • アンインストール方法をインストール案内の末尾に明記 する

ガイドライン — 分析データ (Analytics)

  • ユーザーの同意なしに使用データやクラッシュデータを送信しない
  • 収集する場合は、収集項目、理由、匿名化方法、保存期間を明確に公開する
  • 既定では opt-in を推奨 — opt-out 方式なら初回起動時または Web サイトで明確に告知する
    • Angular.js(明示的 opt-in)、Homebrew(Google Analytics、FAQ 公開)、Next.js(既定有効の匿名統計) の 3 事例を紹介
  • 分析の代替として Web ドキュメントの計測、ダウンロード数の計測、ユーザーへの直接インタビュー を活用できる

1件のコメント

 
GN⁺ 2024-02-07
Hacker News のコメント
  • 今では多くの人がコマンドラインが何かを知らず、なぜそれを使うべきかにも関心がない。

    • 1980年代も状況は同じだったが、現在はこれまで以上にコマンドラインを知っている人が増えている。CLI(コマンドラインインターフェース)の黄金期と言える。
  • スクリプトではサブコマンドの任意の省略を許可しないこと。たとえば、mycmd install の代わりに mycmd insmycmd i を許可すると、i で始まる新しいコマンドを追加できなくなる。

    • スクリプトでは短い引数の使用を避けるべき。短い引数は人が使う際にタイピングを減らす利便性があるが、スクリプトでは明示的に書くほうがコストが低く、読み書きの比率を考えても望ましい。
  • --dry-run オプションを検討すること。実際の変更を行わずにどんな処理が実行されるかを事前に示す機能は、ツールを学んだり複雑なオプションを正しく設定できているか確認したりするのに非常に有用。

  • stdout が対話型ターミナルでない場合、アニメーションを表示しないこと。これは CI のログ出力で進捗バーがクリスマスツリーのようになるのを防ぐ。

    • stdout では決してアニメーションを表示しないこと。stderr はロギングや情報提供などのためのものであり、stdout は tty かどうかに関係なく有用な出力を提供すべき。
  • 記号や絵文字は、明確さを高める場合にのみ使うこと。

    • 記号や絵文字はターミナルごとに描画が一貫しないことがあり、ユーザーの好みによって評価が分かれることもあるため、きわめて慎重に使うべき。
  • 現在の Unix コマンドラインは、一方では「驚くほど有用」であり、他方では「設計上の欠陥」もある。

    • Unix コマンドラインが有用である理由は、同じ作業を C や Rust で行うのにかかる時間を考えれば分かる。
    • 設計上の欠陥は、コマンドラインインターフェースが同時に人間にも機械にも読める必要がある点に由来する。この問題を解決する定石はない。
  • CLI が非常に大きく、入れ子が必要な場合(例: aws)を除き、ほとんどのアプリはすべてのオプションをヘルプに出力し、ユーザーが less を使って必要な内容を探せるようにするほうを好む。

  • 伝統的に UNIX コマンドは、主に他のプログラムから使われることを前提に書かれていた。

    • 実際には、対話的なログインシェルの中でインタラクティブに使われることを意図していた。出力を生成するプログラムと「静かな」テキストフィルタに分かれ、複雑なプログラムは C で書かれる。
  • 環境変数からパスワードを読み取らないこと。

    • パスワードはクレデンシャルファイル、パイプ、AF_UNIX ソケット、シークレット管理サービス、または他の IPC メカニズムを通じてのみ受け取るべき。
  • CLI ガイドラインについて最も包括的な本は Eric Raymond の著作である。

    • かなり時間は経っているが、clig.dev をざっと見ると、時の経過とともに考え方が大きく変わってきたことが分かる。