golangにおける独自エラー定義方法とエラーごとの処理分岐

内容
====================
- 独自エラーを生成する方法
- 受け取り側で処理分岐できるエラーを作るには？

golangにおけるエラー
====================

- 下記インターフェースを実装した構造体
- `Error()` は、エラーメッセージとしての文字列を出力する

```go
type error interface {
	Error() string
}
```

独自エラーを生成する方法
====================

## ① `errors.New(text string) error`
- エラーメッセージを文字列で指定
- 実体の構造体は、`errors` パッケージで定義されている `errorString` 型

## ② `fmt.Errorf(format string, a ...interface{}) error`
- `fmt.Sprintf` と同様、フォーマットでエラーメッセージを指定
- 内部的には `errors.New(fmt.Sprintf(format, a))` を呼び出しているだけ
	- `errors.New` と同じく `errorString` 型

## ③ `error` インターフェースを実装した独自構造体を定義する
- 定義した独自構造体のインスタンスを生成してエラーとして使用する
- ①②との違いは、以下
	- **型を変えられる** : 型アサーションによりエラー処理を分岐できる
	- **メッセージ以外のプロパティを持たせられる** : エラーにステータスを持たせられる

## （参考） `pkg/errors` パッケージ
- `errors.New` と `errors.Errorf` メソッドが用意されており、①②を同一パッケージで書ける
- 内部的な処理は微妙に違うが、統一感がでるので個人的にはこちらが好き


受け取り側で処理分岐できるエラーを作るには？
====================

## ① `Error()` で出力される文字列を比較する 【アンチパターン】
- `error` としてはエラーメッセージ（文字列）を返すメソッドがあるだけ
- エラーメッセージを比較すれば、所望のエラーかどうか確認することができる

```go
package main

import (
    "errors"
    "log"
)

var (
	ErrMsg1 = "my error 1"
	ErrMsg2 = "my error 2"
)

func main() {
    if err := doError(); err != nil {
    	switch err.Error() {
    	case ErrMsg1:
    		log.Fatalf(ErrMsg1)
    	case ErrMsg2:
    		log.Fatalf(ErrMsg2)
    	}
    }
}

func doError(i int) error {
	switch i {
	case 1:
		return errors.New(ErrMsg1)
	case 2:
		return errors.New(ErrMsg2)
	}
	return nil
}
```


## ② エラーインスタンスを比較
- パッケージのトップレベルにエラーインスタンスを作っておく
	- エラー生成側：事前生成したエラーインスタンスを返却する
	- エラー処理側：受け取ったエラーと事前生成したエラーインスタンスを比較する

```go
package main

import (
    "errors"
    "log"
)

var (
    ErrMyError1 = errors.New("my error 1")
    ErrMyError2 = errors.New("my error 2")
)

func main() {
    if err := doError(); err != nil {
    	switch err {
    	case ErrMyError1:
    		log.Fatalf("%#v", ErrMyError1)
    	case ErrMyError2:
    		log.Fatalf("%#v", ErrMyError2)
    	}
    }
}

func doError(i int) error {
	switch i {
	case 1:
		return ErrMyError1
	case 2:
		return ErrMyError2
	}
	return nil
}
```

## ③ 独自定義したエラー構造体の型を比較
- `error` インターフェースを実装した独自構造体を定義する
	- エラー生成側：定義した構造体を生成して返却する
	- エラー処理側：受け取ったエラーインスタンスに型アサーションを実施する

```go
package main

import (
    "errors"
    "log"
)

type MyError1 struct {}

func (e *MyError1) Error() string {
	return "my error 1"
}

type MyError2 struct {}

func (e *MyError2) Error() string {
	return "my error 2"
}

func main() {
    if err := doError(); err != nil {
    	switch e := err.(type) {
    	case *MyError1:
    		log.Fatalf("%#v", e)
    	case *MyError2:
    		log.Fatalf("%#v", e)
    	}
    }
}

func doError(i int) error {
	switch i {
	case 1:
		return &MyError1{}
	case 2:
		return &MyError2{}
	}
	return nil
}
```

## ②と③の使い分け
- 処理の分岐だけできれば良い場合
	- ②を使い、事前定義したインスタンスと比較する

- 処理を分岐した上で、更に状態を取りたい場合
	- ③を使い、アサーションして構造体ごとの処理を呼ぶ

