tweeeetyのぶろぐ的めも

アウトプットが少なかったダメな自分をアウトプット<br>\(^o^)/

【go】golangのエラー処理メモ - ③. pkg/errorsでのエラーハンドリング

はじめに

goをさわって数ヶ月ですが、雰囲気では書けていたものの
errorやエラーハンドリングについてはもやもやしたままだったので自分理解メモの③

関連

この記事の関連です。

アジェンダ

  1. エラーハンドリングで困る事
  2. pkg/errorsでのエラーハンドリング(errors.Wrap)
  3. pkg/errorsでのエラーハンドリング(errors.Cause)

1. エラーハンドリングで困る事

main -> packageA.method -> packageB.method のように呼び出しを行っている場合、
packageB.methodで起きたエラーをpackageA.methodで拾ってmainにさらに返したいときがあります。

packageA.methodでのハンドリングによっては、
mainでなんのエラーだったかわかりにくくなるときがあります。

サンプル

ここでは main -> fuga.method -> hoge.method のように呼び出しているとします。

hoge.methodが返すエラーをfuga.method
ハンドリングしてさらにエラーを返します。

mainではerrrorかどうかをハンドリングします。

コード
package hoge

import "fmt"

type HogeSomethingError struct{}

func (f *HogeSomethingError) Error() string {
  return fmt.Sprintf("this is HogeSomethingError")
}

type HogeAnythingError struct{}

func (f *HogeAnythingError) Error() string {
  return fmt.Sprintf("this is HogeAnythingError")
}

func DoSomething() error {
  return &HogeSomethingError{}
}

func DoAnything() error {
  return &HogeAnythingError{}
}

func DoExciting(b bool) error {
  if b {
    return DoSomething()
  } else {
    return DoAnything()
  }
  return nil
}
package fuga

import (
  "errors"
  "go-error-handling_pkg-errors/package/hoge"
)

func DoExciting(b bool) error {
  err := hoge.DoExciting(b)

  if err != nil {
    switch err.(type) {

    case *hoge.HogeSomethingError:
      return errors.New("error1 at fuga")

    case *hoge.HogeAnythingError:
      return errors.New("error2 at fuga")

    }
  }

  return nil
}
package main

import (
  "fmt"
  "os"

  "go-error-handling_pkg-errors/package/fuga"
)

func main() {
  if err := fuga.DoExciting(false); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }

  fmt.Println("main success")
}
出力
$ go run src/go-error-handling_pkg-errors/sample01/main.go 
error2 at fuga
exit status 1
困ること

mainでは、error2 at fuga と出力され、
エラーがあったことはわかるものの、hogeでエラーだったことがわかりません。

2. pkg/errorsでのエラーハンドリング(errors.Wrap)

そんな場合に pkg/errors でエラーハンドリングを行ってみます。

インストール

使う前にインストールしておきます。

pkg/errrosgo getなら以下のようにしてインストールできます。

go get -v github.com/pkg/errors

自分はglide環境なので、glide.yamlを以下のようにしてglide installしました。

package: go-error-handling_pkg-errors
import:
  - package: github.com/pkg/errors

サンプル

さきほどの fuga.methodpiyo.methodに置き換えて、
main -> piyo.method -> hoge.method と呼び出すようにします。

また、piyo.methodでは`pkg/errorsを使ってエラーハンドリングするようにしてみます。

コード
上記と同じです
package piyo

import (
  "go-error-handling_pkg-errors/package/hoge"

  "github.com/pkg/errors"
)

func DoExciting(b bool) error {
  err := hoge.DoExciting(b)

  if err != nil {
    switch err.(type) {

    case *hoge.HogeSomethingError:
      return errors.Wrap(err, "error1 at piyo")

    case *hoge.HogeAnythingError:
      return errors.Wrap(err, "error2 at piyo")

    }
  }

  return nil
}
package main

import (
  "fmt"
  "os"

  "go-error-handling_pkg-errors/package/piyo"
)

func main() {
  if err := piyo.DoExciting(false); err != nil {
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
  }

  fmt.Println("main success")
}
出力
$ go run src/go-error-handling_pkg-errors/sample02/main.go 
error2 at piyo: this is HogeAnythingError
exit status 1
結果

error2 at piyoに続いて、大元の: this is HogeAnythingErrorも表示されました。

3. pkg/errorsでのエラーハンドリング(errors.Cause)

errors.Causeというのもあり、
これを使うと上記の例でいうとmainで原因となるエラーの型ハンドリングが可能となります。

mainだけ変更した例をのせておきます。

コード
package main

import (
  "fmt"
  "os"

  "github.com/pkg/errors"

  "go-error-handling_pkg-errors/package/hoge"
  "go-error-handling_pkg-errors/package/piyo"
)

func main() {
  if err := piyo.DoExciting(false); err != nil {
    switch errors.Cause(err).(type) {
    case *hoge.HogeSomethingError:
      fmt.Fprintln(os.Stderr, "case1 at main:", err)
    case *hoge.HogeAnythingError:
      fmt.Fprintln(os.Stderr, "case1 at main:", err)
    }
  }

  fmt.Println("main success")
}
出力
$ go run src/go-error-handling_pkg-errors/sample03/main.go 
case1 at main: error2 at piyo: this is HogeAnythingError
main success

おわり

こちらの参考でも言われてますが、
たしかにあまり深くWarpしまくるのも、という気もするので結局悩ましいループになりそう\(^o^)/

参考