emahiro/b/logs

勉強記録と書評とたまに長めの呟きを書きます

Goにおける簡易だけど最低限の情報が入っているerror出力方法の知見

errorについての検討

golangはerrorまで含めて愚直に書くことが求められたりして、それはそれで慣れではあるなーと感じているのですが、errorのformatが人に依存したり、1人で個人開発とかしているとerrorをちゃんと書いてるのがめんどくさくなったりするので、ある程度簡易にかけて、どんなerrorが返っているのかわかりやすい方法はないものか、を考えていたところ、同じチームの人の知見が非常に役に立ったので、検討して備忘録としてまとめてみました。

最低限の要件

errorを書くのであれば、多少冗長でも、どういうerrorが返ってきていて、さらにはどのpackageで出力されたのかがわかってかつ、formatが大体統一されていれば、大体十分かなという程度で考えていきます。

要件

  • 冗長でもメッセージがわかる
  • できればどのpackageで吐かれたかがわかる
  • formatが大体統一されている

fmtパッケージのformatを駆使する

最低限の要件を満たすために、どういうerrorの書き方が良いのかを検討したところ、おなじみのfmtパッケージをうまく使えば、特にerrorの形式を規約等で決めることなく標準の機能だけでそれらしいエラーを出力できるのでは?と思ったので実際に書いてみました。

formatの出力に関する詳細は以下を参考にしました。

blog.y-yuki.net

上記のエントリーを参考にすると、fmtパッケージで出力する場合

  • %#v -> Go言語のリテラル表現で対象データの情報を埋め込む
  • %T -> errorの型を明示してくれる

というようなルールがあるので、このルールを元に以下のような出力形式を考えます。

errorを発生させるコードは以下です。

package main

import (
    "encoding/json"
    "fmt"
    "os"
    "strings"
)

type User struct {
    ID int64 `json:"id"`
    Name string `json:"name"`
}

func main() {
    j := `{
      "id": 1,
      "name": "taro",
  }`

    r := strings.NewReader(j)

    u := User{}
    if err := json.NewDecoder(r).Decode(&u); err != nil {
        // error出力箇所
        os.Exit(-1)
    }

    fmt.Printf("user: %#v", u)
}

errorの出力内容は以下の用に2パターン考えてみました。
この他にもわかりやすい書式はあると思いますが、最低限欲しい情報のみをまとめた形になります。

fmt.Printf("%#v\n", err) 
//&json.SyntaxError{msg:"invalid character '}' looking for beginning of object key string", Offset:33}

fmt.Printf("Error:%T message: %v", err, err) 
//Error:*json.SyntaxError. message: invalid character '}' looking for beginning of object key stringexit status 255

ここで検討したいのは # を付けるとpackageまで含めてerror messageを標準で出力してくれるということです。
また %T のformatにした場合、errorが起きたpackageのtypeを型として出力してくれるので、これもどこのpackageで出力されたかを一目で理解することが出来ます。

まとめ

golangでerrorを書くときはプロジェクトごとに決まりがあるかと思いますが、もしない場合でerrorの書き方が属人的になってしまっている場合は、golangの標準のformatに準拠してみると、馴染みやすく、また簡潔で、ひと目でなんのエラーか理解できるので使えるなと思いました。

errorはめんどくさいですが多少冗長なくらいでちょうどいいと感じています。