大家好,我是煎鱼。
一年半前分享了《先睹为快,Go2 Error 的挣扎之路》的文章,内容波及 Go1 错误处理的问题、Go1.13 的增强、Go2 的新错误处理提案的详解。有多少小伙伴还记得 Go2 的新谬误提案的“美妙”将来?
过后 Go2 的新提案也蒙受到了不少批评,@Liam Breck 在《Golang, how dare you handle my checks!》中对此进行了批评,让咱们一起来学习吧!
温习语法
在 2018 年 8 月,官网正式颁布了 Go 2 Draft Designs,其中蕴含泛型和错误处理机制改良的初步草案:
上面是要害的 Go2 错误处理新语法。
错误处理(Error Handling)
第一个要解决的问题就是大量 if err != nil
的问题,针对此提出了 Go2 error handling 的草案设计。
简略例子:
if err != nil {return err}
优化后的计划如下:
func CopyFile(src, dst string) error {
handle err {return fmt.Errorf("copy %s %s: %v", src, dst, err)
}
r := check os.Open(src)
defer r.Close()
w := check os.Create(dst)
handle err {w.Close()
os.Remove(dst) // (only if a check fails)
}
check io.Copy(w, r)
check w.Close()
return nil
}
主函数:
func main() {
handle err {log.Fatal(err)
}
hex := check ioutil.ReadAll(os.Stdin)
data := check parseHexdump(string(hex))
os.Stdout.Write(data)
}
该提案引入了两种新的语法模式,首先是 check
关键字,其能够选中一个表达式 check f(x, y, z)
或 check err
,其将会标识这是一个显式的谬误查看。
其次引入了 handle
关键字,用于定义谬误处理程序流转,逐级上抛,依此类推,直到处理程序执行 return
语句,才正式完结。
谬误值打印(Error Printing)
第二个要解决的问题是谬误值(Error Values)、谬误查看(Error Inspection)的问题,其引申出谬误值打印(Error Printing)的问题,也能够认为是谬误格式化的不便当。
官网针对此提出了提出了 Error Values 和 Error Printing 的草案设计。
简略例子如下:
if err != nil {return fmt.Errorf("write users database: %v", err)
}
优化后的计划如下:
package errors
type Wrapper interface {Unwrap() error
}
func Is(err, target error) bool
func As(type E)(err error) (e E, ok bool)
该提案减少了谬误链的 Wrapping Error 概念,并同时减少 errors.Is
和 errors.As
的办法,与后面说到的 Go1.13 的改良统一,不再赘述。
Go1.13 没有实现 %+v
输入调用堆栈的需要(没有调用栈,排查问题会很苦恼),因为此举会毁坏 Go1 兼容性和产生一些性能问题,大略会在 Go2 退出。
对提案批评
指标较为含糊
在 Go2 新错误处理的提案和草案中,@Liam Breck 认为其没有去探讨基本的需要。仅有一个简短的指标局部,如下四点:
- 占用空间小的谬误查看。
- 对开发人员敌对的错误处理。
- 显式检查和处理错误。
- 保障现有 Go1 代码的兼容性。
更也没有提到将来可能的扩展性,只是纯正的满足当下的诉求。这类是含糊的,在激发新的设计思路上有局限性。
无奈对立错误处理
在实在的利用中,一个函数应用两种及以上的反复错误处理形式是十分常见的。
如下代码:
// 形式 1
{debug.PrintStack(); log.Fatal(err) }
// 形式 2
{log.Println(err) }
// 形式 3
{if err == io.EOF { break} }
// 形式 4
{conn.Write([]byte("oops:" + err.Error())) } // network server
新提案中,check 和 handle 函数并不提供多种处理错误的路径。这是一个显著的脱漏,也没法对立错误处理机制。
如此的话,check 和 handle 就齐全是只加了一种新的形式,让本来的错误处理机制更加的繁冗。
混用 err != nil 和 check
handle 函数是后进先出的模式,只能从一个函数中跳出。也就是说它不能很敌对的解决可复原的谬误内容。
但实际上,许多办法返回谬误是很常见的。因而咱们须要同时应用 err!= nil 和 check,显得十分的繁琐。
如下代码:
handle err {...}
v, err := f()
if err != nil {if isBad(err) {check err}
// recovery code
}
又是 if err != nil,又是 handle,又是 check 函数。
嵌套 check 更简单
通过 check 函数,对返回谬误的函数调用进行嵌套调用,将会含糊了操作的程序。
尽管在大多数状况下,谬误产生时的调用程序应该是分明的,但在 check 函数下会显得不如 if err != nil 清晰。
如下代码:
check step4(check step1(), check step3(check step2())
另外嵌套代码会助长不可读的构造:
f1(v1, check f2(check f3(check f4(v4), v3), check f5(v5))
当初回顾一下,该语言禁止。
f(t ? a : b) 和 f(a++)
是不是显得有些讥刺呢?
if err != nil 太好用
Go1 的谬误处理程序太敌对了,也就是:
if err != nil {...}
其挫败了 “ 进步开发人员编写谬误处理程序的可能性 ” 的指标,它使得在没有上下文信息的状况下返回谬误是很容易的。
注:个人感觉,这一点既像黑又像粉 … 原作者反串黑?当然,的确 if err != nil 很好上手。
简单的谬误链
对于上面的例子,看看它的感觉 …
如下代码:
func f() error {handle err { return ...} // finally this
if ... {handle err { ...} // not that
for ... {handle err { ...} // nor that
...
}
}
handle err {...} // secondly this
...
if ... {handle err { ...} // not that
...
} else {handle err { ...} // firstly this
check thisFails() // trigger}
}
显然,这段代码是“难以捉摸”的,咱们必须用眼睛解析整个函数,了解整个错误处理的流程和程序。
将会加大咱们对程序的认知累赘。
总结
通过对 Go2 错误处理的设计草案的温习,咱们理解到了 check 和 handle 函数的用法和思路。再针对新的语法,又对可能产生的新问题进行了“批评”。
总的来说,新的语法,在弊病上会减少既有的代码复杂度和可读性,能够引发各种奇怪的嵌套,还会与 if err != nil 产生反复,变成了一种新的解决形式(多了一种)。
是否会还不如 if err != nil 那么的纯正?
Go 图书系列
- Go 语言入门系列:初探 Go 我的项目实战
- Go 语言编程之旅:深刻用 Go 做我的项目
- Go 语言设计哲学:理解 Go 的为什么和设计思考
- Go 语言进阶之旅:进一步深刻 Go 源码
举荐浏览
- goto 语句让 Go 代码变成意大利面条?
- Go 只会 if err != nil?这是不对的,分享这些优雅的解决姿态给你!