湛江,一篇文章理解围棋语言中有争议的错误处理-火竞猜-火竞猜电子竞技赛事平台

国际新闻 214℃ 0

写过 C 的同学知道,C 言语中常常回来整数过错码(errno)来表明函数处理犯错,一般用 -1 来表明过错,用 0 表明正确。

而在 Go 中,咱们运用 error 类型来表明过错,不过它不再是一个整数类型,是一个接口类型:

type error interface {
Error() string
}

它表明那些能用一个字符串就能说清的过错。

咱们最常用的便是 errors.New() 函数,十分简略:

// src/errors/errors.go
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}

运用 New 函数创立出来的 error 类型实践上是 errors 包里未导出的 errorString 类型,它包括仅有的一个字段 s,而且完成了仅有的方法:Error() string。

一般这就够了,它能反映其时“犯错了”,可是有些时分咱们需求愈加详细的信息,例如:

func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementa难民服tion
}

当调用者发现犯错的时分,只知道传入了一个负数进来,并不清楚究竟传的是什么值。在 Go 里:

It is the error implementation’s responsibility to summarize the context.

它要求回来这个过错的函数要给出详细的“上下文”信息,也便是说,在 Sqrt 函数里,要给出这个负数究竟是什么。

所以,假如发现 f 小于 0,应该这样回来过错:

if f < 0 {
return 0, fmt.Errorf("math: square ro金同志飞起来ot of negative number %g", f)
}

这就用到了 fmt.Errorf 函数,它先将字符串格局化,再调用 errors.New 函数来创立过错。

当咱们想知道过错类型,而且打印过错的时分,直接打印 error:

fmt.Println(err)

或许:

fmt.Println(err.Error)

fmt 包会主动调用 err.Error() 函数来打四年级下册数学印字符串。

一般,咱们将 error 放到函数回来值的最终一个,没什么好说的,咱们都这样做,约定俗成。

参考资料【Tony Bai】这篇文章说到,结构 error 的时分,要求传入的字符串首字母小写,完毕不带标点符号,这是由于咱们常常会这样运用回来的 error:

... err := errors.New("error example")
fmt.Printf("The returned error is %s.\n", err)

error 的困局

In Go, error handling is important. The language’s design and conventions encourage you to explicitly check for errors where they occur (as distinct from the conventio蜂胶n in other languages of throwing exceptions and sometimes catching them).

在 Go 言语中,过错处理是十分重要的。它从言语层面要求咱们需求明确地处理遇到的过错。而不是像其他言语,类如 Java,运用 try-catch- finally 这种“花招”。

这就构成代码里 “error” 满天飞,显得十分冗长磨蹭。

而为了代码健壮性考虑,关于函数回来的每一个过错,咱们都不能疏忽它。由于犯错的一起,很可能会回来一个 nil 类型的目标。假如不对过错进行判别,那下一行对 nil 目标的操作百分之百会引发一个 panic。

这样,Go 言语中诟病最多的便是它的过错处理方法好像回到了上古 C 言语年代。

rr := doStuff1()
if err != nil {
//handle error...
}
e蒙城天气预报rr = doStuff2()
if err != nil {
//handle error...
}
err = doStuff3()
if err != nil {
//handle error...
}
Go authors 之一的 Russ Cox 关于这种观念进行过批驳:最初挑选回来值这种过错处理机制而不是 try-catch,首要是考虑前者适用于大型软件,后者更适合小程序。

在参考资料【Go FAQ】里也说到,try-catch 会让湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道代码变得十分紊乱,程序员会倾向将一些常见的过错,例如,failing to open a file,也抛到反常里,这会让过错处理愈加冗长繁琐且易犯错。

而 Go 言语的多回来值使得回来过错反常简略。关于真实的反常,Go 供给 panic-recover 机制,也使得代码看起来十分简练。

当然 Russ Cox 也供认 Go 的过错处理机湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道制关于开发人员的确有必定的心智担负。

参考资料【Go 言语的过错处理机制是一个优异的规划吗?】是知乎上的一个答复,论述了 Go 对待过错和反常的不同处理方法,前者运用 error,后者运用 panic,这样的处理比较 Java 那种过错反常一锅端的做法更有优势。

