区分错误与异常
很赞成参考文章中知乎高赞中“达达”老哥说的,首先要理清错误与异常之间的区别。我在阅读之后我总结的区别如下:
| 区分点 | 错误 | 异常 |
|---|---|---|
| 语言层面 | error | panic |
| 不处理的结果 | 可能会导致逻辑业务上的错误,或者进一步产生异常 | 进程异常退出 |
| 预见性 | 可以预见 | 不可预见 |
总的来说,在Go语言中,可以按照是否可以提前指导,很分明的区分了错误(可以提前知道)与异常(不能提前知道)。
不同语言之间的错误与异常
错误与异常,拿来和Python和Java相比的话,总结起来应该是这样的:
| 语言 | 错误 | 异常 |
|---|---|---|
| Python | Error 解释器不想出现的结果(如语法错误) | Exception 运行中的异常情况(-_- 其实我感觉Python混为一谈了) |
| Java | Error Java 与虚拟机有关的错误(如栈溢出、内存不足等等),不能被抓取 | Exception Java 程序运行中可预料的异常情况,可以被抓取,分为运行时异常和受检查的异常 |
| Go | Error 可以预料的错误,是业务处理中的一部分 | Panic 不可预料的错误,会导致异常退出 |
还是很别扭,特别是Java和Go,简直就是颠倒了(\掀桌)。如果结合函数调用的场景,它们的区别如下(总结的,不一定准确):
| 场景 | Python | Java | Go |
|---|---|---|---|
| 调用其它函数调用失败(如语法错误) | 错误 | 错误 | |
| 函数中参数不对(如类型不符) | 错误 | 错误 | |
| 调用过程中预料之中的问题(如文件权限不足),但是调用成功 | 异常/错误 | 异常 | 错误 |
| 调用过程中预料之外的问题(空指针引用) | 异常/错误 | 异常 | 异常 |
| 堆栈溢出等 | 异常 | 异常 | 异常 |
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
}
}
结果
