先日、SPMというコマンドラインツールをリリースしましたが、golangでコマンドラインツールを作成する上で便利だったツールや考え方等を備忘として残します。

CLIのベース

3rd Partyのライブラリを利用せずとも標準のflagパッケージで処理を書けますが、色々と面倒なのでurfave/cliのパッケージを利用しました。このパッケージは以下のように、簡単にサブコマンドやフラグを定義できます。

非常に使いやすいライブラリなのですが、エラーハンドリングはやや柔軟性に劣る印象です。

ライブラリの使い方としては、Actionの関数の戻り値にerrorを入れれば良いのですが、その場合、HandleExitCoderが呼び出され、fmt.Fprintfで標準エラー出力にエラーメッセージを出力するとともに、os.Exitされてしまいます。コマンドラインでの挙動を柔軟にしたい場合は、cliのインスタンスをラップし、独自にエラー変数を用意して、それを返す方が良さそうです。

YAMLのロード

設定のロードでYAMLを利用する際はgopkg.in/yaml.v2を使います。

使い方はxmlやjsonのUnmarshalと同じく、structを定義してマッピングします。

ロガー

標準のロガーであるlogパッケージはレベル付きのロギングをサポートしていなかったり、フォーマッティングは自分でやらないといけなかったりと不便なので、3rd PartyのSirupsen/logrusを使いました。

使い方はこんな感じ。

あるいは

のような使い方でもOK。前者の方法は一つのチャネルにしかロギングできませんが、後者の方法であれば複数のloggerインスタンスを作成できるため、複数のチャネル(例えばstdoutとstderr)にロギングすることができます。

実際には、このloggerを直接使うのではなくラップした独自のloggerを定義して利用した方が変更に強い実装になりそうです。

テスト

DIの話と同様に、入出力のストリームを置き換えできるようなインターフェースにしておくとテストが楽です。CLIの場合は入力のストリームはコマンドライン引数であり、出力ストリームは標準出力になります。これらをテスト時には文字列の配列、Bufferに置き換えることになります。

SPMだとロガーのデフォルトはStdout、Stderrにしており、テスト時はbytes.Bufferに書き込むようにしています。StdoutはFileの型なので、コマンド利用時とテスト時の両方に適用できる型として、io.Writerのインターフェースを指定していることに注意してください。

またテストはstretcher/testifyのライブラリを利用するとアサーションが楽です。

参考URL