【怎么高雅的在Golang中进行过错处理】关于在事务上怎么处理 error,给出了一些很好的示例。

测验破局老婆大人有点冷全文免费阅览

这部分的内容首要来自 Dave cheney GoCon 2016 的讲演,参考资料能够直达原文。

常常听到 Go 有许多“告诫”,说得很顺口,但了解起来并不是太简略,由于它们大部分都是有故事的。例如,咱们常说:

Don’t communicating by sharing memory, share memory by communicating.

文中还列举了许多,都很有意思:

下面咱们讲三条关于 error 的“告诫”。

Errors are just values

Errors are just values 的实践意思是只需完成了 Error 接口的类型都能够认为是 Error,重要的是要了解这些“告诫”背面的道理。

作者把处理 error 的方法分为三种:

  • Sentinel errors
  • Error Types
  • Opaque errors

咱们来挨个说。首要 Sentinel errors,Sentinel 来自计算机中常用的词汇,中文意思是“岗兵”。曾经在学习快排的时分,会有一个“岗兵”,其他元素都要和“岗兵”进行比较,它划出了一条边界。

这儿 Sentinel errors 实践想说的是这儿有一个过错,暗示处理流程不能再进行下去了,有必要要在这儿停下,这也是一条边界。而这些过错,往往是提早约定好的游乐场。

例如,io 包里的 io.EOF,表明“文件完毕”过错。可是这种方法处理起来,不太灵敏:

func main() {
r := bytes.NewReader([]byte("0123456789"))

_, err := r.Read(make([]byte, 10))
if err == io.EOF {
log.Fatal("read failed:", err)
}
}

有必要要判别 err 是否和约定好的过错 io.EOF 持平。

再来一个比方,当我想回来 err 而且加上一些上下文信息时,就费事了:

在 readfile 函数里判别 err 不为空,则用 fmt.Errorf 在 err 前加上详细的 file 信息,回来给调用者。回来的 err 其实仍是一个字符串。

构成的成果极客修时,调用者不得不必字符串匹配的方法判别底层函数 readfile 是不是呈现了某种过错。当你有必要要这样才干判别某种过错时,代码的“坏滋味”就呈现了。

顺带说一句,err.Error() 方法是给程序员而非代码规划的,也便是说,当咱们调用 Error 方法时,成果要写到文件或是打印出来,是给程序员看的。在代码里,咱们不能依据 err.Error() 来做一些判别,就像上面的 main 函数里做的那样,欠好。

Sentinel errors 最大的问题在于它在界说 error 和运用 error 的包之间建立了依靠联系。比方要想判别 err == io.EOF 就得引进 io 包,当然这是规范库的包,还 Ok。假如许多用户自界说的包都界说了过错,那我就要引进许多包,来判别各种过错。费事来了,这简略引起循环引证的问题。

因而,咱们应该尽量防止 Sentinel errors,仅管规范库中有一些包这样用,但主张仍是别仿照。

第二种便是 Error Types,它指的是完成了 error 接口的那些类型。它的一个重要的优点是,类型中除了 error 外,还能够顺便其他字段,然后供给额定的信息,例如犯错的行数等。

规范库有一个十分好的比方:

// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string
Path string
Err error
}

PathError 额定记载了犯错时的文件途径和操作类型。

一般,运用这样的 error 类型,外层调用者需求运用类型断语来判别过错:

// underlyingErr喀门or returns the underlying error for known os error types.
func underlyingError(err error) error {
switch err := err.(type) {
case *PathError:
return err.Err
case *LinkError:
return err.Err
case *SyscallError:
return err.Err
}
return err
}

可是这又不可防止地在界说过错和运用过错的包之间构成依靠联系,又回到了前面的问题。

即便 Error types 比 Sentinel errors 好一些,由于它能承载更多的上下文信息,可是它仍然存在引进包依靠的问题。因而,也是不引荐的。至少,不要把 Error types 作为一个导出类型。

最终一种,Opaque errors。翻译一下,便是“黑盒 errors”,由于你能知道过错发生了,可是不能看到它内部究竟是什么。

