前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang错误处理笔记

golang错误处理笔记

原创
作者头像
挖坑工程师
修改2022-12-01 23:07:49
5100
修改2022-12-01 23:07:49
举报
文章被收录于专栏:个人学习摘要个人学习摘要

概念

Go语言中,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果,发生异常可能表示程序中存在 BUG 或发生了其它不可控的问题。

错误

错误用error接口类型表示,我们可以通过实现该接口来自定义异常信息,并通过Error()方法来获得字符串类型的错误信息。

自定义错误

代码语言:go
复制
package main

import (
	"fmt"
)

type MyError struct {
	Code    uint32
	Message string
}

func NewMyError(code uint32, msg string) error {
	return &MyError{
		Code:    code,
		Message: msg,
	}
}

// GetCode 获取错误码
func (e *MyError) GetCode() uint32 {
	return e.Code
}

// GetMessage 获取错误信息
func (e *MyError) GetMessage() string {
	return e.Message
}

// MyError 实现了 Error() 方法的对象都可以
func (e *MyError) Error() string {
	return fmt.Sprintf("%d-%s", e.Code, e.Message)
}

func NameCheck(name string) (bool, error) {
	if name == "" {
		return false, NewMyError(100, "name is empty")
	}
	return true, nil
}

func main() {
	_, err := NameCheck("")
	if err != nil {
		// 判断是否自定义错误类型,如果是自定义类型则输出code:message
		e, ok := err.(*MyError)
		if ok && e != nil {
			fmt.Println(e.GetCode(), ":", e.GetMessage())
		} else {
			fmt.Println(err)
		}
	} else {
		fmt.Println("name is ok")
	}
}

// out: 100 : name is empty

错误包装

代码语言:go
复制
package main

import (
	"errors"
	"fmt"
)

func NameCheck(name string) (bool, error) {
	if name == "" {
		return false, errors.New("第一层错误")
	}
	return true, nil
}

func main() {
	_, err := NameCheck("")
	if err != nil {
	    // 使用fmt.Errorf("xxxx %w", err)来进行错误wrap
		err = fmt.Errorf("第二层错误: %w", err)
		
		// Unwrap 一次只解一层错误,所以可以通过遍历来判断一共wrap几层
		for ; err != nil; err = errors.Unwrap(err) {
			fmt.Println(err)
		}
	} else {
		fmt.Println("name is ok")
	}
}
// out:
// 第二层错误: 第一层错误
// 第一层错误

错误判断(Is)

代码语言:txt
复制
// errors.Is(err, target error) bool
// 如果err和target是同一个,那么返回true
// 如果err是一个wraperror,target也包含在这个嵌套error链中的话,那么也返回true。

package main

import (
	"errors"
	"fmt"
)

type MyError struct {
	msg string
	err error
}

func (e *MyError) Error() string {
	return e.msg
}

func (e *MyError) Unwrap() error {
	return e.err
}

func main() {
	e1 := &MyError{"第1层错误", nil}
	e2 := &MyError{"第2层错误", e1}
	e3 := &MyError{"第3层错误", e2}
	err := &MyError{"第3层错误", e2}

	// err == err
	fmt.Println("is err:", errors.Is(err, err))
	// err 包装了e1, 返回true
	fmt.Println("is e1:", errors.Is(err, e1))
	// err 包装了e2, 返回true
	fmt.Println("is e2:", errors.Is(err, e2))
	// err 未包装了e3, 返回false
	fmt.Println("is e3:", errors.Is(err, e3))
}

错误判断(As)

代码语言:txt
复制
// errors.As(err, target error) bool
// 如果err和target是同一个,那么返回true, 并将值赋给target
// 如果err是一个wraperror,target也包含在这个嵌套error链中的话,那么也返回true, 并将值赋给target
package main

import (
	"errors"
	"fmt"
)

type MyError struct {
	msg string
	err error
}

func (e *MyError) Error() string {
	return e.msg
}

func (e *MyError) Unwrap() error {
	return e.err
}

type MyNewError struct {
	msg string
	err error
}

func (e *MyNewError) Error() string {
	return e.msg
}

func (e *MyNewError) Unwrap() error {
	return e.err
}

func main() {
	e1 := &MyError{"第1层错误", nil}
	err := &MyNewError{"第3层错误", e1}
	
	var aE1 *MyError
	fmt.Println("as MyError:", errors.As(err, &aE1))
	fmt.Println(aE1)

	var aE2 *MyNewError
	fmt.Println("as MyNewError:", errors.As(err, &aE2))
	fmt.Println(aE2)
}
// out:
// as MyError: true
// 第1层错误
// as MyNewError: true
// 第3层错误

返回错误方式错误

Go 语言中的错误是一种接口类型。接口信息中包含了原始类型和原始的值,只有当接口的类型和原始的值都为空的时候,接口的值才对应 nil。

在下面的例子中,试图返回自定义的错误类型,并且当没有错误的时候返回 nil,但是最终返回的结果其实并非是nil, 而是一个正常的错误,错误的值是一个 MyError 类型的空指针。

代码语言:go
复制
func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // Will always return a non-nil error.
}

修正returnsError:

