go 错误处理

“可对值进行编程,而错误是值,因此可对错误进行编程.错误不像异常,因为错误没有什么特别之处,而未处理的异常可能导致程序崩溃.”–Rob Pike

错误的分类和处理机制

广义错误(errors) 非期望的行为

  • 未捕获错误(untrapped error 异常) 未知错误,未定义的错误类型 --> 段错误
  • 可捕获错误(trappend error) 已知错误,预定义的错误类型
    • 编译错误(compile errors) 编译器捕获 --> 语法错误
    • 运行时错误(runtime errors) 运行时捕获 --> 解引用空指针,数组越界访问
    • 逻辑错误(logic errors) 人工(测试/错误处理代码/代码审查)捕获

C 是非类型安全的语言.段错误(Segmentation Fault)在编译期和运行期都没捕获处理,由操作系统进行异常处理,属于未捕获错误.
Go 是类型安全的语言.不会出现编译期和运行期都无法捕获的错误,所以 go 的错误是可捕获的错误.编译错误可以靠编译器发现,因此, go 要处理的错误就只有两种:

  • 运行时错误:由运行时捕获并抛出 panic.处理方式:
    • 被 recover 捕获,被处理或继续抛出 panic.
    • 未被 recover 捕获,程序崩溃.
  • 逻辑错误:非预期的行为(结果),不会引发运行时错误.处理方式:
    • 无法判定错误严重性,希望调用者决定,返回 error.
    • 判定错误严重,希望程序停止运行(崩溃),主动抛出 panic.

错误的表示

  • 返回 error
  • 抛出 panic

错误的处理

  • 控制流处理机制( if/else/return )
  • 异常处理机制( panic/recover )

error

错误原因的种类

  • 0 ,总是成功,不返回或返回对应的值.(v = fnxxx(…))
  • 1 ,错误只有一种情况,用 bool 类型返回值, ture 表示正确, false 表示错误.(v,ok = fnxxx(…))
  • n ,错误有多种的情况,用 error 类型返回值, nil 表示正确,其他不同的值表示不同的错误.(ret,err = fnxxx(…))

错误处理跟在函数调用之后,正确的代码由可能就不要缩进,使代码平坦.

v,ok = fnxxx()
if !ok{
    return;
}
fnyyy()     //不用缩进,代码平坦

发生错误时,尽可能返回个有用的值而非一定是 nil,因为用户可能这么用

v,_= fnxxx()
fmt.Printf("...",v)   //不作判断直接使用 v

尽量不定义新的 error 类型.没有特殊性需要通过新的 error 类型去传达,只要返回通用的 error 类型.内置的 errors 包可以构建一个 error 变量.

import errors
fnxxx()error{
    //...
    return errors.New("error ...")
}

更好用 fmt.Errorf ,封装了 errors.New.

package fmt
import  "errors"
func Errorf(format string, a ...interface{}) error {
	return errors.New(Sprintf(format, a...))
}

nil 用作返回值的含意

  • 零值,表示返回值是 nil, retval = nil
  • 零值,表示无错误, error = nil
  • 占位符,表示不想返回值.

panic

panic 表示程序遇到严重错误.

发生 panic 时,程序的行为

  • 程序正常执行终止,进入 panic 状态.
  • 从栈顶到栈底倒序执行 defer 函数.
  • 如果有 defer 函数调用了 recover 函数, 那么 panic 被捕获, panic 状态解除, defer 所在函数正常返回(程序进入正常状态).
  • 否则直到 main 函数,然后异常退出.
  • 打印栈跟踪信息,之后清理栈.

遇到不可能的情况, panic 是最好的选择

switch s := suit(drawCard()); s {
    case "Spades":
    // ...
    case "Hearts":
    // ...
    case "Diamonds":
    // ... 
    case "Clubs":
    // ...
    default:
        panic(fmt.Sprintf("invalid suit %v", s))
}

假设这段代码是程序的主要逻辑. suit() 的返回值有且仅有四种(Spades,Hearts,Diamonds,Clubs),这是已知条件,是 suit() 给出的契约,可以从 suit() 源码或 API 手册确认.现在进入default 分支,出现了第五种值. 处理方式一,打印日志,继续运行,带着错误继续运行产生更多的错误,继续运行也没意义了,还不如停止抛出 panic.处理方式二,返回错误值给上层调用者处理,上层代码是根据 suit() 的契约,带着预期使用 suit(), 现在这一种超预期的结果要它处理,那潜藏的成千上万种超预期的结果是否也要它处理.所以返回错误值,调用者也处理不了,还是原地抛出 panic.

Must 开头的函数一般要求正确的输入,如果输入不正确引发 panic

package regexp
func MustCompile(str string) *Regexp {
    regexp, error := Compile(str)
    if error != nil {
        panic(`regexp: Compile(` + quote(str) + `): ` + error.Error())
    }
    return regexp
}

//使用场景
var httpSchemeRE = regexp.MustCompile(`^https?:`)//"http:"或"https" 

这里直接用 `^https?:` 调用 Compile() 也能得到想要的结果.但是接口不匹配,只想要一个值但 Compile() 却返回两个值,所以需要一个包装函数获得希望的接口方便使用,即 MustCompile() 函数. 这种情况下最终传给 Compile() 是 `^https?:` ,结果是可预期的(regexp = xxx , error = nil),如果得到非预期的结果(nil != nil)抛出 panic 可以快速发现低级错误( `^https?:` 拼写错误)或严重错误( Compile() 函数有问题).

panic 函数的参数 interface{} 类型,可以传给它任何值.对应的处理函数是 recover,如果它捕获了 panic,那么返回值是传给 panic 函数的值.如果没捕获 panic,那么返回值是 nil.尽量用 error 接口的值传给 panic 函数,以备 recover 函数捕获后分析.

defer func(){
    if err := recover(); err != nil{
    }
}
panic(errors.New("some errors"))

参考:
Errors are Values
Go程序设计语言
Go语言核心编程

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值