比方下面这段伪代码:

func fn() error {
x, err := bar.Foo()
if err != nil {
return err
}

// use x
return nil
}

作为调用者,调用完 Foo 函数后,只用知道 Foo 是正常作业仍是出了问题。也便是说你只需求判别 err 是否为空,假如不为空,就直接回来过错。不然,持续后边的正常流程,不需求知道 err 究竟是什么。

这便是处理 Opaque errors 这种类型过错的战略。

当然,在某些情况下,这样做并不够用。例如,在一个网络恳求中,需求调用者判别回来的过错类型,以此来决议是否重试。这种情况下,作者给出了一种方法:

In this case rather than asserting the error is a specific type or value, we can assert that the error implements a particular behaviour.

便是说,不去判别过错的类型究竟是什么,而是去判别过错是否具有某种行为,或许说完成了某个接口。

来个比方:

type temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}

拿到网络恳求回来的 error 后,调叶春晖新浪博客用 IsTemporary 函数,假如回来 true,那就重试。

这么做的优点是在进行网络恳求的包里,不需求 import 引证界说过错的包。

handle not just check errors

这一节要说第二句告诫:“Don’t just check errors, handle them gracefully”。

func AuthenticateRequest(r *Request) error {
err 湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道:= auvlpkldthenticate(r.User)
if err != nil {
return err
}
return nil
}

上面这个比方中的代码是有问题的,直接优化成一句就能够了:

func AuthenticateRequest(r *Request) error {
return authenticate(r.User)
}

还有其他的问题,在函数调用链的最顶层,咱们得到的过错可能是:No such file or directory。

这个过错反应的信息太少了,不知道文件名、途径、行号等等。

测验改善一下,添加一点上下文:

func AuthenticateRequest(r *Request) error {
err := authenticate(r.User)
if err != nil {
return fmt.Errorf("authenticate failed: %v", err)
}
return nil
}

这种做法实践上是先过错转换成字符串,再拼接另一个字符串,最终,再经过 fmt.Errorf 转换成过错。这样做破坏了持平性检测,即咱们无法判别过错是否是一种预先界说好的过错了。

应对计划是运用第三方库:github.com/pkg/errors。供给了友爱的界面:

// Wrap annotates cause with a message.
func Wrap(cause error, message string) error
// Cause unwraps an annotated error.
func Cause(err error) error

经过 Wrap 能够将一个过错,加上一个字符串,“包装”成一个新的过错;经过 Cause 则能够进行相反的操作,将里层的过错复原。

有了这两个函数,就便利许多:

func ReadFile(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, errors.Wrap(err, "open failed")
}
defer f.Close()

buf, err := ioutil.ReadAll(f)
if err != nil {
return nil, errors.Wrap(err, "read failed")
}
return buf, nil
}

这是一个读文件的函数,先测验翻开文件,假如犯错,则回来一个附加上了 “open failed” 的过错信息;之后,测验读文件,假如犯错,则回来一个附加上了 “read failed” 的过错。

当在外层调用 ReadFile 函数时:

func main() {
_, err := ReadConfig()
if err !张琪格= nil {
fmt.Println(err)
os.Exit(1)
}
}
func ReadConfig() ([]byte, error) {
home := os.Getenv("HOME")
config, err := ReadFile(filepath.Join(home, ".settings.xml"))
return config, errors.Wrap(err, "could not read config")
}

这样咱们在 main 函数里就能打印出这样一个过错信息:

could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory

它是有层次万人空巷的,十分明晰。而假如咱们用 pkg/errors 库供给的打印函数:

func main() {
_, err := ReadConfig()
if err != nil {
errors.Print(err)
os.Exit(1)
}
}

能得到更有层次、更详细的过错:

readfile.go:27: could not read config
readfile.go:14: open failed
open /Users/dfc/.settings.xml: no such file or dir学拼音ectory

上面讲的是 Wrap 函数,接下来看一下 “Cause” 函数,曾经面说到的 temporary 接口为例:

type temporary interface {
Temporary() bool
}
// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
te, ok := errors.Cause(err).(temporary)
return ok && te.Temporary()
}