代码语言:go
复制
func returnsError() error {
    if bad() {
        return (*MyError)(err)
    }
    return nil
}

因此,在处理错误返回值的时候,没有错误的返回值最好直接写为 nil。

异常

panic异常

panic异常可以通过 recover进行捕获,让程序恢复正常。

注意:

  1. 必须要和有异常的栈帧只隔一个栈帧,recover函数才能正常捕获异常。换言之,recover函数捕获的是祖父一级调用函数栈帧的异常(刚好可以跨越一层 defer 函数)!
  2. 我们必须在 defer 函数中直接调用 recover。如果 defer 中调用的是 recover 函数的包装函数的话,异常的捕获工作将失败!
代码语言:go
复制
// 正确示例:数据越界, 可以捕获异常
package main

import (
    "fmt"
)

func testPanicError(){
    defer func(){
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    var bar = []int{1}
    fmt.Println(bar[1])
}

func main(){
    testPanicError()
    fmt.Println("exit")
}

// out
// runtime error: index out of range
// exit
代码语言:go
复制
// 错误示例:使用嵌套defer, 不可以捕获异常
package main

import (
    "fmt"
)

func testPanicError(){
    defer func() {
        defer func() {
            // 无法捕获异常
            if err := recover(); err != nil {
                fmt.Println(err)
            }
        }()
    }()
    var bar = []int{1}
    fmt.Println(bar[1])
}

func main(){
    testPanicError()
    fmt.Println("exit")
}
代码语言:go
复制
// 错误示例:使用封装的recover, 不可以捕获异常
func main() {
    defer func() {
        // 无法捕获异常
        if r := MyRecover(); r != nil {
            fmt.Println(r)
        }
    }()
    panic(1)
}

func MyRecover() interface{} {
    log.Println("trace...")
    return recover()
}
代码语言:go
复制
// 错误示例:跨goroutine, 不可以捕获异常
package main

import (
	"fmt"
	"time"
)

func testPanicError() {
	var bar = []int{1}
	fmt.Println(bar[1])
}

func main() {
	defer func() {
		// 无法捕获goroutine中异常
		if err := recover(); err != nil {
			fmt.Println("main panic", err)
		}
	}()
	go testPanicError()
	fmt.Println("exit")
	time.Sleep(time.Second * 5)
}

// out:
exit
panic: runtime error: index out of range [1] with length 1

goroutine 18 [running]:
main.testPanicError()
        xxx/err/main.go:10 +0x1b
created by main.main
        xxx/err/main.go:20 +0x46

Process finished with the exit code 2

fatal异常(src/runtime/panic.go:fatal|fatalthrow|fatalpanic)

不可以通过recover进行捕获,程序直接退出os.Exit(1)

代码语言:go
复制
// log中fatal
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...any) {
	std.Output(2, fmt.Sprint(v...))
	os.Exit(1)
}

// src/runtime/panic.go:func fatal(s string) 
func fatal(s string) {
	// Everything fatal does should be recursively nosplit so it
	// can be called even when it's unsafe to grow the stack.
	systemstack(func() {
		print("fatal error: ", s, "\n")
	})

	fatalthrow(throwTypeUser)
}

throw函数(src/runtime/panic.go:fatal|fatalthrow|fatalpanic)

不可以通过recover进行捕获,程序直接退出os.Exit(1)

代码语言:go
复制
// 并发写map
package main

import (
  "fmt"
)

func testThrowError(){
  defer func(){
      if err := recover(); err != nil {
          fmt.Println(err)
      }
  }()
  var bar = make(map[int]int)
  go func(){
      defer func(){
          if err := recover(); err != nil {
              fmt.Println(err)
          }
      }()
      for{
          _ = bar[1]
      }
  }()
  for{
      bar[1]=1
  }
}

func main(){
  testThrowError()
  fmt.Println("exit")
}


// out:
fatal error: concurrent map read and map write

goroutine 5 [running]:
runtime.throw(0x4bd8b0, 0x21)
  xx/src/runtime/panic.go:617 +0x72 fp=0xc00004c780 sp=0xc00004c750 pc=0x427f22
runtime.mapaccess1_fast64(0x49eaa0, 0xc000088180, 0x1, 0xc0000260d8)
  xx/src/runtime/map_fast64.go:21 +0x1a8 fp=0xc00004c7a8 sp=0xc00004c780 pc=0x40eb58
......
exit status 2


// 这里主要是map并发写会触发throw,这里需要加锁处理
if h.flags&hashWriting != 0 {
  throw("concurrent map read and map write")
}

// src/runtime/panic.go:func throw(s string)
func throw(s string) {
	// Everything throw does should be recursively nosplit so it
	// can be called even when it's unsafe to grow the stack.
	systemstack(func() {
		print("fatal error: ", s, "\n")
	})

	fatalthrow(throwTypeRuntime)
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概念
  • 错误
    • 自定义错误
      • 错误包装
        • 错误判断(Is)
          • 错误判断(As)
            • 返回错误方式错误
            • 异常
              • panic异常
                • fatal异常(src/runtime/panic.go:fatal|fatalthrow|fatalpanic)
                  • throw函数(src/runtime/panic.go:fatal|fatalthrow|fatalpanic)
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                  http://www.vxiaotou.com