校对完毕

校对完毕,谢谢Geekpi
This commit is contained in:
jasminepeng 2017-09-04 17:06:37 +08:00 committed by GitHub
parent 5dfed65d10
commit 23d55f5a78

View File

@ -1,12 +1,12 @@
我对 Go 的错误处理有哪些不满,以及我是如何处理的
======================
写 Go 的人往往对它的错误处理模式有一定的看法。根据你对其他语言的经验,你可能习惯于不同的方法。这就是为什么我决定要写这篇文章,尽管有点固执己见,但我认为吸收我的经验在辩论中是有用的。 我想要解决的主要问题是,很难去强制良好的错误处理实践,错误没有堆栈追踪,并且错误处理本身太冗长。不过,我已经看到了一些潜在的解决方案或许能帮助解决一些问题。
写 Go 的人往往对它的错误处理模式有一定的看法。按不同的语言经验,人们可能有不同的习惯处理方法。这就是为什么我决定要写这篇文章,尽管有点固执己见,但我认为吸收我的经验是有用的。我想要讲的主要问题是,很难去强制良好的错误处理实践,经常错误没有堆栈追踪,并且错误处理本身太冗长。不过,我已经看到了一些潜在的解决方案或许能帮助解决一些问题。
### 与其他语言的快速比较
[在 Go 中,所有的错误是值][1]。因为这点,相当多的函数最后会返回一个 `error`, 看起来像这样:
[在 Go 中,所有的错误是值][1]。因为这点,相当多的函数最后会返回一个 `error`, 看起来像这样:
```
func (s *SomeStruct) Function() (string, error)
@ -21,7 +21,7 @@ if err != nil {
}
```
另外一种是在其他语言中如 Java、C#、Javascript、Objective C、Python 等使用的 `try-catch` 模式。如下你可以看到与先前的 Go 示例类似的 Java 代码,声明 `throws` 而不是返回 `error`
另外一种方法,是在其他语言中如 Java、C#、Javascript、Objective C、Python 等使用的 `try-catch` 模式。如下你可以看到与先前的 Go 示例类似的 Java 代码,声明 `throws` 而不是返回 `error`
```
public String function() throws Exception
@ -39,7 +39,7 @@ catch (Exception e) {
}
```
当然,还有其他的不同。如,`error` 不会使你的程序崩溃,然而 `Exception` 会。还有其他的一些,我希望在在本篇中专注在这些上
当然,还有其他的不同。如,`error` 不会使你的程序崩溃,然而 `Exception` 会。还有其他的一些,在本篇中会专门提到这些
### 实现集中式错误处理
@ -68,7 +68,7 @@ func viewCompanies(w http.ResponseWriter, r *http.Request) {
}
```
这并不是一个好的解决方案,因为我们不得不重复在所有的处理函数中处理错误。为了能更好地维护,最好能在一处地方处理错误。幸运的是,[在 Go 的博客中Andrew Gerrand 提供了一个替代方法][2]可以完美地实现。我们可以错见一个处理错误的类型
这并不是一个好的解决方案,因为我们不得不重复在所有的处理函数中处理错误。为了能更好地维护,最好能在一处地方处理错误。幸运的是,[在 Go 的博客中Andrew Gerrand 提供了一个替代方法][2],可以完美地实现。我们可以创建一个处理错误的 Type
```
type appHandler func(http.ResponseWriter, *http.Request) error
@ -109,9 +109,9 @@ func viewUsers(w http.ResponseWriter, r *http.Request) error {
调用链可能会相当深,在整个过程中,各种错误可能在不同的地方实例化。[Russ Cox][4]的这篇文章解释了如何避免遇到太多这类问题的最佳实践:
在 Go 中错误报告的部分约定是函数包含相关的上下文、包含正在尝试的操作(比如函数名和它的参数)
在 Go 中错误报告的部分约定是函数包含相关的上下文,包括正在尝试的操作(比如函数名和它的参数)。”
给出的例子是 OS 包的一个调用:
给出的例子是 OS 包的一个调用:
```
err := os.Remove("/tmp/nonexist")
@ -140,11 +140,11 @@ if err != nil {
}
```
这意味着错误发生时它们没有交流
这意味着错误何时发生并没有传递出来
应该注意的是,所有这些错误都可以在 `Exception` 驱动的模型中发生 - 糟糕的错误信息、隐藏异常等。那么为什么我认为该模型更有用?
如果我们在处理一个糟糕的异常消息_我们仍然能够了解堆栈中发生了什么_。因为堆栈跟踪这引发了一些我对 Go 不了解的部分 - 你知道 Go 的 `panic` 包含了堆栈追踪,但是 `error` 没有。我认为推论是 `panic` 可能会使你的程序崩溃,因此需要一个堆栈追踪,而处理错误并不会,因为它会假定你在它发生的地方做一些事。
如果我们在处理一个糟糕的异常消息_我们仍然能够了解它发生在调用堆栈中什么地方_。因为堆栈跟踪这引发了一些我对 Go 不了解的部分 - 你知道 Go 的 `panic` 包含了堆栈追踪,但是 `error` 没有。我推测可能是 `panic` 会使你的程序崩溃,因此需要一个堆栈追踪,而处理错误并不会,因为它会假定你在它发生的地方做一些事。
所以让我们回到之前的例子 - 一个有糟糕错误信息的第三方库,它只是输出了调用链。你认为调试会更容易吗?
@ -170,13 +170,13 @@ github.com/Org/app/core/vendor/github.com/rusenask/goproxy.FuncReqHandler.Handle
如果我们使用 Java 作为一个随意的例子,其中人们犯的一个最愚蠢的错误是不记录堆栈追踪:
```
LOGGER.error(ex.getMessage()) // Doesn't log stack trace
LOGGER.error(ex.getMessage(), ex) // Does log stack trace
LOGGER.error(ex.getMessage()) // 不记录堆栈追踪
LOGGER.error(ex.getMessage(), ex) // 记录堆栈追踪
```
但是 Go 设计中似乎没有这个信息
但是 Go 似乎设计中就没有这个信息。
在获取上下文信息方面 - Russ 还提到了社区正在讨论一些潜在的接口用于剥离上下文错误。了解更多这点或许会很有趣。
在获取上下文信息方面 - Russ 还提到了社区正在讨论一些潜在的接口用于剥离上下文错误。关于这点,了解更多或许会很有趣。
### 堆栈追踪问题解决方案
@ -188,7 +188,7 @@ if errors.Is(err, crashy.Crashed) {
}
```
不过,我认为这个功能能成为语言的一等公民将是一个改进,这样你就不必对类型做一些修改了。此外,如果我们像先前的例子那样使用第三方库,那就可能不必使用 `crashy` - 我们仍有相同的问题。
不过,我认为这个功能如果能成为语言的<ruby>第一类公民<rt>first class citizenship </rt></ruby>将是一个改进,这样你就不必对类型做一些修改了。此外,如果我们像先前的例子那样使用第三方库,它可能没有使用 `crashy` - 我们仍有相同的问题。
### 我们对错误应该做什么?
@ -201,7 +201,7 @@ if err != nil {
}
```
如果我们想要调用大量会返回错误的方法时会发生什么,在同一个地方处理它们么?看上去像这样:
如果我们想要调用大量方法,它们会产生错误,然后在一个地方处理所有错误,这时会发生什么?看上去像这样:
```
err := doSomething()
@ -222,7 +222,7 @@ func doSomething() error {
}
```
这感觉有点冗余,然而在其他语言中你可以将多条语句作为一个整体处理。
这感觉有点冗余,在其他语言中你可以将多条语句作为一个整体处理。
```
try {
@ -245,9 +245,9 @@ public void doSomething() throws SomeErrorToPropogate {
}
```
我个人认为这两个例子实现了一件事情,只`Exception` 模式更少冗余更加弹性。如果有什么,我发现 `if err= nil` 感觉像样板。也许有一种方法可以清理?
我个人认为这两个例子实现了一件事情,只`Exception` 模式更少冗余,更加弹性。如果有什么的话,我觉得 `if err= nil` 感觉像样板。也许有一种方法可以清理?
### 将多条语句像一个整体那样发生错误
### 将失败的多条语句做为一个整体处理错误
首先,我做了更多的阅读,并[在 Rob Pike 写的 Go 博客中][7]发现了一个比较务实的解决方案。
@ -317,11 +317,11 @@ if err != nil {
}
```
这可以用,但是并没有帮助太大,因为它最后比标准的 `if err != nil` 检查带来了更多的冗余。我有兴趣听到有人能提供其他解决方案。或许语言本身需要一些方法来以不那么臃肿的方式传递或者组合错误 - 但是感觉似乎是特意设计成不那么做。
这可以用,但是并没有太大帮助,因为它最终比标准的 `if err != nil` 检查带来了更多的冗余。如果有人能提供其他解决方案,我会很有兴趣听。或许这个语言本身需要一些方法来以不那么臃肿的方式传递或者组合错误 - 但是感觉似乎是特意设计成不那么做。
### 总结
看完这些之后,你可能会认为我反对在 Go 中使用 `error`。但事实并非如此,我只是描述了如何将它与 `try catch` 模型的经验进行比较。它是一个用于系统编程很好的语言,并且已经出现了一些优秀的工具。仅举几例有 [Kubernetes][8]、[Docker][9]、[Terraform][10]、[Hoverfly][11] 等。还有小型、高性能、本地二进制的优点。但是,`error` 难以适应。 我希望我的推论是有道理的,而且一些方案和解决方法可能会有帮助。
看完这些之后,你可能会认为我在对 `error` 挑刺儿,由此推论我反对 Go。事实并非如此我只是将它与我使用 `try catch` 模型的经验进行比较。它是一个用于系统编程很好的语言,并且已经出现了一些优秀的工具。仅举几例有 [Kubernetes][8]、[Docker][9]、[Terraform][10]、[Hoverfly][11] 等。还有小型、高性能、本地二进制的优点。但是,`error` 难以适应。 我希望我的推论是有道理的,而且一些方案和解决方法可能会有帮助。
--------------------------------------------------------------------------------