分享一個在go tour上看到的練習題,練習裡要求使用者自己定義一個錯誤型別,實現error
介面,函式在引數不滿足條件的時候返回自定義的錯誤型別的值。練習中特別提示使用者不要在實現的Error
方法裡直接使用fmt.Sprint(e)
以避免造成程式記憶體溢位。
下面貼一下具體的練習題
Practice
從之前的練習中複製 Sqrt
函式,修改它使其返回 error
值。
Sqrt
接受到一個負數時,應當返回一個非 nil 的錯誤值。複數同樣也不被支援。
建立一個新的型別
type ErrNegativeSqrt float64
併為其實現
func (e ErrNegativeSqrt) Error() string
方法使其擁有 error
值,透過 ErrNegativeSqrt(-2).Error()
呼叫該方法應返回 "cannot Sqrt negative number: -2"
。
注意: 在 Error
方法內呼叫 fmt.Sprint(e)
會讓程式陷入死迴圈。可以透過先轉換 e
來避免這個問題:fmt.Sprint(float64(e))
。這是為什麼呢?
修改 Sqrt
函式,使其接受一個負數時,返回 ErrNegativeSqrt
值。
Solution
這裡只為敘述返回error的情況,所以請忽略Sqrt函式的功能實現。
package main
import (
"fmt"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
// 這裡直接使用e值會記憶體溢位
return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}
func Sqrt(x float64) (float64, error) {
if x < 0 {
err := ErrNegativeSqrt(x)
return 0, err
}
return 0, nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
接下來探究一下為什麼在練習中把值e
先轉換為float64型別後程式就不會再記憶體溢位。
fmt.Sprint(e)
將呼叫e.Error()
將e
轉換為字串。如果Error()
方法呼叫fmt.Sprint(e)
,則程式將遞迴直到記憶體溢位。可以透過將e
轉換成一個非錯誤型別(未實現Error介面)的值來避免這種情況。
實際上在Error
方法中把error
值直接傳遞給fmt
包中Print相關的函式都會導致無限迴圈。原因可以在fmt包的原始碼中找到。
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting wasString and handled, and deferring catchPanic,
// must happen before calling the method.
switch v := p.field.(type) {
case error:
wasString = false
handled = true
defer p.catchPanic(p.field, verb)
// 這裡呼叫了Error方法
p.printField(v.Error(), verb, plus, false, depth)
return
透過連結可以在Github上看到這塊詳細的原始碼 https://github.com/golang/go/blob/2ed57a8c...
這個練習感覺還是給開發者提示了一個非常隱蔽的坑,感興趣的可以去go tour上的這個練習題自己試驗一下。
本作品採用《CC 協議》,轉載必須註明作者和本文連結