こんにちは。Tomoyuki(@tomoyuki65)です。
実務ではシステムを運用していく必要がありますが、そういった場合は適切なエラーハンドリングをしながらプログラムを書く必要があります。
この記事では、Go言語(Golang)の基本的なエラーハンドリングについて解説します。
Go言語(Golang)の基本的なエラーハンドリングについて
まずGo言語(Golang)の基本的なエラーハンドリングについて、例えば関数の戻り値に「error」型を返すことがあります。
package main
import (
"errors"
"fmt"
)
func f1() error {
err := errors.New("予期せぬエラー")
return err
}
func main() {
// 関数f1を実行
err := f1()
// エラーハンドリング
if err != nil {
fmt.Printf("f1の処理でエラー: %v", err)
}
}
※正常終了の場合、error型の戻り値にはnilを返します。そして呼び出し元ではnilチェックをします。
実行結果
f1の処理でエラー: 予期せぬエラー
error型の値を定義する際は、主に以下の二種類を利用します。動的なエラーメッセージを定義したい場合は「fmt.Errorf()」を使います。
// errorsパッケージのNewメソッドを利用する場合
err1 := errors.New("予期せぬエラー")
// fmtパッケージのErrorfメソッドを利用する場合
err2 := fmt.Errorf("%s", "予期せぬエラー")
「fmt.Errorf()」については、エラーをラップしてエラーメッセージを作る際にも利用します。
err1 := errors.New("予期せぬエラー")
err2 := fmt.Errorf("エラー:%w", err1)
※error型の値をラップしたい場合、「%w」を使います。
カスタムのエラー用変数を定義して利用した上で、これらを駆使することで、関数の呼び出し元でカスタムエラーかどうかのチェックが可能です。
package main
import (
"errors"
"fmt"
)
// カスタムエラー用変数を定義
var CustomErr = errors.New("カスタムエラー")
func f1() error {
// カスタムエラーを利用してエラーを作成
err := CustomErr
return err
}
func f2() error {
// 関数f1を実行
err := f1()
// エラーハンドリング
if err != nil {
return fmt.Errorf("f1の処理でエラー: %w", err)
}
return nil
}
func main() {
// 関数f2を実行
err := f2()
// CustomErrかどうかをチェック
if errors.Is(err, CustomErr) {
fmt.Printf("カスタムエラーが発生しました。[%v]", err)
} else {
fmt.Printf("その他のエラーが発生しました。[%v]", err)
}
}
実行結果
カスタムエラーが発生しました。[f1の処理でエラー: カスタムエラー]
この例ではCustomErrかどうかをチェックに「errors.Is」を利用していますが、動的なエラーメッセージのカスタムエラーを返すようにして、そのカスタムエラーかどうかをチェックしたい場合もあると思います。
その場合、以下のようにカスタムエラーを構造体として定義することで実現可能で、カスタムエラーかどうかのチェックには「errors.As」を利用します。
package main
import (
"errors"
"fmt"
)
// カスタムエラーの構造体を定義
type CustomErr struct {
// プロパティにint型のidを持つ
id int
}
// error型に合わせてstring型の戻り値を持つError()のメソッドを定義する
func (e *CustomErr) Error() string {
return fmt.Sprintf("カスタムエラー[id=%d]", e.id)
}
func f1() error {
// カスタムエラーを定義
err := &CustomErr{id: 10}
return err
}
func f2() error {
// 関数f1を実行
err := f1()
// エラーハンドリング
if err != nil {
return fmt.Errorf("f1の処理でエラー: %w", err)
}
return nil
}
func main() {
// 関数f2を実行
err := f2()
// チェック用にカスタムエラーのポインタ変数を定義
var cErr *CustomErr
// CustomErrかどうかをチェック
if errors.As(err, &cErr) {
fmt.Printf("カスタムエラー(構造体)が発生しました。[%v]", err)
} else {
fmt.Printf("その他のエラーが発生しました。[%v]", err)
}
}
※errors.Asの場合は型チェックをしています。
実行結果
カスタムエラー(構造体)が発生しました。[f1の処理でエラー: カスタムエラー[id=10]]
このようにカスタムエラーは構造体を使って定義することもできますが、動的なエラーメッセージを返したい場合や、後から動的なメッセージにプロパティを追加する可能性がある場合は、構造体でカスタムエラーを定義した方がいいです。
カスタムエラーを定義すべき場所について
例えば実務ではDDD(ドメイン駆動設計)の構成でAPIを作ることが多いですが、その場合はユースケース層からドメイン層やリポジトリ層などを呼び出してビジネスロジックを処理します。
その場合は一番深いところの処理がドメイン層やリポジトリ層になり、ユースケース層にてドメインやリポジトリの処理結果によってエラーハンドリングが必要なる可能性を考慮すると、ドメイン層やリポジトリ層でerror型を返すような場合はカスタムエラーを利用しておいた方がいいです。
その際にカスタムエラーを「errors.New()」で定義するのか「構造体」で定義するのかはものによるので、どちらにすべきかはしっかり検討して実装するようにして下さい。
最後に
今回はGo言語(Golang)の基本的なエラーハンドリングについて解説しました。
実務においては運用面を考慮し、ドメイン層やリポジトリ層などでカスタムエラーを定義して利用すべきケースがあると思うので、実務でAPIを作る際はぜひ参考にしてみて下さい。
コメント