Go Tips連載の第6弾です。
はじめに
TIG DXユニットの真野です。先週のこの記事ぶりの投稿になります。
フューチャー社内には「Go相談室」というチャットルームがあり、そこでGoに関連する疑問を投げたら、大体1日くらいで強い人が解決してくれるという神対応が行われています。そこでAWSやGCPの独自エラーをError warppingされた時にどうやってハンドリングすればよいの? と聞いた時にやり取りした内容をまとめました。
背景
Go1.13からfmt.Errorf
関数に %w
という新しい構文が追加サポートされたことは、ご存知の方が多いと思います。
利用方法は、%w
(pkg/errorsの時と異なりコロンは不要だし末尾じゃなくてもOK) と一緒に fmt.Errorf
を用いることで、コンテキストに合わせた情報をメッセージに追加できます。
func main() { |
- [Go Playground] https://play.golang.org/p/C__gN90iyt7
また、error種別ごとに処理を分けたい場合で、Sentinel errorを判定する場合は、 errorsパッケージに追加された errors.Is
でWrapの判定できます。逆に言うとWrapされている場合、今まで通りの if err == ErrNotFound {
といった構文では判定できなくなるので、既存コードへの導入時は呼び出し元と合わせてリライトが必要です。
var ErrNotFound = errors.New("not found") |
- [Go Playground] https://play.golang.org/p/R4KzOPVd_SA
この場合はシンプルで良いのですが、AWS SDK for GoなどのerrorをWrapした時に呼び出し側で判定をしたい時、どうすればよいのかが直接的な内容が見当たらなかったのでここにまとめておきたいと思います。
Handling Errors in the AWS SDK for Go
ドキュメントを読むと例えば、AWSのErorrハンドリングは以下のように、awserr.Error
というインタフェースで表現されており、一度errを型アサーションしてから内部的なエラーコードに応じてハンドリングすることになっています。
if err != nil { |
これをWrapされたときは、呼び出し元で単純に型アサーションを行ってもうまく判定できません。
func AnyFunc() error { |
※Go Playgroundでサンプルを載せようと思いましたが、importでTimeoutになったので諦めました
対応方法
この awserr.Error
を満たすerrorをWrapしたときはどうすべきかというと、 errors.As
を用います。errors.As
を代入用の変数とともに利用するとうまくいきます。
if err := AnyFunc(); err != nil { |
例として愚直にif分岐をすべて網羅するように書きましたが、早期returnを活用すると、よりネストが浅く見通しが良いコードにできると思います。
GCP SDKの場合
しばしば以下のエラーを返すことが多いとのことです。
https://godoc.org/google.golang.org/api/googleapi#Error
if e, ok := err.(*googleapi.Error); ok { |
もしこれらのerrorをWrapする場合は、同様に errors.As
で判定します(実際は後述する各サービスごとに宣言されているSentinel errorで判断することが多いと思います)
if err := AnyFunc(); err != nil { |
一方で、StorageなどはSentinel errorを返します。
var ( |
errorを返すAPIを利用してWrapした場合は errors.Is
で判定します。
// Storageに対して何かしらアクセスする処理 |
どのAPIがどういったerrorを返しうるかは、各GoDocに書いてありますので、個別のハンドリングが必要な場合は確認することになると思います。
Stacktraceの出力について
https://play.golang.org/p/NAYR7XySCdW にサンプルコードを載せましたが、 %w
構文を用いたfmt
パッケージではStacktraceが出力されません。もし、Stacktraceが必要な場合は fmt.Errorf
ではなく xerrors.Errorf
を用いてWrapします。
シビアに性能が求められない、例えばBackendのWeb APIをGoで実装する場合は、 xerrorsパッケージを利用した方が、2020/01/26 時点では良さそうです。
- xerrorsについては、そな太さんの Goの新しいerrors パッケージ xerrors の記事がとても参考になりました
import ( |
- [Go Playground] https://play.golang.org/p/4xcqP7Ukt0H
これを実行するとStacktraceが出力されました。
stacktrace: anyFunc any error - internal failed: |
ちなみに、xerrorsでWrapされたエラーでも、errors.Is, errors.Asで判定できました(混在すると少し気持ち悪いですが)
- [Go Playground] https://play.golang.org/p/nfu_JXo6N_e
まとめ
- Sentinel errorの場合は、
errors.Is
で、独自Error型を宣言している場合は、errors.As
を利用してハンドリングする - Stacktrace情報が必要な場合は、xerrorsパッケージを利用する
- xerrorsでWrapしても
errors.Is
,errors.As
で扱える