```go
package main

import (
    "errors"
    "log"
)

type MyError1 struct {
	Status int
}

func (e *MyError1) Error() string {
	return "my error 1"
}

func main() {
    if err := doError(); err != nil {
    	switch e := err.(type) {
    	case *MyError1:
    		// さらにこういった分岐をしたい場合は型アサーションにする　
    		switch e.Status {
    		case 404:
	    		log.Fatalf("%#v, %#v", e, 404)
    		case 409:
	    		log.Fatalf("%#v, %#v", e, 409)
    		}
    	}
    }
}

func doError(i int) error {
	return &MyError1{Status: 404}
}
```

## ④ 番外編 : Assert errors for behaviour, not type
[Inspecting errors | Dave Cheney](https://dave.cheney.net/2014/12/24/inspecting-errors) より

> Assert errors for behaviour, not type

- エラーをチェックするメソッド `IsXXX(err error) bool` を定義する方法
- 上記②③の方法の場合、呼び出し元が特定の変数/型に依存することになる
- この方法であればインターフェースへの依存で済む


```go
package main

import (
    "errors"
    "log"
)

type temporary interface {
	Temporary() bool
}

func (e *MyError1) Error() string {
	return "my error 1"
}

func (e *MyError1) Temporary() bool {
	return true
}

type MyError2 struct {}

func (e *MyError2) Error() string {
	return "my error 2"
}

func IsTemporary(err error) bool {
   te, ok := err.(temporary)
   return ok && te.Temporary()
}
		
func main() {
    if err := doError(); err != nil {
    	if IsTemporary(err) {
    		log.Fatalf("%#v", err)
    	}
    }
}

func doError(i int) error {
	switch i {
	case 1:
		return &MyError1
	case 2:
		return &MyError2
	}
	return nil
}
```

（おまけ） errorを作れるパッケージたち
====================

### errors
- `New(message string) error`

### fmt
- `Errorf(format string, args ...interface{}) error`

### pkg/errors
#### エラーの新規作成
- `New(message string) error`
	- `errors.New` とほぼ同じ
- `Errorf(format string, args ...interface{}) error`
	- `fmt.Errorf` をほぼ同じ

#### エラーのラッピング
- `WithMessage(err error, message string) error`
	- 元の `error` をラップし、メッセージを付与
- `WithStack(err error) error`
	- 元の `error` をラップし、スタックトレースを付与
- `Wrap(err error, message string) error`
- `Wrapf(err error, format string, args ...interface{}) error`
	- 元の `error` をラップし、メッセージとスタックトレースを付与
- `Cause(err error) error`
	- ラップされた `error` を取り出す

（おまけ） panicを起こせるパッケージたち
====================

### ビルトイン関数
- `panic(message string)`

### log
#### panic系
- `Panic(v ...interface{})`
	- Panic is equivalent to Print() followed by a call to panic().
- `Panicln(v ...interface{})`
	- Panicln is equivalent to Println() followed by a call to panic().
- `Panicf(format string, v ...interface{})`
	- Panicf is equivalent to Printf() followed by a call to panic().

#### （参考）fatal系
- `Fatal(v ...interface{})`
	- Fatal is equivalent to Print() followed by a call to os.Exit(1).
- `Fatalln(v ...interface{})`
	- Fatalln is equivalent to Println() followed by a call to os.Exit(1).
- `Fatalf(format string, v ...interface{})`
	- Fatalf is equivalent to Printf() followed by a call to os.Exit(1).


参考
====================
- [Go言語のエラーハンドリングについて - Qiita](https://qiita.com/nayuneko/items/3c0b3c0de9e8b27c9548)
- [【go】golangのエラー処理メモ - ②. 例外はないがエラーハンドリングはできるよ（インスタンスや型でハンドリング） - tweeeetyのぶろぐ的めも](http://tweeeety.hateblo.jp/entry/2017/08/17/022500)
- [Javaの検査例外は、呼び出し側の責任でない異常系 - Qiita](https://qiita.com/yuba/items/d41290eca726559cd743)
- [Go言語でエラーを書くときに気をつけること - taknb2nchのメモ](http://d.hatena.ne.jp/taknb2nch/20140307/1394169432)
- [DSAS開発者の部屋:Go ではエラーを文字列比較する？という話について](http://dsas.blog.klab.org/archives/go-errors.html)
- [loggingについて話そう - Qiita](https://qiita.com/methane/items/cedbf546ae2db8a63c3d)
- [Gocon Spring 2016](https://dave.cheney.net/paste/gocon-spring-2016.pdf)
- [errors - The Go Programming Language](https://golang.org/pkg/errors/)
- [fmt - The Go Programming Language](https://golang.org/pkg/fmt/)
- [log - The Go Programming Language](https://golang.org/pkg/log/)
- [errors - GoDoc](https://godoc.org/github.com/pkg/errors)