判别之前先运用 Cause 取犯过错,做断语,最终,递归地调用 Temporary 函数。假如过错没完成 tempor湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道ary 接口,就会断语失利,回来 false。

Only handle errors once

什么叫“处理”过错:

Handling an error means inspecting the error value, and making a decision.

意思是查看了一下过错,而且做出一个决议。

例如,假如不做任何决议,相当于疏忽了过错:

func Write(w io.Writer, buf []byte) { w.Write(buf)
w.Write北京农商银行(buf)
}

w.Write(buf) 会回来两个成果,一个表明写成功的字节数,一个是 error,上面的比方中没有对这两个回来值做任何处理。

下面这个比方却又处理了两次过错:

func Write(w io.Writer, buf []byte) error { 
_, err := w.Write(buf)
if err != nil {
// annotated error goes to log file湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道
log.Println("unable to write:", err)

// unannotated error returned to caller return err
return err
}
return nil
}

第一次处理是将过错写进了日志,第2次处理则是将过错回来给上层调用者。而调用者也可能将过错写进日志或是持续回来给上层。

这样一来,日志文件中会有许多重复的过错描绘,而且哥本哈根在最上层调用者(如 main 函数)看来,它拿到的过错却仍是最底层函数回来的 error,没有任何上下文信息。

运用第三方的 error 包就能够比较完美的处理问题:

func Write(w io.Write, buf []byte) error {
_, err := w.Write(buf)
return errors.Wrap(err, "write failed")
}

回来的过错,关于人和机器而言,都是友爱的。

小结

这一部分首要讲了处理 error 的一些准则,引进了第三方的 errors 包,使得过错处理变得愈加高雅。

作者最终给出了一些定论:

  • errors 就像对外供给的 API 相同,需求认真对待。
  • 将 errors 当作黑盒,判别它的行为,而不是类型。
  • 尽量不要运用 sentinel errors。
  • 运用第三方的过错包来包裹 error(errors.Wrap),使得它更好用。
  • 运用 errors.Cause 来获取底层的过错。

胎死腹中的 try 提案

之前现已呈现用 “check & handle” 关键字和 “try 内置函数”改善过错处理流程的提案,现在 try 内置函数的提案现已被官方提早回绝,原因是社区里一边倒地对立声响。

关于这两个提案的详细内容见参考资料【check & handle】和【try 提案】。

go 1.13 的改善

有一些 Go 言语失利的测验,比方 Go 1.5 引进的 vendor 和 internal 来办理包,最终被乱用而引发了许多问题。因而 Go 1.13 直接扔掉了 GOPATH 和 vendor 特性,改用 module 来办理包。

柴大在《Go 言语十年而立,Go2 蓄势待发》一文中表明:

比方最近 Go 言语之父之一 Robert Griesemer 提交的经过 try 内置函数来简化过错处理就被否决了。失利的测验是一个好的现象,它表明 Go 言语仍然在一些新式范畴的测验 —— Go 言语仍然处于活泼期。

本年 9 月 3 号,Go 发布 1.13 版别,除了 module 特性转正之外,还改善了数字字面量。比较重要的还有 defer 功用提高 30%,将更多的目标从堆上移动到栈上以提高功用,等等。

还有一个严重的改善发生在 errors 规范库中。errors 库添加了 Is/As/Unwrap三个函数,这将用于支撑过错的再次包装和辨认处理,为 Go 2 中新的过错处理改善提早做准备。

1.13 支撑了 error 包裹(wrapping):

An error e can wrap another erro青青草在线针对r w by providing an Unwrap湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道 method that returns w. Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.

为了支撑 wrapping,fmt.Errorf 添加了 %w 的格局,而且在 error 包添加了三个函数:errors.Unwrap,errors.Is,errors.As。

fmt.Errorf

运用 fmt.Errorf 加上 %w 格局符来生成一个嵌套的 error,它并没有像 pkg/errors 那样运用一个 Wrap 函数来嵌套 error,十分简练。

Unwrap

func Unwrap(err error) error

将嵌套的 error 解析出来,多层嵌套需求调用 Unwrap 函数屡次,才干获取最里层的 error。

