错误,延迟

60 阅读3分钟

一、错误

go语言哲学:面向错误的编程,你只要发现了错误就得解决它,而不对错误以外的值进行期待。在go语言中,用error接口表示错误,任何实现此接口的类型都当成一个错误

type error interface{
	Error ()stirng
}

1. 返回错误

errors.New

package main  
  
import (  
    "errors"  
    "fmt")  
  
// 定义User结构体,包含Name字段  
type User struct {  
    Name string  
}  
  
// 生成User对象的函数,返回*User和error  
func genUser() (*User, error) {  
    // 返回nil和自定义错误  
    return nil, errors.New("user == nil")  
}  
  
func main() {  
    // 调用genUser并同时接收返回的用户对象和错误  
    if u, err := genUser(); err != nil {  
       // 若有错误,打印错误信息  
       fmt.Println(err)  
    } else {  
       // 若无错误,打印用户姓名  
       fmt.Println(u.Name)  
    }  
}

代码先定义User结构体和生成用户的genUser函数(该函数固定返回错误),在main函数中调用genUser,通过if-else结构先判断是否有错误,有错误则打印错误,无错误则打印用户姓名,体现了 Go 语言 “面向错误编程” 的风格,优先处理错误,再处理正常逻辑

fmt.Errorf

package main  
  
import (
	"fmt"
	"errors"
)  
  
func main() {  
    money := 1  
    err := pay(money)  
    if err != nil {  
       fmt.Println(err)  
    } else {  
       fmt.Println("支付成功")  
    }  
}  
  
func pay(money int) error {  
    if money < 10 {  
       return fmt.Errorf("余额不足")  
    }  
    return nil  
}

2. 判断特定错误

errors.Is

func main() {  
    // 定义一个自定义错误  
    var customErr = errors.New("自定义错误")  
    // 模拟函数返回错误  
    err := doSomething()  
    if err != nil {  
       //使用 errors.Is 判断错误是否为 customErr
       if errors.Is(err, customErr) {  
       fmt.Println("捕获到自定义错误")  
       } else {  
       fmt.Println("未知错误:", err)  
       }  
    }  
}  
func doSomething() error {  
    // 这里返回我们定义的自定义错误  
    return errors.New("函数错误")  
}

自定义错误为customErr,写了一个函数返回错误为“函数错误”,errors.Is来判断这两个错误是不是一样

3. 封装与合并

两次都会打印 这两个错误相等,因为封装和解包后,原始错误的 “身份” 始终保持一致:

func main() {  
    originalErr := errors.New("原始错误")  
    // 用 %w 封装原始错误,生成新错误(保留原始错误的“身份”)  
    newError := fmt.Errorf("error: %w", originalErr)  
    // 用 errors.Is 判断“封装后的错误”是否等价于“原始错误”  
    if errors.Is(newError, originalErr) {  
       fmt.Println("这两个错误相等")  
    }  
    // 用 errors.Unwrap 解包,获取原始错误  
    originalErr1 := errors.Unwrap(newError)  
    // 再次判断解包后的错误是否和原始错误等价  
    if errors.Is(originalErr, originalErr1) {  
       fmt.Println("这两个错误相等")  
    }  
}

会依次打印 err is err1 和 err is err2,因为合并后的错误包含了这两个子错误:

func main() {  
    err1 := errors.New("err1")  
    err2 := errors.New("err2")  
    // 合并多个错误为一个错误  
    err := errors.Join(err1, err2)  
    // 判断合并后的错误是否包含 err1
    if errors.Is(err, err1) {  
       fmt.Println("err is err1")  
    }  
    // 判断合并后的错误是否包含 err2    
    if errors.Is(err, err2) {  
       fmt.Println("err is err2")  
    }  
}

4. 程序崩溃:panic

panic比较特殊,属于中断程序。使用场景:当程序不能恢复时,但是程序的崩溃是不可接受的,我们需要去预料它

捕获

捕获panic前,执行defer延迟操作,使用recover执行panic指令

二、延迟调用

关键字deferdefer后面跟的函数叫延迟函数,一般在return前或者在panic前才执行

image.png

后进先出

func main() {  
  
    defer fmt.Println(1)  
    defer fmt.Println(2)  
    defer fmt.Println(3)  
}

image.png