blog.syfm

徒然なるままに考えていることなどを書いていくブログ

CLI ツールのフラグ設計むずかしい

CLI ツールを作る・保守していく上で最も難しいのはフラグ設計かもしれない。 一つのことをうまくやる、サブコマンドを持たないレベルまで小さいものであれば比較的容易だけど、サブコマンドを使うレベルになってくると途端に難しくなる。

どういった点が難しいのかをざっと思いつくものについて上げてみる。

  • フラグ名の割り当て
  • 適切な名前
  • 提供する機能の粒度
  • 後方互換

フラグ名の割り当て

例えば --version の短縮名として -v--help の短縮名として -h がしばしば使われる。なので、ユーザは初めて使う CLI ツールであってもとりあえず -v-h を叩いてみることが多い (特に使い方がわからない場合は --help-h を必ずと行っていいほど叩くと思う)。
ここで、-h がヘルプではなく別の機能に割り当てられていた場合、ユーザは違和感を覚えると思う。直感的な操作を提供できるか、というのは CLI ツールの設計においてかなり重要だと感じるのでできる限り直感に沿った機能を提供したい。

しかし、その一方でフラグ名の割り当ては慎重に決定する必要がある。例えば、ヘルプはユーザが CLI ツールの使い方を忘れるたびに叩かれるのでそれなりに使用される頻度は多いが、バージョンを表示する --version はそれと比べて圧倒的に頻度が少ないはず。使用頻度が少ないコマンドに短縮名を使ってしまうと、その文字を頭文字に持つ他のフラグの短縮名を提供したくなったときに困ることになる。例えば、v が頭文字につくものでしばしばフラグ名に使われているものとして --verbose がある。--verbose が具体的にどういった機能を提供するかはそれぞれの CLI ツールに依るが、少なくとも冗長・補助的な出力を増やすフラグであることがほとんどで、--version よりも使用される頻度が高いと思う。

短縮形で使用できる文字にはかなり制限がある (一文字しか使えないので) ため、短縮形を使うべきフラグは非常に使用頻度が高いものや、その CLI ツールにおいてコアな機能を提供するものにのみ使うべきだと個人的には思う (= 基本的に短縮形は使わない)。

適切な名前

例えば --port のような、フラグ名の指しているものの値を変えるような場合であればフラグ名を決定するのは容易だが、振る舞いを変えるようなフラグ名の場合は適切な名前を決めるのが難しくなる。これは変数名をつける時と同じ問題だが、CLI ツールの場合、フラグ名の長さは短ければ短いほど良いのでより難しい。 しかも、振る舞いを変えるフラグの場合、あまり慣習のような名前もない。そのため、日常的に使われている CLI ツールを見てもあまり参考にならなかったりする。

提供する機能の粒度

これが一番難しい。本当に難しい。例えば、出力のフォーマットを JSON 形式で行いたいというユースケースがあるとする。ぱっと思いつくフラグ名は --json といったフラグを提供して、これが有効になっていれば通常の出力ではなく JSON 形式に切り替えるようにする。しかし、JSON だけで良いのかという問題もある。例えば、kubectl は --output というフラグを提供していて、これは --output=jsonJSON 形式、--output=yamlYAML 形式で出力を行うことができる。

ではこの CLI ツールにも --output--format のようなフラグを作って複数のフォーマットに対応したほうが良いのだろうか?正直一長一短だと思う。複数のフォーマットに対応するということは一つのことをうまくやる UNIX 哲学に沿っていないようにも感じられる。また、--output フラグには値を指定しなければいけないため、bool のフラグよりもほんの少しだけ使い方が複雑になる。しかし、その一方で出力内容が複雑な場合、構造化された出力形式がないとパイプを組み合わせたフィルタ処理がしにくく、やはり UNIX 哲学に沿えてない設計になってしまう。また、--json のような特定のフォーマットに依存したようなフラグを定義すると他のフォーマットをサポートすることになった場合に比例してフラグの数が増えることになる。また、--json という名前だけでは 出力 フォーマットかどうか知ることができない。ツールが複雑になっていくと、例えば入力を JSON 形式の文字列で受け取りたいといった別なユースケースが生じてフラグ名が衝突する可能性もある。かといって --json-out のような名前にするとフラグ名が長くなってしまい、打つのがめんどうになる。短縮名を用いることはできるが前述したとおり、それもまた衝突する可能性がある。

後方互換

OSS として公開しているともっとも大変なのが後方互換性の維持だと思う。自分しか使っていないような CLI ツールであれば master に雑に push して後方互換性を破壊しても困る人はいないが、多くの人に使われるようになった CLI ツールでは後方互換性を維持することが必須となる。セマンティックバージョニングであればメジャーバージョン前であれば定義的には後方互換性を壊しても問題ないが、告知なしに互換性を壊されるとツールを使うのをやめるユーザも多いと思う。そもそも多くの人にとってセマンティックバージョニングはどうでも良く、手元ですんなり動くことが最も重要なはず。

例えば社内のライブラリであれば開発チャンネルで @here したり、マイクロサービスであればクライアントをすぐに洗い出せるので合意も取りやすい。しかし、CLI ツールは社内のソフトウェアとは異なり、ユーザの顔が見えないので後方互換性を崩したい場合は事前に告知をした上で、メジャーバージョンを上げる際にのみ行わなければいけない。

前述したフラグ名の問題も後方互換性の問題と組み合わせるとより辛くなる。迂闊にネームスペースの大きいフラグ名を付けてしまうと次のメジャーバージョンまでそれを変更することができず、さらにそのフラグ名にふさわしい機能が増えた時に割り当てることができずに妥協した名前になってしまう。特にサブコマンドがある CLI ツールの場合、グローバルフラグ (コマンド名の直後に現れる、CLI ツール全体で有効なフラグ) とローカルフラグ (サブコマンドでのみ有効なフラグ) があることが多いので定義するフラグがグローバル・ローカルどちらにあるべきかを慎重に選ばないといけない。

うーん、むずかしい 😇