golang Error interfaceのレシーバーはポインタ型にしよう
前提
Error() stringを実装したstructはError interfaceとして扱える
fmt.Printlnの内部では引数に渡されたオブジェクトがerror interface型であれば、オブジェクト.Errorメソッドを呼び出し、その結果を出力してくれる
code: golang
package main
import (
"fmt"
)
type APIError struct {
StatusCode int
}
func PostRequest() error {
// dosomething
return APIError{StatusCode: 400}
}
func (a APIError) Error() string {
return fmt.Sprintf("Error! StatusCode is %v", a.StatusCode)
}
func main() {
err := PostRequest()
if err != nil {
fmt.Println(err)
// Error! StatusCode is 400
}
}
errの中身を比較する場合、Errorメソッドのレシーバーが値型の場合、思わぬ結果になり得る
例えばAPIErrorを持つ変数をmainメソッドの中で定義し、PostRequest()の戻り値と比較した場合、trueとなる
code: golang
func main() {
errVar := APIError{StatusCode: 400}
err := PostRequest()
if err != nil {
fmt.Println(err)
// Error! StatusCode is 400
if err == errVar {
fmt.Println("同じです")
}
}
}
PostRequestが返すエラーと、PostRequestの外で定義したエラーが同じ扱いになると、バグが発生するかもしれない
これを防ぐために、Errorメソッドのレシーバーはポインタ型とするのが良いとされている
code: golang
package main
import (
"fmt"
)
type APIError struct {
StatusCode int
}
func PostRequest() error {
// dosomething
return &APIError{StatusCode: 400}
}
// レシーバーをポインタ型に
func (a *APIError) Error() string {
return fmt.Sprintf("Error! StatusCode is %v", a.StatusCode)
}
func main() {
errVar := &APIError{StatusCode: 400}
err := PostRequest()
if err != nil {
fmt.Println(err)
// Error! StatusCode is 400
if err == errVar {
fmt.Println("同じエラーです")
} else {
fmt.Println("異なるエラーです")
}
}
}
// 異なるエラーです
ポインタ型にしたことで、err同士の400エラーか..?の検証ができなくなった
以下のようにerrを*APIErrorに型アサーションしStatusCodeと数値を比較することで同等の処理ができる
code: golang
func main() {
err := PostRequest()
if err != nil {
fmt.Println(err)
// Error! StatusCode is 400
if err.(&APIError).StatusCode == 400 {
fmt.Println("同じエラーです")
} else {
fmt.Println("異なるエラーです")
}
}
}
// 同じエラーです