源码如下:

func Unwrap(err error) error {
// 判别岳晓遥是否完成了 Unwrap 方法
u, ok := err.(interface {
Unwrap() error
})
// 假如不是,回来 nil
if !ok {
return nil
}
// 调用 Unwrap 方法回来被嵌套的 error
return u.Unwrap()
}

对 err 进行断语,看它是否完成了 Unwrap 方法,假如是,调用它的 Unwrap 方法。不然,回来 nil。

Is

func Is(err, target error) bool

判别 err 是否和 target 是同一类型,或许 err 嵌套的 error 有没有和 target 是同一类型的,假如是,则回来 true。

源码如下:

经过一个无限循环,运用 Unwrap 不断地将 err 里层嵌套的 error 解开,再看被解开的 error 是否完成了 Is 方法,而且调用它的 Is 方法,当两者都回来 true 的时分,整个函数回来 true。

As

func As(err error, target interface{}) bool

从 err 过错链里找到和 target 持平的而且设置 target 所指向的变量。

源码如下:

回来 true 的条件是过错链里的 err 能被赋值到 target 所指向的变量;或许 err 完成的 As(interface{}) bool 方法回来 true。

前者,会将 err 赋给 target 所指向的变量;后者,由 As 函数供给这个功用。

假如 target 不是湛江,一篇文章了解围棋言语中有争议的过错处理-火竞猜-火竞猜电子竞技赛事渠道一个指向“完成了 error 接口的类型或许其它接口类型”的非空的指针的时分,函数会 panic。

这一部分的内容,飞雪无情大佬的文章【飞雪无情 剖析 1.13 过错】写得比较好,引荐阅览。

总结

Go 言语运用 error 和 panic 处理过错和反常是一个十分好的做法,比较明晰。至所以运用 error 仍是 panic,看详细的事务场景。

当然,Go 中的 error 过于简略,以至于无法记载太多的上下文信息,关于过错包裹也没有比较好的方法。当然,这些能够经过第三方库来处理。官方也在新发布的 go 1.13 中对这一块作出了改善,信任在 Go 2 里会有更进一步的优化。

本文还列举了一些处理 error 的示例,例如不要两次处理一个过错,判别过错的行为而不是类型等等。

参考资料里列举了许多过错处理相关的示例,这篇文章作为一个引子。

参考资料

【Go 2 过错提案】https://go.googlesource.com/proposal/+/master/design/29934-error-values.md

【check & handle】https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md

【过错评论的 issue】https://github.com/golang/go/issues/29934

【error value 的 FAQ】https://github.com/golang/go/wiki/ErrorValueFAQ

【error 包】https://golang.org/pkg/errors/

【飞雪无情的博客 过错处理】https://www.flysnow.org/2019/01/01/golang-error-handle-suggestion.html

【飞雪无情 剖析 1.13 过错】https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html

【Tony Bai Go言语过错处理】https://tonybai.com/2015/10/30/error-handling-in-go/

【Go 官方 error 运用教程】https://blog.golang.org/error-handling-and-go

【Go FAQ】https://golang.org/doc/faq#exceptions

【ethancai 过错处理】https://ethancai.github.io/2017/12/29/Error-Handling-in-Go/

【Dave cheney GoCon 2016 演failure讲】https://dave.cheney.net/paste/gocon-spring-2016.pdf

【Morsing’s Blog Effective error handling in Go】http://morsmachine.dk/error-handling

【怎么高雅的在Golang中进行过错处理】https://www.ituring.com.cn/article/508191

【Go 2 过错处理提案:try 仍是 check?】https://toutiao.io/posts/uh9qo7/preview

【try 提案】https://github.com/golang/go/issues/32437

【否决 try 提案】https://github.com/golang/go/issues/32437#issuecomment-512035919

【Go 言语的过错处理机制是一个优异的规划吗?】https://www.zhihu.com/question/27158146/answer/44676012

本文作者:饶全成,原创授权发布

本文链接:https://qcrao.com/2019/09/18/golang-error-break-through/

版权声明:本文章著作权归作者一切,任何方式的转载都请注明出处。