什么是error?
在日常的代码开发中,错误处理和自定义错误都是难免要遇到的。Java中提供了Exception和Error,Python提供了Exception和Error。Go也提供了错误处理:error。比如: Go的文件I/O接口:
func Open(name string) (file *File, err error)
如果os.Open
在打开文件时遇到错误,那么返回的err的值为非空。
Go代码使用error值来表示一个异常的状态。
如何处理error?
如果我们调用os.Open
返回的err不为空,那么我们应该如何处理?
f, err := os.Open("file.txt")
if err != nil {
log.Fatal("error occurred when oopen file file.txt", err)
}
error是什么?
在Go中,error
也是一种接口类型,这个接口类型定义如下:
type error interface {
Error() string
}
Go中实现了error接口,其中最通用的error实现是,源代码链接:errors.go:
// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
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
}
快速创建一个error
根据上面的定义,我们可以使用errors.New("")快速创建一个error。比如:
file, err := os.Open("file.txt")
if err != nil {
return errors.New("file cannot be open")
}
也可以使用fmt包的Errorf函数,比如:
err := fmt.Errorf("user %q (id %d) not found", name, id)
如何自定义error?
error是一个接口类型,根据Go的约定:只要一个类型实现了接口的所有方法,那么这个类型实现了这个接口。
方式1
type NegativeSqrtError float64
func (f NegativeSqrtError) Error() string {
return fmt.Sprintf("math: square root of negative number %g", float64(f))
}
方式2
type SyntaxError struct {
msg string // description of error
Offset int64 // error occurred after reading Offset bytes
}
func (e *SyntaxError) Error() string { return e.msg }
方式3
实际业务开发中一般我们需要自定义错误,比如:当有错误发生时,我们会返回一个自定的错误,这个错误包含了接口返回时的所需字段信息。
实际业务场景:登录。用户输入手机号和短信验证码登录,如果手机号或者验证码不正确,我们可能会返回自定自定义错误:PhoneOrChaptaIncorrectError,同时我们接口需要返回以下json:
{
"bizCode":"",
"msg":"phone or chapta is incorrect"
}
那我们自定义异常可能会这样设计:
type PhoneOrChaptaIncorrectError struct {
BizCode string
Msg string
}
func NewPhoneOrChaptaIncorrectError() *PhoneOrChaptaIncorrectError {
return &PhoneOrChaptaIncorrectError{
BizCode: "1234",
Msg: "phone or chapta is incorrect",
}
}
func (e *PhoneOrChaptaIncorrectError) Error() string {
return e.Msg
}
或者自定义一个通用异常,然后各种new:
// 定义一个包含业务代码和错误消息的自定义错误类型
type BizError struct {
BizCode string
Msg string
}
// 实现 error 接口的 Error 方法
func (e *BizError) Error() string {
return fmt.Sprintf("%s: %s", e.BizCode, e.Msg)
}
// 定义一个可以创建具体BizError实例的功能函数
func NewBizError(bizCode, msg string) *BizError {
return &BizError{
BizCode: bizCode,
Msg: msg,
}
}
又或者按照Go推荐方式:
先定义一个自己的struct,
type appError struct {
Error error
Message string
Code int
}
然后让函数或者方法返回这个struct:
func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
c := appengine.NewContext(r)
key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
record := new(Record)
if err := datastore.Get(c, key, record); err != nil {
return &appError{err, "Record not found", 404}
}
if err := viewTemplate.Execute(w, record); err != nil {
return &appError{err, "Can't display record", 500}
}
return nil
}
最后获得自定义的错误消息:
func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if e := fn(w, r); e != nil { // e is *appError, not os.Error.
c := appengine.NewContext(r)
c.Errorf("%v", e.Error)
http.Error(w, e.Message, e.Code)
}
}
如何判断是哪个error?
我们业务中可能有判断是哪个错误的需求,Go也提供了这样的方法:errors.Is
func Is(err, target error) bool
if _, err := os.Open("non-existing"); err != nil {
if errors.Is(err, fs.ErrNotExist) {
fmt.Println("file does not exist")
} else {
fmt.Println(err)
}
}
什么是error的层次结构
在 Go 1.13 版本中,标准库引入了几个新的错误处理功能,包括 %w 用于 fmt.Errorf 来包装错误,以及 errors.Unwrap 函数用于解开错误层次。
import (
"errors"
"fmt"
"log"
)
// 底层错误
var ErrBase = errors.New("base error")
// 创建错误树的中间层
func middleLayer() error {
return fmt.Errorf("middle layer: %w", ErrBase)
}
// 创建最顶层的错误
func topLevel() error {
return fmt.Errorf("top level: %w", middleLayer())
}
func main() {
err := topLevel()
if err != nil {
fmt.Println(err) // 输出完整的错误链条信息
// 解开错误树以获取原始的错误
if errors.Is(err, ErrBase) {
fmt.Println("An error occurred at the base level")
}
// 获得错误的直接源头
wrappedErr := errors.Unwrap(err)
fmt.Println(wrappedErr) // 输出中间层的错误
// 递归解开所有层次的错误
for err != nil {
fmt.Println(err)
err = errors.Unwrap(err)
}
}
}
总结
- Go中的异常处理是返回error接口类型,程序员需要手动判断error是否为空
- 快速创建error使用errors.New或者fmt.Errorf(如果你想格式化一些变量)
- 自定义异常可以自己实现error接口类型或者使用组合方式实现一个自定义struct
- Go的error是有层次结构的,这个类似于异常的继承,Go提供了fmt.Errorf,errors.Is和errors.As, errors.Unwrap来支持层次结构
- Go error的设计哲学显式错误检查,有点类似于Java的checked exception。