区分错误与异常

很赞成参考文章中知乎高赞中“达达”老哥说的,首先要理清错误异常之间的区别。我在阅读之后我总结的区别如下:

区分点错误异常
语言层面errorpanic
不处理的结果可能会导致逻辑业务上的错误,或者进一步产生异常进程异常退出
预见性可以预见不可预见

总的来说,在Go语言中,可以按照是否可以提前指导,很分明的区分了错误(可以提前知道)与异常(不能提前知道)。

不同语言之间的错误与异常

错误与异常,拿来和Python和Java相比的话,总结起来应该是这样的:

语言错误异常
PythonError 解释器不想出现的结果(如语法错误)Exception 运行中的异常情况(-_- 其实我感觉Python混为一谈了)
JavaError Java 与虚拟机有关的错误(如栈溢出、内存不足等等),不能被抓取Exception Java 程序运行中可预料的异常情况,可以被抓取,分为运行时异常受检查的异常
GoError 可以预料的错误,是业务处理中的一部分Panic 不可预料的错误,会导致异常退出

还是很别扭,特别是Java和Go,简直就是颠倒了(\掀桌)。如果结合函数调用的场景,它们的区别如下(总结的,不一定准确):

场景PythonJavaGo
调用其它函数调用失败(如语法错误)错误错误
函数中参数不对(如类型不符)错误错误
调用过程中预料之中的问题(如文件权限不足),但是调用成功异常/错误异常错误
调用过程中预料之外的问题(空指针引用)异常/错误异常异常
堆栈溢出等异常异常异常

Golang中的异常处理

try catchpanic recover defer
deferpanicdeferrecoverpanicdefer
使用的例子
package main

 import "fmt"

 func foo(){
	 defer func() {
		 fmt.Println("------> defer\tbegin-----")
		 if err :=recover(); err != nil{
			 fmt.Println("[*] recover error: ", err)
		 }
		 fmt.Println("[=] (recover done)")
		 fmt.Println("------< defer\tend-----")
	 }()

	 fmt.Println("===> foo Begin")
	 panic("!!{Bug boom}!!")
	 fmt.Println("<=== foo End")
 }

 func main() {
	 fmt.Println("============[main begin]================")
	 foo()
	 fmt.Println("============[main   end]================")
 }
 

运行结果如下:

Golang中的错误处理

error的定义
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
	Error() string
}
error
error的创建
errors.New ()
errorsNew

源码:

func New(text string) error {
    return &errorString{text}
}

使用方法:

	err := errors.New("一个新错误")
	fmt.Printf("err 错误类型:%T,错误为:%v\n", err, err)

运行结果:

fmt.Errorf ()

这个函数会对字符串格式化,增加上下文信息,可以更精确的描述错误。

	// 类型1:字符串中未包含错误
	err1 := fmt.Errorf("Error1:E1") //返回一个错误
	fmt.Printf("err1 %T \t %v\n", err1, err1)
	// 类型2:字符串中包含了%w
	err2 := fmt.Errorf("Error2:%w", err1)
	fmt.Printf("err2 %T \t\t %v\n", err2, err2)

运行结果:

%w
%werrors.New()*errors.errorString%wwrapError*fmt.wrapError
error的处理

在error中最常见的处理方式就是通过多值进行返回,明了的暴露出来,要么处理,要么略过。最常见的处理方式如下:

   err := bar()
   if err != nil {
      fmt.Printf("got err, %+v\n", err)
   }

错误处理的最佳实践

错误处理的弊端
if err!=nil

同时由于错误的信息很少,我们需要知道出错的更多信息,在什么文件的,哪一行代码,用来更好的定位。

最佳实践:github.com/pkg/errors

有几个关键函数:

WrapWithMessageWithStackCause
package main
import (
	"fmt"
 
	"github.com/pkg/errors"
 )

/* 全局变量 自定义错误*/
var ERROR=errors.New("<Error>")

//错误源头,包含堆栈信息
 func foo() error {
	return errors.Wrap(ERROR, "====={Foo Message}=====")
 }

 //间接调用错误函数
 func bar() error {
	return errors.WithMessage(foo(), "====={Bar Message}=====")
 }
 
 func main() {
	err := bar()
	//使用Cause 进行判断
	if errors.Cause(err) == ERROR {
	   fmt.Printf("Simple Error Info: %v\n", err) //信息保持一行
	   fmt.Printf("\nRich   Error Info: %+v\n", err) //信息包含调用栈
	   return
	}
 }

结果

参考文章: