エラーハンドリング - Go

最終更新日: 2026-07-25

TOP(About this memo)) > 一覧(Go) > エラーハンドリング

間違ったエラーハンドリング

if strings.Contains(err.Error(), "cannot unmarshal") {
    logger.Warn(c.RequestID, "Invalid field format:%s", err)
    return ErrRequestFieldFormat
}

panic, recover

panic

func f1() {
	defer print("f1 defer\n") // deferは呼ばれる。
	panic("f1 panic!!!!")
	print("not execute!!!") // 実行されない
}
func f2() {
	defer print("f2 defer\n") //呼び出し元のdeferも呼ばれる。
	f1()
}
func main() {
	f2()
	print("not execute!!!") // 実行されない
}
// f1 defer
// f2 defer
// panic: f1 panic!!!!

recover

recover() は関数内部で実行されることで初めてpanicを受け取ってくれる

func main() {
	recover()
	panic("パニックが回収されませんでした")
}
func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("パニックが回収されました")
		}
	}()
	panic("パニックが回収されませんでした")
}

panic時に戻り値はデフォルト値になる。

func main() {
	s := f()
	fmt.Printf("%#v\n", s)// <nil>になる。
}
func f() error {
	var r interface{}
	defer func() {
		r = recover()
		if r != nil {
			println(r.(string))
		}
	}()
	if true {// 分岐を挟んでいるのは、go vet による nerver reached codeの回避のため。
		panic("fff")
	}
	return errors.New("aaa") // ここは実行されない。
}

error, errors(Wrap, Is, As)

Goのエラー周りの参考

interface

// /usr/local/go/src/builtin/builtin.go
type error interface {
	Error() string
}

errors.New

// /usr/local/go/src/errors/errors.go
func New(text string) error {
	return &errorString{text}
}

type errorString struct {
	s string
}

Goのエラーの返しには暗黙のルールがある

エラーのラップ

ラップ方法

errors.Is, errors.As

errors.Is(err, target error) bool

errors.As(err error, target any) bool

なんとなくの使い方。

import (
	"encoding/json"
	"errors"
	"strings"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
)

func Ptr[T any](a T) *T {
	return &a
}

func TestError(t *testing.T) {
	t.Run("assert_error", func(t *testing.T) {
		//errors.IsはDeep equalではない。したがってこれはfalseになる
		assert.Equal(t, errors.Is(errors.New(""), errors.New("")), false)
		a := errors.New("")
		assert.Equal(t, errors.Is(a, a), true) // これはtrue
		// AssertFalse(t, errors.As(errors.New(""), Ptr(errors.New("")))) これだとanalyzerのerrorになる。(第二引数がerror型は駄目らしい)

		// Unmarshalで発生するエラーの判定の例
		assert.Equal(t, errors.As(json.Unmarshal([]byte("{"), &struct{}{}), Ptr(&json.SyntaxError{})), true)// json.SyntaxErrorはポインタレシーバーのため、&json.SyntaxError{}を渡す必要があり、errors.Asの第二引数targetはerror型へのポインタである必要があるため、Ptr(&json.SyntaxError{})を渡す。
		assert.Equal(t, errors.As(json.Unmarshal([]byte(`{"id":"a"}`), &struct {
			Id int `json:"id"`
		}{}), Ptr(&json.UnmarshalTypeError{})), true)
		assert.Equal(t, errors.As(json.Unmarshal([]byte(`{"id":"a"}`), (*int)(nil)), Ptr(&json.InvalidUnmarshalError{})), true)

		// uuidの実装はエラーの定義がないため、errros.Isで比較できないため文字列で比較する必要がある。
		strings.Contains(json.Unmarshal([]byte(`{"id":"a"}`), &struct {
			Id uuid.UUID `json:"id"`
		}{}).Error(), "invalid UUID length")
		assert.Equal(t, json.Unmarshal([]byte(`{"id":"g0000000-0000-0000-0000-000000000000"}`), &struct {
			Id uuid.UUID `json:"id"`
		}{}).Error(), "invalid UUID format")
	})
}