最近、ちまちまとGoのlinterを作るのにはまっています。
作ったlinterの一つnamedについて紹介したいと思います。
目次
- defer Closeのエラーハンドリングをちゃんとする。
- defer Closeのエラーハンドリングをちゃんとやるには名前付き戻り値を使わないとだめ。
- Linter named は何をしてくれるのか?
- Q&A
defer Closeのエラーハンドリングをちゃんとする。
os.File や net/http.Request.Body は使い終わったあとにClose
メソッドを呼ぶ必要があります。なので、よくあるサンプルコードではClose
忘れを防ぐためにdefer
を使います。
|
|
しかし、io.Closer の定義を見れば分かるように、Close
メソッドはエラーを返すことができます。つまり、サンプルコードのように単純にdefer
でClose
呼び出しを行うと、戻り値のエラーを無視してしまうことになります。
ということで、ちゃんとエラーハンドリングをしようとすると以下のような感じになります。
|
|
このようなスニペットを専用の関数にまとめてしまうこともあるでしょう。
|
|
defer Closeのエラーハンドリングをちゃんとやるには名前付き戻り値を使わないとだめ。
実は、Close
メソッドからの戻り値をちゃんとキャッチしてエラーハンドリングするにはもうちょっと注意が必要です。
次のサンプルコードはClose
で発生したエラーを補足するのに失敗した「だめな例」です。
|
|
The Go Playgroundで実行してみると分かる通り、出力されるエラーメッセージはoriginal error
だけでfailed to close
は含まれていません。
せっかく、defer Close
を呼び出しても、ローカル変数のerr
を書き換えただけで、戻り値を書き換えられたわけではないためです。
ということで、タイトルの通り、「名前付き戻り値」を使って、戻り値の書き換えを行ってみます。
|
|
出力は期待通り、err from close: failed to close: original error
になります。The Go Playgroundでも確かめられます。
このように、defer Close
のエラーハンドリングをちゃんとやろうとすると、意外と注意することが多いのです。。
Linter named は何をしてくれるのか?
Linter named
は、指定した関数が名前付き戻り値を引数として受け取ることを保証します。
例えば、defer Closeのエラーハンドリングをちゃんとやるには名前付き戻り値を使わないとだめ。 の最初の例(だめな例)に対して、linter named
を実行すると次のようにlintエラーが表示されます。
./main.go:17:8: Close should be called with a named return value as the 2th argument
いい感じですね 😙
Linter named
はよくあるGoのlinterとちがってバイナリを提供しません。go install
できません。利用者にbuildしてもらい、go vet
と一緒に使ってもらう想定です。こんな感じで main.go
にnamed
の設定をハードコードします。
|
|
buildしたバイナリnamed
にパスが通っていれば、go vet -vettool=$(which naned)
でリントできます。
ちなみに、named
はdefer Close
以外のユースケースにも対応できます。例えば、準標準package pkgsite の内部で使われている、関数derrors.Wrapにも使えます。derrors.Wrap
はエラーをラップしないままにreturnしてしまうのを防ぐための関数であり、先程のdefer Close
のパターンと同様に戻り値を書き換えることを意図しています。そのため、derrors.Wrap
の引数にも名前付き戻り値を渡す必要があり、linter named
の守備範囲に含まれることになります。
試しにpkgsiteにnamed
linterをかけてみたところ、エラーは検出されませんでした。流石でした🙇
Q&A
defer Close用の関数を用意してないんだけど??
この場合、named
は使えません。そもそもこのようにdefer Close用の関数が用意されていない場合、Close
メソッドの戻り値であるエラーをどうハンドリングするかは決まっていないのでlinterで誤りを検知するのは難しそうです。
false positiveを許せばチェックは可能かもしれません。呼び出し元の関数で名前付き戻り値を使っていない場合はエラーにするとか、Close
の戻り値が直接的あるいは間接的に名前付き戻り値の値に使われていることをチェックするとか。
どうして設定ファイルを使わずに、設定をGoでハードコードするの?
yamlを書きたくないからです。リポジトリにこれ以上yamlファイルを増やしたくない。。
もうちょっとちゃんと言うと、Goで設定を書けばIDEの恩恵を簡単に受けられるためです。設定を構造体として記述していくので、自動補完も効きますし、フィールドの説明文も自動で表示されて便利です。