Replicate, co-creator of Docker Compose.
@bfirsh
Carl Tashian
Offroad Engineer at Smallstep, first engineer at Zipcar, co-founder Trove.
tashian.com @tashian
Eva Parish
Technical Writer at Squarespace, O’Reilly contributor.
evaparish.com @evpari
Design by Mark Hurrell. Thanks to Andreas Jansson for early contributions, and Andrew Reitz, Ashley Williams, Brendan Falk, Chester Ramey, Dj Walker-Morgan, Jacob Maine, James Coglan, Michael Dwan, and Steve Klabnik for reviewing drafts.
このガイドやCLIデザインについて議論したいなら、Discordに参加してください。
このページは Command Line Interface Guidelines の日本語訳です。 オリジナルのライセンスは CC BY-SA 4.0 であり、この翻訳もライセンスを継承します。 翻訳の修正や改善にご協力いただける方は、GitHubリポジトリにプルリクエストを送ってください。
1980年代において、パーソナルコンピュータを使って何かをしたい時は、C:\>
や~$
という表示に対して何を入力すればいいのかを知っている必要がありました。
ヘルプは分厚い螺旋綴じのマニュアルでした。
エラーメッセージは不明瞭なものでした。
Stack Overflow のようなお助けサイトはありませんでした。
幸運にも十分なインターネット接続があれば、Usenetから助けを得ることができました。 Usenet はインターネット初期に存在したコミュニティで、あなたと同様にイライラしている人々で溢れていました。
Usenet によって問題を解決する助けを得るか、少なくとも多少のモラルサポートと仲間意識を得ることができました。
それから40年後、コンピュータはより多くの人の手に行き渡りましたが、多くの場合低レベルエンドユーザーコントロールが犠牲になりました。 多くのデバイスではコマンドラインアクセスは全く存在せず、それはコマンドラインアクセスが企業の興味分野である囲い込みとアプリストアに反することが理由のひとつにありました。
今では多くの人がコマンドラインが何かを知らないし、なぜわざわざそんなものを使いたがるかも知りません。 コンピューティングのパイオニアである Alan Kay は2017年のインタビューでこのように言っています。 「人々はコンピューティングとは何かを理解していないので、それがiPhoneの中にあると思っており、その幻想は『Guitar Hero』が本物のギターだと思うのと同じくらいよくない幻想です」
Kayの言う「本物のギター」は正確にはCLIのことではありません。 彼はテキストファイルにソフトウェアを記述することを超えた、CLIの力を受けたコンピュータプログラミングについて話しています。 Kayの弟子たちの間には、我々が何十年も囚われているテキストベースの局所最適を抜け出すべきだという信念があります。
コンピュータプログラミングが今とは非常に異なる方法で行われる未来を想像するのはとても楽しいことです。 今日でもスプレッドシートは圧倒的人気プログラミング言語であり、才能あるプログラマの強い需要の一部を置き換えるためのノーコードムーブメントが急速に進行中です。
数十年前からのガタついた制約と不可解な癖にもかかわらず、コマンドラインはいまだコンピュータの最も多用途な一角を占めています。 コマンドラインはカーテンを開け、本当は何が起きているかを知り、GUIには到達できない洗練度と深さでマシンとクリエイティブに交流することを可能にします。 コマンドラインは多くのラップトップで利用可能であり、多くの人はそこからコマンドラインについて学ぶことができます。 コマンドラインはインタラクティブに利用することもできるし、自動化もできます。 そして、システムの他の部分と比較して頻繁に変化しません。 その安定性には創造的価値があります。
したがって、それがまだ我々の手の中にあるうちに、利用率とアクセシビリティの最大化をすべきです。
コンピュータプログラミングの方法は、その初期の頃と比較して非常に多くのことが変化しています。
過去においてコマンドラインはマシンファーストでした。それはスクリプティングプラットフォーム上のREPLに毛が生えた程度のものでした。
しかし汎用インタプリタ言語の繁栄に伴って、シェルスクリプトの役割は小さくなりました。
今日のコマンドラインはヒューマンファーストです。つまり、あらゆるツール、システム、プラットフォームへのアクセスを提供するテキストベースUIとなりました。
過去にはエディタはターミナルの中にありました。今ではターミナルがエディタのいち機能となっています。
git
のようなマルチツールコマンドも爆発的に増加しています。
コマンド内コマンドや高レベルコマンドは、原始的な関数というよりワークフロー全体として振る舞っています。
伝統的UNIX哲学をインスパイアし、CLI環境をより楽しくアクセシブルなものにするよう奨励するという関心によって、我々はコマンドラインプログラムを構築する際のベストプラクティスとデザイン原則を再訪することにしました。
コマンドライン万歳!
このドキュメントは高レベルのデザイン哲学と、堅固なガイドラインの両方をカバーします。 実践者のための哲学とは哲学しすぎないことであるため、ガイドラインの比重が大きくなっています。 我々は例とともに学ぶことの力を信じているため、例をたくさん入れました。
このガイドは emacs や vim のようなフルスクリーンターミナルプログラムをカバーしません。 フルスクリーンプログラムはニッチなプロジェクトであり、それをデザインできる立場にある人は限られています。
このガイドはプログラミング言語やツールについても基本的に触れません。
このガイドはどのような人のためのものでしょうか?
良いCLIデザインの基礎的原則について考えます。
伝統的に、UNIXコマンドは他のプログラムで最も利用されることを仮定して書かれています。 これらはグラフィカルアプリケーションよりもプログラミング言語の関数に近いものです。
今日、多くのCLIプログラムは人間に最も多く (または人間のみが) 利用されるのにもかかわらず、その多くのインタラクションデザインはそのような過去を引きずっています。 今こそ過去を捨て去る時です。コマンドラインが第一に人間に使われるならば、人間第一にデザインするべきです。
オリジナルのUNIX哲学の核となる教義は小さく、シンプルでクリーンなインターフェースを持つプログラムは組み合わせてより大きなシステムにできるというものです。 プログラムに機能をたくさん付けるのではなく、必要に応じて再構成できるように十分モジュラーにします。
かつて、パイプとシェルスクリプトはプログラムをまとめるプロセスにおいて重要な役割を持っていました。 その役割は汎用インタプリタ言語の成長に伴い減少したかもしれませんが、確かになくなってはいません。 つまり、より大規模なオートメーション、CI/CD、オーケストレーションと構成管理が繁栄しているのです。 プログラムを構成可能にすることはいまだ重要なままです。
幸いにも、UNIX環境の長年続く慣習はそのような目的でデザインされており、今でも我々の助けになります。 標準入力/出力/エラー、シグナル、終了コードやその他メカニズムによって異なるプログラムが適切に噛み合うことを可能にします。 プレーン行ベーステキストはコマンド間を接続しやすくします。 より最近の発明であるJSONは、必要なときに構造化を支援し、コマンドラインツールとウェブをより簡単に統合できるようにします。
あなたが作成しているソフトウェアが何であれ、人々があなたの想像を超えた使い方をするのは間違いありません。 あなたのソフトウェアはより大きなシステムの一部となります。あなたができることはより良い部品として振る舞うようにすることだけです。
最も重要なこととして、構成可能にデザインすることはヒューマンファーストにデザインすることの反対にはありません。 このドキュメントのアドバイスはその両方を実現します。
ターミナルの慣習はあなたの指に刻まれています。 コマンドラインの構文、フラグ、環境変数その他について学ぶコストを支払う必要はありましたが、長期的な効率の観点でペイします……プログラムが一貫している限り。
可能ならば、CLIは既に存在するパターンに追従すべきです。 それこそがCLIを直感的かつ予測可能にします。効率的な利用を可能にもします。
とは言うものの、使用法の簡単さと一貫性が衝突することはあります。 たとえば、多くの古くからあるUNIXコマンドはデフォルトでは多くの情報を出力しませんが、これはコマンドラインに親しみのない人にとって混乱や不安を招きます。
慣習に従うことがプログラムのユーザビリティを損なう場合、その慣習を打ち破るときかもしれません。ただしそのような決定をするときは慎重になってください。
ターミナルは純粋な情報の世界です。 情報はインターフェースであるということができます。そして、他のインターフェースと同様に、それはしばしば過剰になったり過少になったりします。
コマンドが数分間ハングして壊れているのでないかとユーザが心配し始めるなら情報が過少です。 何ページにもわたってデバッグ出力が行われ、そのような情報の海で何が本当に必要な情報かわからなくなるなら情報が過剰です。 どちらにしても最終的な結果は同じです。つまり、明確性が失われ、ユーザを混乱させたり苛つかせたりします。
このバランスを正しく保つのは難しいですが、ソフトウェアがユーザに仕え、エンパワーするためには重要なことです。
機能の見つけやすさという点ではGUIの方に軍配が上がります。 GUIアプリケーションの機能はスクリーンの上にあるため、事前に学ぶ必要なく機能を探すことが可能であり、知らない機能を見つけることすら可能です。
コマンドラインインターフェースはその対極にあると思われます。つまり、すべてを覚えておく必要があるということです。 1987年に公開されたオリジナルのマッキントッシュ ヒューマンインターフェースガイドラインでは「見て、指す (覚えてタイプするのではなく)」ことが推奨されています。 まるでそれらのどちらかしか選択できないかのような書かれ方です。
それらは互いに排他的である必要はありません。 コマンドラインの効率性はコマンドを覚えることから来ていますが、コマンドが学び覚える手助けをできない理由はありません。
発見可能なCLIは包括的なヘルプテキストを持ち、多くの例を提供し、次に何をすべきかを提案し、エラーの際には何をすべきかを提案します。 GUIからは、CLIを学習と使用が簡単で、パワーユーザにも役に立つものにするために多くのアイデアを盗むことができます。
引用: The Design of Everyday Things (Don Norman), Macintosh Human Interface Guidelines
GUIデザイン、特にその初期のものは、メタファーを多用していました。デスクトップ、ファイル、フォルダ、ゴミ箱などがその例です。 この時はまだコンピュータは自分自身の正当性を示さなければならなかったので、これは妥当なことでした。 メタファーの実装しやすさはCLIに対するGUIの大きな利点です。 しかし皮肉にも、CLIは最初から偶然にメタファーを含んでいました。つまり、会話のメタファーです。
最も単純なコマンドでも、プログラムの実行には通常は複数回の実行が必要になります。 これは、最初から正しく物事を行うのが通常は難しいからです。ユーザはコマンドを入力し、エラーを受け取り、コマンドを変更し、また違うエラーを受け取り、うまくいくまでこれを繰り返します。 失敗の繰り返しから学ぶこのモードは、ユーザとプログラムの会話のようなものです。
トライアル・アンド・エラーは会話式インタラクションの唯一の類型ではありません。 他にはこのようなものがあります。
git add
の後にgit commit
)。cd
やls
でディレクトリ構造について知る、もしくはgit log
とgit show
でファイルの履歴を探索する。コマンドラインインタラクションの会話的性質を認識することで、そのデザインに関する技法が得られます。 入力が不正の時は可能な修正を提案できるし、マルチステップのプロセスを行っている時は中間状態を明確にしたり、何か恐ろしいことをする前にはすべてが正しいことを確認することができます。
ユーザーはあなたが意図する、しないにかかわらずあなたの作ったソフトウェアと対話します。 最悪の場合、それは敵対的な会話となり、バカにされたように感じたり憤慨したりします。 最良の場合、新たに得た知識と達成感によって物事を加速させる楽しい交流になります。
参考文献: The Anti-Mac User Interface (Don Gentner and Jakob Nielsen)
堅牢性は主観的性質であり、客観的性質でもあります。 もちろん、ソフトウェアは堅牢である必要があります。予期しない入力は適切に処理されるべきである、オペレーションは可能なら冪等であるべきである、などがそれに当たります。 それに加えて、ソフトウェアは堅牢であるように感じられる必要があります。
堅牢であるように感じられることとは、ソフトウェアが壊れないことではありません。 ヤワなプラスチックの「ソフトスイッチ」のようではなく、大きな機械仕掛けのように、素早く、敏感に反応するように感じさせるということです。
客観的堅牢性のためには、細部に注意をはらい、何がそれを損なうかをよく考える必要があります。 ユーザが何が起きているかを知っている状態を維持することや、一般的なエラーに対しては恐ろしいスタックトレースではなくその意味の説明を行うことなど、細かい注意点がたくさんあります。
一般的なルールとして、堅牢性は物事を単純にすることにより生まれます。 特殊ケースが多く複雑なコードは不安定な印象を与えます。
コマンドラインツールはプログラマのクリエイティブなツールキットであるため、使うのが楽しいものでなければなりません。 これはツールをビデオゲームのようにしたり、絵文字を多用したりするということではありません (絵文字を使うこと自体には本質的な問題はありません😉)。 ユーザにこちらが仲間であり、ユーザの成功を望んでおり、ユーザの問題とその解決法についてよく考えていると感じてもらうことです。
彼らにそう思ってもらうためにできることのリストは存在しませんが、我々のアドバイスに従うことで少しでもその方向へ進むことを願っています。 ユーザを喜ばせることは常に期待を超え続けることであり、それはエンパシーから始まります。
ターミナルの世界はめちゃくちゃです。 非一貫性はいたるところに存在し、我々の足を引っ張り、自分自身を後から批判します。
しかしこのカオスが力の源であったことは否定できません。 ターミナル環境、一般にはUNIX系コンピューティング環境のような環境は、その上に構築するものに関しての制約が非常に少ないです。 この空間では、あらゆる発明方法が花開きました。
皮肉なことに、このドキュメントは既存のパターンに従うよう要請しており、それと並行して数十年に渡るコマンドラインの伝統に反するようアドバイスもしています。 我々もルールを破る罪を犯しているわけです。
あなたにもルールを破らなければならない時が来るかもしれません。 その時は意図と目的を明確にするようにしてください。
“プロダクティビティやユーザの満足度に明らかに害が及ぶなら、標準を放棄すべきである” — Jef Raskin, The Humane Interface
これはコマンドラインプログラムをより良くするためにできることの集まりです。
最初のセクションはあなたが従うべき必須事項です。 これを間違うと、あなたのプログラムは使いづらくなるか、悪しきCLI市民になります。
残りはすると良いことです。 これらのことを行う時間と気力があるなら、あなたのプログラムは平均的プログラムより良いものになります。
もしプログラムのデザインについて深く考えたくないならば、考えなくて良いというのがこのガイドラインのアイデアになります。その場合はただこれらのルールに従えば、プログラムは良いものになるでしょう。 一方で、デザインについて考えていてこれらルールがあなたのプログラムにおいては正しくないと考えるなら、それも良いでしょう。 (あなたのプログラムがルールに従っていないからといってそれをリジェクトする中央当局は存在しません。)
そして、これらのルールは石に刻まれた決定事項ではありません。 一般的ルールに反対するいい理由があるなら、我々は変更を受け付けています。
ここではあなたが従うべきいくつかのルールを取り扱います。 これを間違えると、あなたのプログラムは非常に使いづらいものになるか、速やかに壊れます。
あなたが利用可能なコマンドライン引数パースライブラリを使ってください。 言語組み込みのものか、サードパーティーに良いものがあればそれを使います。 これにより引数のハンドリング、フラグのパース、ヘルプテキスト、またスペリングの提案までもを賢明な方法で行えるようになります。
こちらが我々のお気に入りです。
成功時は終了コードゼロ、失敗時には非ゼロを返してください。 終了コードはスクリプトがプログラムの成功または失敗を判定する方法であり、正しく報告する必要があります。 非ゼロの終了コードは最も重要な失敗モードに割り当ててください。
出力はstdout
に行ってください。
コマンドの主要な出力はstdout
に出力されるべきです。
機械可読ななんらかの出力もstdout
に行われるべきです。パイプによってデフォルトで送られます。
メッセージはstderr
に出力してください。
ログメッセージ、エラー、その他のものはすべてstderr
に送ります。
これによりコマンドがパイプ接続されている時、メッセージがユーザに表示され、次のコマンドには送られないようになります。
なにもオプションを指定しなかった時、-h
や--help
フラグが指定された時にはヘルプテキストを表示してください。
デフォルトでは簡潔なヘルプテキストを出力するようにしてください。
可能なら、myapp
やmyapp subcommand
が実行された時はデフォルトでヘルプテキストを表示するようにしてください。
プログラムが非常にシンプルで明らかなデフォルトの動作があるとき (例: ls
) や、プログラムが入力をインタラクティブに受け取る時 (例: cat
) はその限りではありません。
簡潔なヘルプテキストは以下のもののみを含むべきです。
--help
フラグでより多くの情報が得られるという指示。jq
はこれの良い例です。
jq
と入力した時、jq
は入門的説明と例を表示し、jq --help
により全フラグのリストを得るよう促しています。
$ jq
jq - commandline JSON processor [version 1.6]
Usage: jq [options] <jq filter> [file...]
jq [options] --args <jq filter> [strings...]
jq [options] --jsonargs <jq filter> [JSON_TEXTS...]
jq is a tool for processing JSON inputs, applying the given filter to
its JSON text inputs and producing the filter's results as JSON on
standard output.
The simplest filter is ., which copies jq's input to its output
unmodified (except for formatting, but note that IEEE754 is used
for number representation internally, with all that that implies).
For more advanced filters see the jq(1) manpage ("man jq")
and/or https://stedolan.github.io/jq
Example:
$ echo '{"foo": 0}' | jq .
{
"foo": 0
}
For a listing of options, use jq --help.
-h
や--help
が渡された時は完全なヘルプを表示してください。
以下のすべてがヘルプを出力すべきです。
$ myapp
$ myapp --help
$ myapp -h
渡されている他のフラグは無視してください。末尾に-h
を付ければヘルプが表示されるようにすべきです。
-h
をオーバーロードしないでください。
あなたのプログラムがgit
ライクなものなら、以下もヘルプを表示すべきです。
$ myapp help
$ myapp help subcommand
$ myapp subcommand --help
$ myapp subcommand -h
フィードバックとイシューのためのサポートへのパスを提供してください。 ウェブサイトやGitHubへのリンクを最上位のヘルプテキストに配置するのが一般的です。
ヘルプテキストの中にドキュメントのWebバージョンへのリンクを入れてください。 サブコマンドに対する特定のページやアンカーがある場合は、そこへの直接リンクを入れてください。 これはWebにより詳細なドキュメントがある場合、もしくは振る舞いなどについての説明を行う文献がある場合に便利です。
例を使って導いてください。 ユーザーは他の形式のドキュメントより例を好むため、例をヘルプページの最初に、特に一般的で複雑な使用例を出してください。 それがコマンドの動作の説明に役立ち、長過ぎなければ、実際の出力も載せてください。
複数の例を使ってストーリーを記述し、複雑な使い方についてあなたの方法を見せることができます。
サンプルがたくさんある場合は、どこか他のところに移動させてください。 たとえばチートシートコマンドやWebページが移動先です。 網羅的で発展的な例は便利ですが、ヘルプテキストが長くなるのは好ましくありません。
他のツールとの統合等、より複雑なユースケースに対しては、完全なチュートリアルを書く方が適切でしょう。
ヘルプテキストの最初には最も一般的なフラグやコマンドを表示してください。 大量のフラグがあってもいいですが、本当によく使う物があるならば、それを最初に表示してください。 たとえば、GitコマンドはGit操作の開始のためのコマンドと、最もよく使われるサブコマンドを最初に表示します。
$ git
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]
These are common Git commands used in various situations:
start a working area (see also: git help tutorial)
clone Clone a repository into a new directory
init Create an empty Git repository or reinitialize an existing one
work on the current change (see also: git help everyday)
add Add file contents to the index
mv Move or rename a file, a directory, or a symlink
reset Reset current HEAD to the specified state
rm Remove files from the working tree and from the index
examine the history and state (see also: git help revisions)
bisect Use binary search to find the commit that introduced a bug
grep Print lines matching a pattern
log Show commit logs
show Show various types of objects
status Show the working tree status
…
ヘルプテキストには書式を持たせてください。 表題を太字にするととても読みやすくなります。 ただし、ユーザにエスケープ文字の壁を見せることがないように、ターミナルに依存しない方法で行ってください。
$ heroku apps --help
list your apps
USAGE
$ heroku apps
OPTIONS
-A, --all include apps in all teams
-p, --personal list apps in personal account when a default team is set
-s, --space=space filter by space
-t, --team=team team to use
--json output in json format
EXAMPLES
$ heroku apps
=== My Apps
example
example2
=== Collaborated Apps
theirapp other@owner.name
COMMANDS
apps:create creates a new app
apps:destroy permanently destroy an app
apps:errors view app errors
apps:favorites list favorited apps
apps:info show detailed app information
apps:join add yourself to a team app
apps:leave remove yourself from a team app
apps:lock prevent team members from joining an app
apps:open open the app in a web browser
apps:rename rename an app
apps:stacks show the list of available stacks
apps:transfer transfer applications to another user or team
apps:unlock unlock an app so any team member can join
Note: heroku apps --help
がページャとパイプ接続された場合、コマンドはエスケープ文字を出力しなくなります。
ユーザが何か間違ったことをして、なにをしようとしたか推測できる時は、それを提案してください。
たとえば、brew update jq
はbrew upgrade jq
を実行するよう伝えます。
提案したコマンドを実行したいかどうか聞くことができますが、強制はしないようにしてください。 たとえば以下のような状況を考えます。
$ heroku pss
› Warning: pss is not a heroku command.
Did you mean ps? [y/n]:
正しい構文を提案するのではなく、まるで最初から正しく入力がなされたかのように単にそれを実行したいという誘惑に駆られるかもしれません。
1つ目に、不正な入力は必ずしも単なるTypoを意味しません。ユーザは論理的間違いや、シェル変数の間違った使い方をしていることがよくあります。 意図を仮定するのは危険であり、その操作が状態変化を招くものならなおさら危険です。
2つ目に、ユーザが入力したものを変えてしまうと、ユーザが正しい構文を覚えられないおそれがあります。 じっさい、ユーザが入力したものを正しい入力として補正するように決めると、無制限にそれのサポートをすることになります。 そのような決定をする時は意図を持って行い、両方の構文をドキュメントに書いてください。
参考文献: “Do What I Mean”
コマンドがパイプ入力を受け取ることを想定しており、stdin
がインタラクティブなターミナルならば、ヘルプを出力して直ちに終了してください。
これをしないとcat
のようにハングすることになります。
他の手段として、ログメッセージをstderr
に表示することも可能です。
ヘルプテキストの目的は、ツールが何であるか、どのようなオプションが利用可能か、最も一般的なタスクはどのように行われるかについての簡単で即座の理解です。 ドキュメントは、その一方で、完全な詳細に立ち入る場です。 これによって人々はツールが何のための物であるか、何のための物でないか、どのように動作するか、やりたいこと全てはどのように行えばいいかを理解します。
ウェブベースのドキュメントを提供してください。 人々はオンラインでツールのドキュメントを検索し、その特定の部分を他の人にリンクできる必要があります。 Webは利用可能な中で最もインクルーシブなドキュメントフォーマットです。
ターミナルベースのドキュメントを提供してください。 ターミナル内ドキュメントはいくつかの良い性質を持ちます。素早くアクセス可能で、インストールされているバージョンと同期しており、インターネット接続がなくても動作します。
man page を提供することを検討してください。
Unixオリジナルのドキュメントシステムである man page は今日でも使われており、多くのユーザがツールの学習の第一ステップとしてman mycmd
を確認します。
生成を簡単にするために、 ronn のようなツールが利用できます (このツールはWebドキュメントも生成します)。
しかしながら、全員がman
のことを知っているわけではないし、man
はすべてのプラットフォームで動作するわけでもないため、ターミナルドキュメントはツールそれ自体からもアクセス可能にするべきです。
たとえば、git
やnpm
は man page をhelp
サブコマンドからアクセスできるようにしており、npm help ls
はman npm-ls
と等価です。
NPM-LS(1) NPM-LS(1)
NAME
npm-ls - List installed packages
SYNOPSIS
npm ls [[<@scope>/]<pkg> ...]
aliases: list, la, ll
DESCRIPTION
This command will print to stdout all the versions of packages that are
installed, as well as their dependencies, in a tree-structure.
...
対人間可読性は最も重要です。
人間が第一、機械はその後です。
出力ストリーム (stdout
やstderr
) は人間に読まれるか、そうでなければTTYではないというのは最もシンプルで素直なヒューリスティックです。
どの言語にもそのようなことを行うためのユーティリティやライブラリがあります (例: Python、 Node、 Go)
TTYが何かについては参考文献を読んでください。
ユーザビリティに影響しない範囲で機械可読な出力にしてください。
テキストストリームはUNIXのユニバーサルなインターフェースです。
プログラムは一般に複数行のテキストを出力し、複数行のテキストを入力で受け取るため、複数のプログラムを組み合わせて使うことができます。
このテキストストリームは通常スクリプトを書くために使われますが、プログラムを使う人間のユーザビリティにも役立ちます。
たとえば、grep
に出力を流して期待する出力を得ることがあります。
“すべてのプログラムの出力は、未知のものも含めた他のプログラムの入力となることを期待する。” — Doug McIlroy
人間可読な出力が機械可読な出力を壊す場合、--plain
で出力を平易な、grep
やawk
のようなツールと統合できる表形式のテキストフォーマットにするようにしてください。
場合によっては、人間にとって読みやすいのとは異なる方法で出力を行う必要があるかもしれません。
たとえば行ベースの表を表示するとき、セルを複数行にまたがって分割し、スクリーンの幅に合わせてより多くの情報を表示することができます。
これは1行に1データが有るという期待する振る舞いを壊すため、スクリプトがそのような操作を無効化して1行1レコードにできるように--plain
フラグを提供すべきです。
--json
が渡された時はフォーマットされたJSONを表示してください。
JSONはプレーンテキストよりも構造的であり、より複雑なデータ構造の操作が可能になります。
jq
はコマンドラインでJSONを取り扱うための一般的ツールであり、今ではJSONを出力・操作する
ツールのエコシステム
ができています。
JSONはWebでも広く使われており、JSONをプログラムの入出力として使うことで、curl
を使ったWebサービスとのパイプ接続が可能になります。
成功時には出力を行ってください。ただし簡潔に。
伝統的に、UNIXコマンドはなにか間違ったことが起きていない限りユーザになんの出力も表示しません。
これはコマンドをスクリプトで使う時は理にかなっていますが、人間が使った時にはハングしていたり壊れたりしているように見えます。
たとえば、cp
は操作に長い時間がかかっているとしても何も出力しません。
なにも出力しないことが最良のデフォルト動作であることはまれですが、ちょっと少なすぎるくらいが最良であることが多いです。
例えば、出力が不要な時 (シェルスクリプトで使う時など) は、stderr
を/dev/null
に流すようなゴチャついたコードを避けるため、 不必要な出力を抑制する-q
オプションを提供できます。
状態を変更する時は、ユーザに伝えてください。 コマンドがシステムの状態を変更する時は、それによって何が発生するか説明することで、ユーザが頭の中でシステムの状態をモデル化できるようになって有用です。ユーザが要求したことと結果が直接対応しない時は特にそうです。
たとえば、git push
は何をしているかと、リモートブランチの新しい状態がどうなったかを伝えます。
$ git push
Enumerating objects: 18, done.
Counting objects: 100% (18/18), done.
Delta compression using up to 8 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (10/10), 2.09 KiB | 2.09 MiB/s, done.
Total 10 (delta 8), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (8/8), completed with 8 local objects.
To github.com:replicate/replicate.git
+ 6c22c90...a2a5217 bfirsh/fix-delete -> bfirsh/fix-delete
システムの現在の状態が簡単にわかるようにしてください。 プログラムが複雑な状態を持ち、それがファイルシステム上で直ちに見られるものでない場合は、それを簡単に見られるようにしてください。
たとえば、git status
はGitリポジトリの現在の状態についての可能な限り十分な情報と、状態を変更する方法についてのヒントを伝えます。
$ git status
On branch bfirsh/fix-delete
Your branch is up to date with 'origin/bfirsh/fix-delete'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: cli/pkg/cli/rm.go
no changes added to commit (use "git add" and/or "git commit -a")
ユーザが実行すべきコマンドを提案してください。
複数のコマンドからワークフローが構成される場合、ユーザに次に何ができるかを提案し、プログラムの使い方を学び新しい機能を発見する手助けをしてください。
たとえば上述のgit status
コマンドの出力では、今見ている状態を変更するために実行できるコマンドを提案しています。
プログラムの内部を超えたアクションは通常明示的であるべきです。 これはたとえば以下のようなことを指します。
情報密度を高めてください。アスキーアートを使って!
たとえば、ls
はパーミッションを見やすく表示します。
最初に見たときには、殆どの情報を無視することができます。
その後、仕組みを理解していくにつれて、より多くのパターンに目を向けられるようになります。
-rw-r--r-- 1 root root 68 Aug 22 23:20 resolv.conf
lrwxrwxrwx 1 root root 13 Mar 14 20:24 rmt -> /usr/sbin/rmt
drwxr-xr-x 4 root root 4.0K Jul 20 14:51 security
drwxr-xr-x 2 root root 4.0K Jul 20 14:53 selinux
-rw-r----- 1 root shadow 501 Jul 20 14:44 shadow
-rw-r--r-- 1 root root 116 Jul 20 14:43 shells
drwxr-xr-x 2 root root 4.0K Jul 20 14:57 skel
-rw-r--r-- 1 root root 0 Jul 20 14:43 subgid
-rw-r--r-- 1 root root 0 Jul 20 14:43 subuid
意図を持って色を使ってください。 たとえば、ユーザに気づいてもらえるようにいくつかのテキストをハイライトしたり、エラーを示すために赤色を使うなどです。 使いすぎないようにしてください。全部が違う色をしていたら、色は意味をなさなくなり、ただ読みづらいだけになります。
プログラムがターミナルで動作していない、もしくはユーザが無効化するよう要請した時は、色を無効化してください。 以下のようなもので色を無効化できるべきです。
stdout
やstderr
がインタラクティブなターミナル (TTY) ではない。
この2つは個別にチェックするのが最良です。stdout
を他のプログラムにパイプ接続しているときでも、stderr
に色がついていると便利です。NO_COLOR
環境変数が設定されている。TERM
環境変数の値がdumb
である。--no-color
オプションを渡した。MYAPP_NO_COLOR
環境変数を追加したくなるかもしれません。参考文献: no-color.org, 12 Factor CLI Apps
stdout
がインタラクティブなターミナルでない場合、アニメーションを表示しないでください。
これによって、CIのログ出力でプログレスバーをクリスマスツリーにすることがなくなります。
それで物事が明確になるなら、記号や絵文字を使ってください。 複数のものを明確にし、ユーザの注目を引きたいときやちょっとした個性を加えたい時には、画像は言葉より優れています。 しかし気をつけてください。それは簡単にやり過ぎになり、プログラムがしっちゃかめっちゃかに見えたり、おもちゃのように感じられたりすることになります。
たとえば、yubikey-agentは出力がテキストの壁にならないように、絵文字を使って出力に構造を与え、情報の重要な箇所には ❌ で注意を引き付けています。
$ yubikey-agent -setup
🔐 The PIN is up to 8 numbers, letters, or symbols. Not just numbers!
❌ The key will be lost if the PIN and PUK are locked after 3 incorrect tries.
Choose a new PIN/PUK:
Repeat the PIN/PUK:
🧪 Retriculating splines …
✅ Done! This YubiKey is secured and ready to go.
🤏 When the YubiKey blinks, touch it to authorize the login.
🔑 Here's your new shiny SSH public key:
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCEJ/
UwlHnUFXgENO3ifPZd8zoSKMxESxxot4tMgvfXjmRp5G3BGrAnonncE7Aj11pn3SSYgEcrrn2sMyLGpVS0=
💭 Remember: everything breaks, have a backup plan for when this YubiKey does.
デフォルトでは、そのソフトウェアの作者しか理解できないような情報は出力しないでください。 あなた (開発者) がソフトウェアの動作を理解する助けになるだけのものが出力に含まれているなら、それはほぼ確実に一般ユーザにデフォルトで表示すべきものではありません。verboseモードでのみ表示すべきです。
外部の人間や、プロジェクトに新しく入った人からユーザビリティについてのフィードバックを受け取ってください。 コードの近くにいると見えない重要な問題に気づく助けになります。
少なくともデフォルトではstderr
をログファイルのように扱わないでください。
verboseモードでない時にログレベルのラベル (ERR
、WARN
等) や関連性の薄い情報を出力しないでください。
大量のテキストを出力する時はページャ (例: less
) を使ってください。
たとえば、git diff
はデフォルトでこれを行います。
ページャの使用は問題を起こしやすいので、ユーザの体験を損なわないように実装には気をつけてください。
stdin
やstdout
がインタラクティブなターミナルでないならページャを使うべきではありません。
less
に対する優れたオプション指定はless -FIRX
です。
これによってコンテンツがスクリーンに収まる場合は動作せず、検索の際に大文字・小文字を無視し、色と書式を有効化し、less
が終了した時に画面にコンテンツを残すようになります。
使用している言語にless
にパイプ接続するよりもロバストなライブラリがあるかもしれません。
例えば、Pythonには pypager があります。
ドキュメントを見に行く理由のなかで特に多いのは、エラーの修正です。 エラーをドキュメントにできれば、ユーザの時間を節約できます。
エラーを補足し、人間向けに書き直してください。 エラーが発生することが予期される場合、それを補足して役に立つようなエラーメッセージに書き直してください。 ユーザがなにか間違ったことをして、プログラムがそれを正しい方向に導くという会話のようなものだと考えてください。 たとえば、「file.txt に書き込むことができませんでした。‘chmod +w file.txt’ を実行してファイルを書き込み可能にする必要があるかもしれません。」のようにします。
S/N比は重要です。 関係ないものを多く出力すると、ユーザが何を間違えたのか気付くことができなくなります。 プログラムが同種のエラーを複数生成する場合、それを大量に出力するのではなく1行の説明的なヘッダーでグルーピングすることを検討してください。
ユーザが最初に観るところについて考えてください。 最も重要な情報は出力の最後に配置してください。 赤文字は目を引くため、意図を持って控えめに使用してください。
予期せぬ、あるいは説明不能なエラーが発生した場合、デバッグ情報とトレースバック情報を提供し、そのバグを報告する方法を指示してください。 とはいえ、S/N比のことは忘れないでください。理解できない情報でユーザを圧倒したくはありません。 デバッグログをターミナルに出力する代わりにログファイルに書き込むことを検討してください。
バグレポートの報告を簡単にしてください。 URLを提供し、できるだけ多くの情報を事前に入力しておくことができます。
参考文献: Google: Writing Helpful Error Messages, Nielsen Norman Group: Error-Message Guidelines
用語説明:
cp
に渡すファイルパスは引数です。
引数の順番は重要であることが多いです。cp foo bar
はcp bar foo
とは違います。-r
) か2つのハイフンと複数文字からなる名前 (--recursive
) で記述されます。
フラグの順番は一般的にはプログラムの意味に影響しません。引数よりフラグを使ってください。 入力文字数が少し多くなりますが、何をしているかがより明確になります。 将来に入力の受け取り方を変える際も簡単になります。 引数を使うと、既存の振る舞いを破壊したり曖昧さを導入したりすることなしに新しい入力を追加することが不可能になることがあります。
引用: 12 Factor CLI Apps.
全てのフラグに完全なバージョンを用意してください。
たとえば、-h
と--help
の両方があるべきです。
フルバージョンがあることで、スクリプトを冗長かつ説明的に書きたいときに便利になり、フラグの意味をいちいち調べに行くことがなくなります。
引用: GNU Coding Standards.
1文字フラグは特に一般的なフラグにのみ使い、 特にサブコマンドを使う時はトップレベルのものに使うようにしてください。 短いフラグの名前空間を「汚染」しないようにすることで、将来追加されるフラグに複雑な文字や大文字を使わなくて済むようになります。
複数の引数は複数のファイルに対する単純なアクションに対しては優れています。
たとえば、rm file1.txt file2.txt file3.txt
のような操作です。
これによってrm *.txt
のようなグロビングもできるようになります。
異なる物に対して複数の引数をとるようになるなら、何かが間違っているかもしれません。
例外は一般的で主要な、覚えておけるほど簡潔なアクションです。
cp <source> <destination>
等がこれに当たります。
引用: 12 Factor CLI Apps.
標準が存在するなら、フラグ名には標準的な名前を使ってください。 他の一般的に使われているコマンドが使っているフラグ名があるなら、既存のパターンに従うのがベストです。 これによって、ユーザは2つの異なるオプション (と、どちらがどちらのコマンドのものか) を覚える必要がなくなり、ヘルプテキストを見ることなくオプションを推測できるようになります。
以下はよく使われるオプションのリストです。
-a
、--all
: 全部。-d
、--debug
: デバッグ出力を表示。-f
、--force
: 強制。
たとえば、rm -f
はそれを行う権限がないと思われる時でも強制的にファイルを削除します。
このオプションは通常ユーザの許可を必要とするような破壊的行為を行うコマンドに対して、スクリプト内でその破壊的行為を強制したいときも役に立ちます。--json
: JSON出力を表示。-h
、--help
: ヘルプ。
このオプションが持つべき意味はヘルプだけです。
ヘルプ の章を参照してください。--no-input
: インタラクティブ性の章を参照してください。-o
、--output
: 出力するファイル。sort
やgcc
が例です。-p
、--port
: ポート。psql
やssh
が例です。-q
、--quiet
: 静かにする。出力を減らします。
これはスクリプトで実行される時は隠したいような人間向け出力を表示する時に特に便利です。-u
、--user
: ユーザ。ps
やssh
が例です。--version
: バージョン。-v
: これはverboseかversionのどちらかを意味していることが多いです。
-d
をverboseに割り当ててこれをversionにするか、混乱を避けるために使わないという選択もできます。デフォルトをほとんどのユーザにとって正しいものにしてください。 設定可能にすることは良いことですが、多くのユーザは正しいフラグを探してそれを覚えて常に使う (またはエイリアスを作る) ことをしません。 デフォルトにしないと、ほとんどのユーザにとっての体験が悪いものになります。
たとえば、ls
はスクリプトへの最適化等の歴史的理由によってデフォルトでは簡潔な出力を行いますが、もし今デザインするならデフォルトはls -lhF
になるでしょう。
ユーザ入力を受け付けてください。 ユーザが引数やフラグを渡さなかった場合、それを尋ねてください。 (See also: インタラクティブ性)
入力を必須にしないでください。
入力をフラグや引数で渡す方法を常に用意してください。
stdin
がインタラクティブなターミナルでないなら、プロンプトをスキップしてただフラグ/引数を要求してください。
何か危険なことをする時は確認を取ってください。
一般的規約はインタラクティブな実行の時はユーザにy
かyes
を入力するように求め、そうでないときは-f
や--force
を渡すことを必要とするというものです。
「危険」というのは主観的な言葉であり、異なるレベルの危険があります。
--confirm="name-of-thing"
のようなフラグを渡せるようにしてください。ものを破壊する非自明な方法が存在しないか検討してください。 たとえば、設定ファイルの数値を10から1に変更すると9つの物が暗黙的に削除されるというような状況です。 これは重度のリスクと見なされるべきであり、このようなことを間違って起こさないようにしなければなりません。
入力や出力がファイルの場合、-
でstdin
から読んだりstdout
へ書いたりする機能をサポートしてください。
これにより、一時ファイルを利用することなしにコマンドの入出力を他のコマンドの入出力として利用できるようになります。
たとえば、tar
はstdin
からファイルを展開できます。
$ curl https://example.com/something.tar.gz | tar xvf -
フラグがオプショナルな値を受け取る場合、“none”のような特殊ワードを受け付けるようにしてください。
たとえば、ssh -F
は代替のssh_config
ファイルのファイル名をとりますが、ssh -F none
とするとコンフィグファイル無しでSSHを実行します。
ただの空文字列を使わないでください。引数がフラグの値なのか引数なのか曖昧になります。
可能ならば、引数やフラグを順序非依存にしてください。
多くのCLI、特にサブコマンドのあるものでは、引数を配置する場所について暗黙のルールがあります。
たとえばサブコマンドの前に配置しないと機能しない--foo
フラグを持つコマンドのようなものが例です。
mycmd --foo=1 subcmd
works
$ mycmd subcmd --foo=1
unknown flag: --foo
これはユーザを混乱させるかもしれません。コマンドの使用においてそのような混乱が起こる最も一般的な動作は、上キーを押して直近の呼び出しを取り出して、末尾にオプションを追加してまた実行するような場面です。 可能ならば、引数パーサの制約にぶつかったとしても、どちらの形式も等価であるようにしてください。
シークレットをフラグから直接読まないでください。
--password
のような形でコマンドがシークレットを受け取るとき、引数の値はps
の出力や場合によりシェルの履歴等から漏洩します。
さらに、この手のフラグはシークレットに対するセキュアでない環境変数の使用を招きます。
--password-file
のようにセンシティブなデータをファイルからのみ受け取るか、stdin
から受け取ることを検討してください。
--password-file
フラグは、様々なコンテキストにおいてシークレットをこっそりと渡すことを可能にします。
(Bashでは--password $(< password.txt)
のようにファイルの内容を渡すことが可能です。
このアプローチはps
の出力によってファイルの中身が漏洩する同じようなセキュリティリスクがあります。
避けるべきです。)
stdin
がインタラクティブなターミナル (TTY) の時のみインタラクティブな要素やプロンプトを使ってください。
これがコマンドがスクリプトで実行されていたりデータを他のコマンドにパイプ接続していたりしており、プロンプトが機能せずユーザに渡すべきフラグを知らせるエラーを送出すべきかを判定する信頼性の高い優れた方法です。
--no-input
が渡された時は、プロンプトやその他インタラクティブなことをしないでください。
これによりユーザがすべてのプロンプトを明示的に無効化できます。
コマンドが入力を必要としている場合は、フラグで情報を渡す方法をユーザに伝えて失敗してください。
パスワードの入力を求める時は、ユーザの入力を表示しないでください。 ターミナルのエコーを無効化することでこれを実現できます。 利用している言語にこれを行うためのヘルパがあるはずです。
脱出させるようにしてください。
出る方法を明確にしてください。
(Vimのようにならないでください。)
プログラムがネットワークI/O等でハングしたときも常にCtrl-Cが動作するようにしてください。
Ctrl-Cで脱出できないようなプログラム実行のラッパーである場合 (SSH、tmux、telnet等) 、脱出方法を明確にしてください。
たとえば、SSHはエスケープ文字~
によるエスケープシーケンスを受け付けます。
ツールが十分に複雑なら、サブコマンドによって複雑性を削減できます。 非常に関連性の高い複数のツールがある場合、それをひとつのコマンドにまとめることで使用と発見が簡単になります (RCSやGitが例です) 。
グローバルフラグ、ヘルプテキスト、設定、ストレージ機構の共有にも便利です。
サブコマンド間で一貫性を保ってください。 同じものに対しては同じフラグ名を使い、同じような出力フォーマットを使う等してください。
複数レベルのサブコマンドに対しては一貫性のある名前を使ってください。
複雑なソフトウェアにおいて多くのオブジェクトがありそのオブジェクトに対する多くの操作があるとき、それに対して2レベルのサブコマンドを使い、その1つを名詞、1つを動詞にするというのは一般的です。
たとえば、docker container create
がその例です。
異なる種類のオブジェクト間で利用する動詞に一貫性を持たせてください。
<名詞> <動詞>
と<動詞> <名詞>
のどちらも機能しますが、<名詞> <動詞>
のほうが一般的なようです。
参考文献: User experience, CLIs, and breaking the world, by John Starich.
曖昧だったり似通っていたりする名前のコマンドを作らないでください。 たとえば、“update” と “upgrade” という名前のサブコマンドがあるというのは非常に混乱を招きます。 異なる単語を使うか、単語を追加して曖昧性を取り除いてください。
ユーザの入力をバリデートしてください。 ユーザからデータを受け取る時は、常に悪いデータを受け取る可能性があります。 早期にチェックしなにか悪いことが起きる前に排除し、わかりやすいエラーにしてください 。
応答性は速度より重要です。 100ミリ秒以内にユーザに何かを表示してください。 ネットワークリクエストを行う場合、それを行う前に何かを表示して、ハングしていたり壊れていたりするように見えないようにしてください。
長時間かかるものには進捗を表示してください。 プログラムが長時間出力を行わないと、壊れているように見えます。 スピナーやプログレスインディケータはプログラムを実際より高速に見せます。
Ubuntu 20.04 にはターミナルの下部に張り付く良いプログレスバーがあります。
プログレスバーが1箇所で長時間停滞すると、ユーザはそれがまだ動いているのか、プログラムがクラッシュしているのかわかりません。 予測残り時間を表示したり、何かアニメーションを行うコンポーネントがあれば、それがまだ動いていることがわかり良いです。
プログレスバーを生成するための良いライブラリがたくさんあります。 たとえばPythonには tqdm 、Goには schollz/progressbar 、Node.jsには node-progress があります。
可能なら並列化してください。ただし思慮深く行ってください。 シェルにおいて進捗表示をすることはただでさえ難しく、並列プロセスに対してそれを行うことは何倍も難しいです。 並列処理はロバストであるように、また出力がぐちゃぐちゃにならないようにしてください。 ライブラリがあるなら利用してください。自分で書くものではありません。 Pythonの tqdm やGoの schollz/progressbar は複数のプログレスバーのネイティブサポートを行います。
これの利点は大きなユーザビリティの向上が見込めることです。
たとえば、docker pull
のマルチプログレスバーは何が起きているかについて素晴らしい洞察を提供します。
$ docker image pull ruby
Using default tag: latest
latest: Pulling from library/ruby
6c33745f49b4: Pull complete
ef072fc32a84: Extracting [================================================> ] 7.569MB/7.812MB
c0afb8e68e0b: Download complete
d599c07d28e6: Download complete
f2ecc74db11a: Downloading [=======================> ] 89.11MB/192.3MB
3568445c8bf2: Download complete
b0efebc74f25: Downloading [===========================================> ] 19.88MB/22.88MB
9cb1ba6838a0: Download complete
気をつけるべきこととして、順調に進行している時にログをプログレスバーの背後に隠すことでユーザが何が起きているか理解しやすくなりますが、エラーが起きている時はログを表示するようにしてください。 そうでないとデバッグが非常に難しくなります。
タイムアウトを設けてください。 ネットワークタイムアウトを設定可能にし、コマンドが永久にハングしないように合理的なデフォルト値を設定してください。
回復可能にしてください。 プログラムが一時的理由 (インターネット接続がダウンしているなど) で失敗した時は、上キーを押してエンターを押せば中止したことをやり直せるようにしてください。
Crash-onlyにしてください。 (訳注: Crash-only softwareは終了時にクリーンアップを行わず常にクラッシュし、次回起動時にクリーンアップを行い回復するようにするというソフトウェアの設計方法。) これは冪等性を確保した後の次のステップです。 操作の後のクリーンアップの必要性をなくせる、もしくはクリーンアップを次の実行まで遅らせられるなら、プログラムは即座に失敗または中断して抜けられるようにできます。 これによりプログラムが堅牢かつ応答性の高いものになります。
引用: Crash-only software: More than meets the eye.
人々はあなたのプログラムに対して間違った使い方をします。 それに備えてください。 彼らはプログラムをスクリプトでラップし、貧弱なインターネット環境で使用し、同時に大量のインスタンスを実行させ、あなたがテストしていない、想定しなかったような癖のある環境下で使用します。 (macOSのファイルシステムが大文字/小文字を区別しないのに大文字/小文字を保持することを知っていましたか?)
あらゆる種類のソフトウェアにおいて、よく文書化された長期間に渡る廃止プロセスなしにインターフェースが変更されないというのは重要です。 サブコマンド、引数、フラグ、設定ファイル、環境変数。これらすべてがインターフェースであり、動作するよう維持するよう務めるべきものです。 (セマンティックバージョニングは大規模な変更のみを許容します。もし毎月メジャーバージョンが上がるようなら、意味がありません。)
可能な限り変更は追加的にしてください。 フラグの振る舞いを後方互換性を壊す形で変更するのではなく、新しいフラグを追加したほうが良いかもしれません。インターフェースが肥大化しすぎない限り。 (See also: 引数よりフラグを使う)
追加的でない変更を行う前に警告してください。 しだいにインターフェースを破壊しないといけなくなってくるでしょう。 そうする前に、プログラムそれ自体の中でユーザに事前警告してください。廃止したいフラグが渡されたら、それが間もなく変更されることを伝える等です。 変更後にも動作するように使い方を今から変えておく方法が存在するようにして、それを伝えてください。
可能なら、ユーザが使い方を変更したことを検知して、それ以降警告を出さないようにしてください。最終的に変更をロールアウトした時に気付かなくなります。
人間向け出力を変更するのは通常問題ありません。
インターフェースを使いやすくする唯一の方法は改善を反復することですが、出力がインターフェースだとみなされるなら、それを反復的に変更することはできなくなります。
出力を安定させるためにスクリプト内では--plain
や--json
を使うようユーザに推奨してください (出力の章を参照) 。
サブコマンドを省略させないでください。
一番良く使われているサブコマンドがあるなら、簡潔にするためにそれを完全に省略できるようにすることに誘惑されるかもしれません。
例えば、任意のシェルコマンドをラップするrun
コマンドがあるとします。
$ mycmd run echo "hello world"
mycmd
の最初の引数が既存のサブコマンドの名前でない場合、run
であると仮定し、以下のように入力することが可能です。
$ mycmd echo "hello world"
これは非常に便利です。しかし、echo
を含め任意のサブコマンド名すべてが既存の使用箇所を壊すリスクなしに導入できなくなりました。
mycmd echo
を使うスクリプトがあった場合、ツールを新しいバージョンにアップグレードすると全く異なる動作をするようになってしまいます。
サブコマンドの任意の短縮形を許可しないでください。
たとえば、install
というサブコマンドがあるとします。
これを追加する時、ユーザのタイプ数を削減するために、曖昧性の発生しないすべての接頭辞、たとえばmycmd ins
や単にmycmd i
をmycmd install
のエイリアスにしようと思うかもしれません。
しかしこれは落とし穴です。外部のスクリプトがi
をinstall
だと仮定しているため、もうi
から始まるコマンドを追加できなくなってしまいました。
エイリアス自体が悪いわけではありません。タイピング数を削減するのは良いことです。しかしそれは明示的に行い、安定させ続ける必要があります。
「時限爆弾」を作らないでください。 今から20年後のことを想像してみてください。 コマンドが今と同じように動作しますか、もしくはインターネット上の外部依存が変更されたりメンテされなくなったりして動作しなくなるでしょうか? 20年後に存在しない可能性が最も高いサーバは、今あなたがメンテしているサーバです。 (とはいえ、Google Analyticsへのブロッキング接続も作らないようにしてください。)
ユーザがCtrl-C (INTシグナル) を入力したら、可能な限り速やかに終了してください。 すぐに反応するようにしてください。クリーンアップを行う前にです。 永久にハングすることのないように、すべてのクリーンアップコードにタイムアウトを設けてください。
時間の掛かりそうなクリーンアップの間にユーザがCtrl-Cを入力したら、それをスキップしてください。 それが破壊的アクションである場合は、再びCtrl-Cを押した時に何が起きるかをユーザに伝えてください。
たとえば、Docker Composeを終了する時Ctrl-Cを2回押すことで、通常のシャットダウンを行う代わりにコンテナを即座に強制停止させることができます。
$ docker-compose up
…
^CGracefully stopping... (press Ctrl+C again to force)
プログラムはクリーンアップが完了していない状況で開始することを想定していなければなりません。 (Crash-only software: More than meets the eyeを参照してください。)
コマンドラインツールは様々な種類の設定を持ち、設定は様々な方法で渡されます (フラグ、環境変数、プロジェクトレベルの設定ファイル) 。 設定を渡す方法の良さを決めるファクターは少なく、特に固有性、安定性、複雑性が挙げられます。
設定は一般にいくつかのカテゴリに分類できます。
次のコマンド呼び出しを変えるもの
例:
一般に1回の呼び出しを超えて安定しているが、不変ではないもの。 プロジェクトにより異なります。 同じプロジェクトであってもユーザによって異なります。
この種の設定は各コンピュータ固有のものであることが多いです。
例:
推奨: フラグや、場合により環境変数も利用してください。
ユーザはこれらの変数をシェルプロファイルで設定してグローバルに適用したり、特定のプロジェクトに向けて.env
を使うかもしれません。
設定が非常に複雑なら設定ファイルを作ってもいいかもしれませんが、通常は環境変数の方が優れています。
すべてのユーザに対する、プロジェクト内で安定しているもの。
これはバージョン管理の対象になる種類の設定です。
Makefile
、package.json
、docker-compose.yml
等が例です。
推奨: コマンド固有の、バージョン管理されるファイルを使ってください。
XDG仕様に従ってください。
2010年にX Desktop Group、現 freedesktop.org は、設定ファイルが置かれるベースディレクトリについての仕様を策定しました。
目標の一つは汎用の~/.config
フォルダのサポートによりユーザのホームディレクトリにおけるドットファイルの増殖を制限することです。
XDGベースディレクトリ仕様
(完全な内容はこちら、要約はこちら
(訳注: 日本語の要約はこちら))
はyarn、fish、wireshark、emacs、neovim、tmux、その他あなたの愛用している多くのプロジェクトによりサポートされています。
あなたのプログラムのものでない設定を自動的に変更する場合、何が起きているか伝えて同意を取ってください。
既存の設定ファイル (例: /etc/crontab
) に追記するよりも新しい設定ファイル (例: /etc/cron.d/myapp
) を作るようにしてください。
もしシステム設定に追記または変更を行わなければならないなら、そのファイル内で変更を示す日付付きのコメントを使用してください。
設定パラメータを優先順位に従って適用してください。 以下は設定パラメータの優先順位を高いものから順に並べたものです。
.env
)環境変数はコマンドが実行されているコンテキストによって異なる振る舞いをするためのものです。 環境変数の「環境」とはターミナルセッションのことです。つまり、コマンドが実行されているコンテキストのことです。 したがって、環境変数はコマンド実行毎、もしくは1つのマシンの異なるターミナルセッション間、もしくは各マシンのプロジェクトの初期化のたびに異なる可能性があります。
環境変数はフラグや設定パラメータと機能が重複しているか、区別が難しい事があるかもしれません。 設定の章を読んでよくあるタイプの設定と、環境変数が最も適切な状況を理解してください。
ポータビリティを最大化するために、環境変数の名前は英大文字、数字、アンダースコアのみを含む (かつ、数字から始まらない) ようにしてください。
つまりO_O
とOWO
だけが環境変数名として正しい絵文字だということです。
環境変数の値は1行になるよう努めてください。
複数行の値も可能ですが、env
コマンドを使う時にユーザビリティの問題を発生させます。
一般的すぎる名前を採用するのは避けてください。 こちらにPOSIX標準の環境変数のリストがあります。
可能なら設定値として汎用環境変数をチェックしてください。
NO_COLOR
は色を無効化 (出力を参照) 、FORCE_COLOR
は有効化し、検知ロジックを無効化しますDEBUG
は冗長出力を有効化しますEDITOR
はユーザにファイルの編集や複数行の入力をさせたい時に使いますHTTP_PROXY
、HTTPS_PROXY
、ALL_PROXY
、NO_PROXY
はネットワーク操作を行う時に使います
(HTTPライブラリによってはこれらを確認してくれます。)SHELL
はユーザの好きなシェルでインタラクティブセッションを開始する時に使います
(シェルスクリプトを実行する必要がある時は/bin/sh
のように特定のインタプリタを使うようにしてください)TERM
、TERMINFO
、TERMCAP
はターミナル固有のエスケープシーケンスを使用する時に参照できますTMPDIR
は一時ファイルの作成に使いますHOME
は設定ファイルの配置場所に使えますPAGER
はページ分割された出力に使いますLINES
とCOLUMNS
はスクリーンサイズに依存する出力 (表など) で利用します適切な場所の.env
から環境変数を読み込んでください。
コマンドがユーザが作業を行う特定のディレクトリにおいてあまり変化しない環境変数を定義している時、
ローカルの.env
も読んでユーザがディレクトリごとに異なる設定を毎回設定することなしに行えるようにするべきです。
多くの言語には.env
を読むためのライブラリがあります (Rust、 Node、 Ruby) 。
.env
を正式な設定ファイルの代わりにしないでください。
.env
ファイルにはさまざまな制約があります。
.env
ファイルはソース管理で保持されないことが多いです.env
ファイル内に書かれる設定には履歴がありません)これらの制約がユーザビリティやセキュリティを損なうと思われる場合は、特化した設定ファイルがより適切かもしれません。
環境変数からシークレットを読まないでください。 環境変数はシークレットの格納に便利かもしれませんが、漏洩しやすいことが証明されています。
curl -H "Authorization: Bearer $BEARER_TOKEN"
のようなシェル置換はグローバルに可視なプロセスの状態から漏洩します。
(cURLは代わりに-H @filename
でファイルから機微なヘッダを読む機能を提供しています。)docker inspect
でアクセスすることで誰でも見ることができますsystemctl show
で誰でも見ることができますシークレットはクレデンシャルファイル、パイプ、AF_UNIX
ソケット、シークレット管理サービス、その他IPCメカニズムでのみ受け取るようにすべきです。
“脅迫的な短縮形の使用と大文字の回避は注目すべきものです。Unixは反復ストレス障害が鉱山労働者にとっての黒い肺と同じようなものだと考える人により発明されたシステムです。 川の流れに削られる石のように、長い名前は3文字の塊にまで摩耗してしまいます。” — Neal Stephenson, In the Beginning was the Command Line
プログラムの名前はCLIにおいて特に重要です。ユーザが常にタイプするものであるため、覚えやすく入力しやすいものでなければなりません。
シンプルで覚えやすい単語にしてください。
ただしあまり一般的だったり、他のコマンドと被ってユーザを混乱させたりしないようにしてください。
たとえば、ImageMagickとWindowsには共にconvert
というコマンドがあります。
小文字のみを使い、ダッシュは本当に必要なときだけ使ってください。
curl
はいい名前です。DownloadURL
は良くない名前です。
短くしてください。
ユーザはそれを常にタイプします。
短くしすぎないでください。非常に短いコマンドはcd
、ls
、ps
のような常に使う一般的ユーティリティのために確保しておくほうが良いです。
入力しやすいものにしてください。 そのコマンドが1日中タイプされると思われるなら、手で入力しやすいものにしてください。
現実の例として、Docker Composeがdocker compose
になるずっと前、それは
plum
という名前でした。
これは片手があちこち飛び回ってぎこちないため、すぐに
fig
という短く入力しやすい名前に変わりました。
参考文献: The Poetics of CLI Command Names
可能なら、シングルバイナリとして配布してください。 使用している言語が標準で実行バイナリへのコンパイルを行わない場合、 PyInstaller のようなものがないか探してください。 本当にシングルバイナリとしての配布ができないなら、プロットフォームネイティブのパッケージインストーラを使ってディスク上に簡単には削除できない形でファイルが散らばるのを避けてください。 ユーザのコンピュータをきれいに保ちましょう。
コードリンタのような言語固有のツールを作っている場合、この規則は適用されません。ユーザのコンピュータにインストールされている言語のインタプリタが存在すると安全に仮定できるためです。
アンインストールしやすくしてください。 アンインストール方法の指示が必要なら、インストール方法の指示の下に配置してください。人々が最もソフトウェアをアンインストールするのはインストール直後です。
使用状況メトリクスはユーザがどのようにプログラムを使用しているか、プログラムをどのように改善すればいいか、また注力すべき場所はどこかを知るために役立ちます。 しかし、ウェブサイトとは異なり、コマンドラインのユーザは環境がコントロール下にあることを期待するため、プログラムが告知なしにバックグラウンドでそのようなことを行うと驚きます。
使用状況やクラッシュデータを同意なしに送信しないでください。 ユーザはいずれそれを発見し、怒ります。 何を収集するか、なぜ収集するか、匿名性はどれくらいか、どのように匿名化されるか、どれくらいの期間保持するかを非常に明示的にしてください。
理想的には、データ収集に協力するかどうかをユーザに確認してください (オプトイン) 。 収集をデフォルトで行うと決めた場合 (オプトアウト) 、ウェブサイト上や初回実行時に明確にユーザに伝え、簡単に無効化できるようにしてください。
以下は使用状況の統計を収集しているプロジェクトの例です。
アナリティクスを収集する以外の方法を検討してください。
参考文献: Open Source Metrics