正确的错误处理是困难的

正确的错误处理是困难的

原文:https://medium.com/hackernoon/correct-error-handling-is-hard-307ea72759c7

就在最近,我在使用谷歌云平台的 Go 客户端时,被提醒了正确的错误处理是多么困难。

我写的代码是这样的:

一切似乎都很好,除了my-object没有被创造出来!

更仔细地检查整个事情,结果是writer.Close()(第 2 行)返回一个错误,说客户端没有被授权。

我的第一反应是,gcpClient.Bucket(“my-gcp-bucket”).Object(“my-object”).NewWriter(context.TODO())io.Copy(writer, src)应该已经返回了那个错误。但是仔细想想,我意识到这是不可能的,此外,一个 I/O 对象只有在关闭它的时候才返回一个错误是完全没问题的。

关键是,I/O 对象可能会在以下情况下返回错误:(1)获取它时,(2)读取/写入它时,或者(3)关闭它时。这是合同的一部分。

所以常用的defer ioObject.Close()依赖于假设Close()返回的错误可以忽略。一般来说这是不正确的。例子是上面例子中的作者,甚至是一个简单的os.File

来自https://linux.die.net/man/2/close:

不检查 close ()的返回值是一个常见但严重的编程错误。很有可能先前的 (2) 操作上的错误是在最后的关闭()时首次报告的。关闭文件时不检查返回值可能会导致数据无声丢失。

不幸的是,Go 博客——推迟、恐慌和恢复建议:

如您所见,没有处理任何Close()错误。

正确的错误处理

如果有正确的错误处理,这个版本会是什么样子?像这样:

什么变了?我们移除了defer调用,并在所有代码路径中用显式的Close调用替换它们。现在,当在Copy(第 13 行)期间发生错误时,我们返回一个错误,但是关闭之前的两个文件(第 15–17 行)。我们忽略来自Close的错误,因为这要么成功,要么失败,但在这一点上,它可能只是已经发生的前一个错误的结果。复制完成后,我们关闭文件,但是显式地处理这些调用的错误,不要忽略它们。

这很啰嗦吗?是的,它是!当您正确处理错误时,代码会变得冗长。当生产中出现问题时,它也是你的生命线。

不幸的是,这段代码也有一些问题:

  • 它包含许多重复的行
  • 很难把Close叫逻辑正确
  • 当某个地方发生紧急情况时,它不会关闭文件

我们能做得更好吗?

更安全的方法

原来有一个小帮手,我称之为SafeCloser,可以帮忙:

这实际上只是一个安全措施,所以你想关闭一个Closer多次。很多Closer的实现已经是幂等的了,但是不能保证。SafeCloser是保证。

它是如何工作的?对于每个Closer,你只需创建一个同伴SafeCloser,并通过这个SafeCloser关闭你的Closer

我们的CopyFile功能示例:

请注意我是如何在每个defer块之前声明一个SafeCloser并在其中使用的(第 6、7、13、14 行)。当我们显式关闭文件时,在底部(第 21 和 26 行)使用相同的SafeCloser

我们避免了重复行,并正确处理了混乱。

为了避免误用,我故意没有用SafeCloser实现io.Closer接口。而不是打算把任何io.Closer包起来,传来传去,随便关几次。相反,这是一个非常具体的用例,根据您是处于失败模式还是成功模式,您会以不同的方式处理Close呼叫。

摘要

正确的错误处理是困难的。大多数情况下,这是不正确的。像上面的SafeCloser这样的小助手可以帮助您正确处理错误,同时尽可能保持简单。

如果你喜欢这个帖子,你可能也会对我的后续帖子感兴趣,“ 纠错很难,Part 2 ”。

黑客中午是黑客如何开始他们的下午。我们是这个家庭的一员。我们现在接受投稿并乐意讨论广告&赞助机会。

要了解更多信息,请阅读我们的“关于”页面在脸书上给我们点赞/发消息,或者简单地说, tweet/DM @HackerNoon。

如果你喜欢这个故事,我们推荐你阅读我们的最新科技故事趋势科技故事。直到下一次,不要把世界的现实想当然!


本站为非盈利网站,作品由网友提供上传,如无意中有侵犯您的版权,请联系删除