Golang 错误处理

102 阅读3分钟

常见处理

项目中比较常见的err处理方式如下:

package main

import "os"

func main() {
	f, err := os.Open("test.go")
	if err != nil {
		panic(err)
	}
	//这个一定要放到if下面,如果open执行失败,那么f有可能为nil,nil就没有close这个方法
	defer f.Close()
	//其他代码 懒得写

}

error类型

在Go中避免不了针对error类型来打交道,error底层就是一个interface

//代码位于 GOROOT/src/builtin/builtin.go L:307
type error interface {
	Error() string
}

自定义项目中的error常量

可以看到error就是一个interface,只要实现一下这个interface就可以在项目中做一些错误常量的定义

示例代码

package load

const ErrorOpenFile = ioError("open file error")

// 定义一个类型,实现error接口
type ioError string

// 逻辑很简单直接返回原因即可
func (e ioError) Error() string { return string(e) }

func ErrorCheck(err error) error {
	if err != nil {
		return ErrorOpenFile
	}

	return nil
}

调用

package main

import (
	"demo/load"
	"fmt"
)

func main() {
	fmt.Println(load.ErrorCheck(nil) == load.ErrorOpenFile)
	fmt.Println(load.ErrorCheck(fmt.Errorf("sss")) == load.ErrorOpenFile)
}

error链路跟踪

当项目中的代码层级比较多的时候,当前层不能处理当前错误,逐层向上反直到某一层可以对该错误进行处理

models层

package models

var dbConn *gorm.DB

const ErrorUserExist = userError("用户已存在")

// 定义一个用户的错误类型,实现error接口
type userError string

// 逻辑很简单直接返回原因即可
func (e userError) Error() string { return string(e) }

type User struct {
	Id       int64  `json:"id" gorm:"column:id;primaryKey"`
	Username string `json:"username" gorm:"column:username" binding:"required"`
	Password string `json:"password" gorm:"column:password"`
	Email    string `json:"email" gorm:"column:email"`
}

func (u User) TableName() string {
	return "user"
}

// gorm钩子函数,保存之前会调度一下这个方法
func (u *User) BeforeSave(tx *gorm.DB) (err error) {
	// 检测一下数据库表里面有没有这个用户
	if u.checkUserExistence(u.Id, u.Username) {
		return UserExist
	}

	return nil
}

func (u User) SaveUser(user User) (int64, error) {
	if err := dbConn.Save(&user).Error; err != nil {
		return 0, err
	}
	return user.Id, nil
}

func (u User) checkUserExistence(id int64, username string) bool {
	var user User
	err := dbConn.Where("id != ?", id).
		Where("username = ?", username).
		Find(&user).Error

	if err != nil || user.Id == 0 {
		return false
	}

	return true
}

调度层

package main

import (
	"demo/models"
	"errors"
	"fmt"
)

func main() {
	err := SaveUserToDb()
	if err != nil {
		if errors.Is(err, models.ErrorUserExist) {
			fmt.Println("用户名已经存在了")
		} else {
			fmt.Println("其他错误:", err)
		}
	}
}

func SaveUserToDb() error {
	_, err := (models.User{}).SaveUser(models.User{Id: 1, Username: "test"})
	if err != nil {
		return fmt.Errorf("save db err:%w", err)
	}

	// 假装这里还有点逻辑处理

	return nil
}

解读

SaveUserToDb这个函数是处理用户数据保存入库,main为我们的控制器层,当在处理数据库保存的时候发现dao层写数据库出现了错误,在SaveUserToDb这一层我们不知道怎么处理这个错误,可以使用fmt.Errorf %w将这个错误包一层向上返回%w错误处理文档,当逻辑逐步向上返回到控制器层时,发现当前层可以处理这个错误了,就可以具体辨别一下这个错误具体是什么,然后去做一些针对性的处理

error格式简化

示例代码

package main

import (
	"bufio"
	"fmt"
	"net"
)

func main() {
	demo("tcp", "127.0.0.1:6666")
}

func demo(network, address string) error {
	conn, err := net.Dial(network, address)
	if err != nil {
		return fmt.Errorf("demo %s %s err:%v", network, address, err)
	}

	_, err = conn.Write([]byte("ping"))
	if err != nil {
		return fmt.Errorf("demo %s %s err:%v", network, address, err)
	}

	r := bufio.NewReader(conn)
	status, err := r.ReadString('\n')
	if err != nil {
		return fmt.Errorf("demo %s %s err:%v", network, address, err)
	}

	if status == "ok" {
		_, err := conn.Write([]byte("hello"))
		if err != nil {
			return fmt.Errorf("demo %s %s err:%v", network, address, err)
		}
	}

	return nil
}

打印简化

package main

import (
	"bufio"
	"fmt"
	"net"
)

func main() {
	fmt.Println(demo("tcp", "127.0.0.1:6666"))
}

func demo(network, address string) (err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("demo %s %s err:%v", network, address, err)
		}
	}()

	conn, err := net.Dial(network, address)
	if err != nil {
		return
	}

	_, err = conn.Write([]byte("ping"))
	if err != nil {
		return
	}

	r := bufio.NewReader(conn)
	status, err := r.ReadString('\n')
	if err != nil {
		return
	}

	if status == "ok" {
		_, err = conn.Write([]byte("ping"))
		if err != nil {
			return
		}
	}

	return nil
}

参考资料:
github.com/golang/go/w…

www.ituring.com.cn/article/508…