見出し画像

Errors, Panic/Recover | Go日記: 『Learning Go』

Chapter 8 Errors をざっと読んでみた。寝る前に Kindle デバイスで読んだ。気になるところをマークしておいたので、PC で読み直しながらメモを取る。

Errors are Values

Go のエラーについて理解しようとするとき、次のことを心にとめておくと良いと思う。
 ・エラーは値。(多言語の try/exception などの特別な例外構造は忘れる)
 ・エラー error は interface である。
 ・(interface なので)エラーのゼロ値は nil である。

Since error is an interface, you can define your own errors that include additional information for logging or error handling.

Bodner, Jon. Learning Go (p.245). O'Reilly Media. Kindle 版.

自分で定義した型に Error() メソッドを実装すると error インターフェイスを満たすのでエラー値として使うことができる。

package main
import (
	"errors"
	"fmt"
)
type Status int
const (
	InvalidLogIn Status = iota + 1
	NotFound
)
/* 独自のエラー型を定義 */
type StatusErr struct {
	Status  Status
	Message string
}
/* Error() を定義して error インターフェイスを満たす */
func (se StatusErr) Error() string {
	return se.Message
}
/*
独自のエラー型を定義したとしても
関数の戻り値は error 型を指定。
こうすると、Caller が特定のエラー型に依存しなくて済む。
*/
func LoginAndGetData(uid, pwd, file string) ([]byte, error) {
	err := Login(uid, pwd)
	if err != nil {
		return nil, StatusErr{
			Status:  InvalidLogIn,
			Message: fmt.Sprintf("%v on %v", err, uid),
		}
	}
	data, err := getData(file)
	if err != nil {
		return nil, StatusErr{
			Status:  NotFound,
			Message: fmt.Sprintf("%v on %v", err, file),
		}
	}
	return data, nil
}
func Login(uid, pwd string) error {
	if uid == "1" && pwd == "100" {
		return nil
	} else {
		return errors.New("Invalid login uid")
	}
}
func getData(file string) ([]byte, error) {
	if file == "f" {
		return []byte("xxx"), nil
	} else {
		return nil, errors.New("File " + file + " not found")
	}
}
func main() {
	for _, u := range [][]string{{"1", "100", "f"}, {"0", "100", "f"}, {"1", "100", "g"}} {
		uid := u[0]
		pwd := u[1]
		file := u[2]
		data, err := LoginAndGetData(uid, pwd, file)
		fmt.Println(string(data), err)
	}
}
Even when you define your own custom error types, always use error as the return type for the error result. This allows you to return different types of errors from your function and allows callers of your function to choose not to depend on the specific error type.

独自のカスタムエラータイプを定義する場合でも、エラー結果の戻りタイプとして常に error を使用してください。 これにより、関数からさまざまなタイプのエラーを返すことができ、関数の呼び出し元は特定のエラータイプに依存しないことを選択できます。

Bodner, Jon. Learning Go (p.246). O'Reilly Media. Kindle 版.

また、エラーがなかった場合は nil を返すこと。カスタムエラータイプのゼロ値を返すと Caller のほうで意図してない挙動に見舞われる可能性がある。

学習メモ

エラーの Unwrap()、Is、As あたりの内容は未消化なのでそのうち戻る。

panic/recover

recover の例。

package main
import (
	"fmt"
)
func myDiv(x, y int) {
	defer func() {
		if v := recover(); v != nil {
			fmt.Println(v)
		}
	}()
	fmt.Printf("%d / %d = %d\n", x, y, x/y)
}
func main() {
	for i, y := range []int{1, 2, 0, 5} {
		x := i * 10
		myDiv(x, y)
	}
}

Output

0 / 1 = 0
10 / 2 = 5
runtime error: integer divide by zero
30 / 5 = 6
The reason we don’t rely on panic and recover is that recover doesn’t make clear what could fail. It just ensures that if something fails, we can print out a message and continue. Idiomatic Go favors code that explicitly outlines the possible failure conditions over shorter code that handles anything while saying nothing. 

panic と recover に依存しない理由は、recover では何が失敗する可能性があるかが明確にならないためです。 何かが失敗した場合に、メッセージを print して続行できるようにするだけです。 Idiomatic Go は、何も言わずに何かを処理する短いコードよりも、発生する可能性のある障害状態の概要を明示的に示すコードを優先します。

There is one situation where recover is recommended. If you are creating a library for third parties, do not let panics escape the boundaries of your public API. If a panic is possible, a public function should use a recover to convert the panic into an error, return it, and let the calling code decide what to do with them.

recover が推奨される状況が1つあります。 サードパーティ用のライブラリを作成している場合は、panic を Public API の境界から逃がさないでください。 panic が発生する可能性がある場合、panic 関数は recover を使用して panic を error に変換して返し、呼び出し元のコードに panic の処理方法を決定させる必要があります。

Bodner, Jon. Learning Go (p.260). O'Reilly Media. Kindle 版.

SN

この記事が気に入ったらサポートをしてみませんか?