大家好,我叫謝偉,是一名程式設計師。
近期我會持續更新內建庫的學習筆記,主要參考的是文件 godoc 和 內建庫的原始碼
本節的主題:error
Go 中的錯誤處理和別的語言不一樣,設計哲學也不一樣,常有開發者埋怨 Go 語言中的錯誤處理。
本節從內建庫的 error 出發,結合內建庫的常用的 錯誤處理手法,總結出適合專案的錯誤處理方式。
大綱
- 自己的常用的 error 錯誤處理方式
- 內建庫實現的 error
- 總結
自己的總結常用的 error 錯誤處理方式
1. 建立 error 型別的值
- errors.New()
- fmt.Errorf()
這兩個方法即可實現。
package main
import (
"errors"
"fmt"
"reflect"
)
func main() {
recordError := errors.New("record not found")
dbError := fmt.Errorf("%s", "db connet fail")
fmt.Println(recordError, reflect.TypeOf(recordError))
fmt.Println(dbError, reflect.TypeOf(dbError))
}
>>
record not found *errors.errorString
db connet fail *errors.errorString
複製程式碼
2. 自定義錯誤型別
- 實現 error 介面
type CodeError struct {
Code int
Message string
}
func (c CodeError) Error() string {
return fmt.Sprintf(" e.Code = %d, e.Message=%s", c.Code, c.Message)
}
func Result() error {
var codeError CodeError
codeError = CodeError{
Code: 400,
Message: "connect fail",
}
return codeError
}
func main(){
var codeError CodeError
var err error
codeError = CodeError{
Code: 404,
Message: "http status code error",
}
err = codeError
fmt.Println(err)
}
>>
e.Code = 404, e.Message=http status code error
複製程式碼
自己定義的錯誤型別,只要實現了 error 介面即可。
可以看下具體的 介面定義:
type error interface {
Error() string
}
複製程式碼
- errors.New 的具體實現
func New(text string) error {
return &errorString{text}
}
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
複製程式碼
即 定義了 errorString 結構體, 繫結了 Error 方法
- fmt.Errorf 的具體實現
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
複製程式碼
即 fmt.Errorf 呼叫的也是 errors.New 方法。
總結上文:
- 建立 error 存在兩種方法
- 底層 是一個結構體實現了 error 介面
- 自定義的錯誤型別,實現 Error 方法即可
內建庫實現的 error
錯誤很常見,是業務的一部分,那麼內建的庫在實現時也會遇到錯誤,看內建庫是如何實現的 error
1. strconv.Atoi
func main(){
number, err := strconv.Atoi("2992-121")
fmt.Println(number,err)
}
>>
0 strconv.Atoi: parsing "2992-121": invalid syntax
複製程式碼
具體的底層實現是:
type NumError struct {
Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat)
Num string // the input
Err error // the reason the conversion failed (e.g. ErrRange, ErrSyntax, etc.)
}
func (e *NumError) Error() string {
return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error()
}
複製程式碼
定義各種錯誤型別:
var ErrRange = errors.New("value out of range")
var ErrSyntax = errors.New("invalid syntax")
func syntaxError(fn, str string) *NumError {
return &NumError{fn, str, ErrSyntax}
}
func rangeError(fn, str string) *NumError {
return &NumError{fn, str, ErrRange}
}
func baseError(fn, str string, base int) *NumError {
return &NumError{fn, str, errors.New("invalid base " + Itoa(base))}
}
func bitSizeError(fn, str string, bitSize int) *NumError {
return &NumError{fn, str, errors.New("invalid bit size " + Itoa(bitSize))}
}
複製程式碼
即:核心是定義了一個結構體,實現了 error 介面
2. net/http
func main(){
response, err := http.Get("https://space.bilibili.com/10056291/#/")
fmt.Println(response, err)
}
複製程式碼
底層的錯誤型別是:
type Error struct {
Op string
URL string
Err error
}
func (e *Error) Error() string { return e.Op + " " + e.URL + ": " + e.Err.Error() }
複製程式碼
即定義了一個結構體,實現的 error 介面。
鑑於對內建庫的底層的 錯誤型別的閱讀,修改自己設定錯誤型別的方法:如下
type ExampleError struct {
Err error
Code int
Message string
}
func (e *ExampleError) Error() string {
return fmt.Sprintf("e.Code = %d e.Err = %s e.Message = %s", e.Code, e.Err.Error(), e.Message)
}
func ExampleResult() error {
return &ExampleError{
Err: errors.New("what the fucking life"),
Code: 502,
Message: "what the fucking life",
}
}
複製程式碼
總結
- 建立 error 值存在兩種方法
- 自己定義的錯誤型別,只需實現 Error 方法,實現 error 介面即可
其他:對錯誤的處理還有這些建議
- 每個庫,可以在庫檔案的開頭宣告常見的錯誤
var ErrorIndexOut = errors.New("index out of range")
var ErrorFuck = errors.New("fucking life")
複製程式碼
- 錯誤型別的變數可以帶上
Err
或者Error
標示 - 建議統一處理錯誤型別,即,完整的專案在宣告自己的庫處理模組,而不是隨意的在專案內建立 error
<完>