设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间00 现在时间00 缓存数据 对自己狠一点,逼自己努力,再过几年你将会感谢今天发狠的自己、恨透今天懒惰自卑的自己。晚安!

对自己狠一点,逼自己努力,再过几年你将会感谢今天发狠的自己、恨透今天懒惰自卑的自己。晚安!

查看: 2049|回复: 5

Golang中重复错误处理的优化方法

[复制链接]

  离线 

TA的专栏

  • 打卡等级:热心大叔
  • 打卡总天数:225
  • 打卡月天数:1
  • 打卡总奖励:3302
  • 最近打卡:2025-12-11 03:17:56
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
424
主题
391
精华
0
金钱
4551
积分
872
注册时间
2023-1-5
最后登录
2025-12-11

发表于 2023-5-19 22:47:26 | 显示全部楼层 |阅读模式
Golang 错误处理最让人头疼的问题就是代码里充斥着「if err != nil」,它们破坏了代码的可读性,本文收集了几个例子,让大家明白如何优化此类问题。
让我们看看 Errors are values 中提到的一个 io.Writer 例子:
  1. _, err = fd.Write(p0[a:b])
  2. if err != nil {
  3. return err
  4. }
  5. _, err = fd.Write(p1[c:d])
  6. if err != nil {
  7. return err
  8. }
  9. _, err = fd.Write(p2[e:f])
  10. if err != nil {
  11. return err
  12. }
复制代码
如上代码乍一看无法直观的看出其本来的意图是什么,改进版:
  1. type errWriter struct {
  2. w io.Writer
  3. err error
  4. }

  5. func (ew *errWriter) write(buf []byte) {
  6. if ew.err != nil {
  7. return
  8. }
  9. _, ew.err = ew.w.Write(buf)
  10. }

  11. ew := &errWriter{w: fd}
  12. ew.write(p0[a:b])
  13. ew.write(p1[c:d])
  14. ew.write(p2[e:f])
  15. if ew.err != nil {
  16. return ew.err
  17. }
复制代码
通过自定义类型 errWriter 来封装 io.Writer,并且封装了 error,新类型有一个 write 方法,不过其方法签名并没有返回 error,而是在方法内部判断一旦有问题就立刻返回,有了这些准备工作,我们就可以把原本穿插在业务逻辑中间的错误判断提出来放到最后来统一调用,从而在视觉上保证让人可以直观的看出代码本来的意图是什么。
让我们再看看 Eliminate error handling by eliminating errors 中提到的另一个 io.Writer 例子:
  1. type Header struct {
  2. Key, Value string
  3. }

  4. type Status struct {
  5. Code int
  6. Reason string
  7. }

  8. func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
  9. _, err := fmt.Fprintf(w, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)
  10. if err != nil {
  11. return err
  12. }

  13. for _, h := range headers {
  14. _, err := fmt.Fprintf(w, "%s: %s\r\n", h.Key, h.Value)
  15. if err != nil {
  16. return err
  17. }
  18. }

  19. if _, err := fmt.Fprint(w, "\r\n"); err != nil {
  20. return err
  21. }

  22. _, err = io.Copy(w, body)
  23. return err
  24. }
复制代码
第一感觉既然错误是 fmt.Fprint 和 io.Copy 返回的,是不是我们要重新封装一下它们?实际上真正的源头是它们的参数 io.Writer,因为直接调用 io.Writer 的 Writer 方法的话,方法签名中有返回值 error,所以每一步 fmt.Fprint 和 io.Copy 操作都不得不进行重复的错误处理,看上去是坏味道,改进版:
  1. type errWriter struct {
  2. io.Writer
  3. err error
  4. }

  5. func (e *errWriter) Write(buf []byte) (int, error) {
  6. if e.err != nil {
  7. return 0, e.err
  8. }

  9. var n int
  10. n, e.err = e.Writer.Write(buf)
  11. return n, nil
  12. }

  13. func WriteResponse(w io.Writer, st Status, headers []Header, body io.Reader) error {
  14. ew := &errWriter{Writer: w}
  15. fmt.Fprintf(ew, "HTTP/1.1 %d %s\r\n", st.Code, st.Reason)

  16. for _, h := range headers {
  17. fmt.Fprintf(ew, "%s: %s\r\n", h.Key, h.Value)
  18. }

  19. fmt.Fprint(ew, "\r\n")
  20. io.Copy(ew, body)

  21. return ew.err
  22. }
复制代码
通过自定义类型 errWriter 来封装 io.Writer,并且封装了 error,同时重写了 Writer 方法,虽然方法签名中仍然有返回值 error,但是我们单独保存了一份 error,并且在方法内部判断一旦有问题就立刻返回,有了这些准备工作,新版的 WriteResponse 不再有重复的错误判断,只需要在最后检查一下 error 即可。
类似的做法在 Golang 标准库中屡见不鲜,让我们继续看看 Eliminate error handling by eliminating errors 中提到的一个关于 bufio.Reader 和 bufio.Scanner 的例子:
  1. func CountLines(r io.Reader) (int, error) {
  2. var (
  3. br = bufio.NewReader(r)
  4. lines int
  5. err error
  6. )

  7. for {
  8. _, err = br.ReadString('\n')
  9. lines++
  10. if err != nil {
  11. break
  12. }
  13. }

  14. if err != io.EOF {
  15. return 0, err
  16. }

  17. return lines, nil
  18. }
复制代码
我们构造一个 bufio.Reader,然后在一个循环中调用 ReadString 方法,如果读到文件结尾,那么 ReadString 会返回一个错误(io.EOF),为了判断此类情况,我们不得不在每次循环时判断「if err != nil」,看上去这是坏味道,改进版:
  1. func CountLines(r io.Reader) (int, error) {
  2. sc := bufio.NewScanner(r)
  3. lines := 0

  4. for sc.Scan() {
  5. lines++
  6. }

  7. return lines, sc.Err()
  8. }
复制代码
实际上,和 bufio.Reader 相比,bufio.Scanner 是一个更高阶的类型,换句话简单点来说的话,相当于是 bufio.Scanner 抽象了 bufio.Reader,通过把低阶的 bufio.Reader 换成高阶的 bufio.Scanner,循环中不再需要判断「if err != nil」,因为 Scan 方法签名不再返回 error,而是返回 bool,当在循环里读到了文件结尾的时候,循环直接结束,如此一来,我们就可以统一在最后调用 Err 方法来判断成功还是失败,看看 Scanner 的定义:
  1. type Scanner struct {
  2. r   io.Reader // The reader provided by the client.
  3. split  SplitFunc // The function to split the tokens.
  4. maxTokenSize int  // Maximum size of a token; modified by tests.
  5. token  []byte // Last token returned by split.
  6. buf   []byte // Buffer used as argument to split.
  7. start  int  // First non-processed byte in buf.
  8. end   int  // End of data in buf.
  9. err   error  // Sticky error.
  10. empties  int  // Count of successive empty tokens.
  11. scanCalled bool  // Scan has been called; buffer is in use.
  12. done   bool  // Scan has finished.
  13. }
复制代码
可见 Scanner 封装了 io.Reader,并且封装了 error,和我们之前讨论的做法一致。有一点说明一下,实际上查看 Scan 源代码的话,你会发现它不是通过 err 来判断是否结束的,而是通过 done 来判断是否结束,这是因为 Scan 只有遇到文件结束的错误才退出,其它错误会继续执行,当然,这只是具体的细节问题,不影响我们的结论。
通过对以上几个例子的分析,我们可以得出优化重复错误处理的大概套路:通过创建新的类型来封装原本干脏活累活的旧类型,同时在新类型中封装 error,新旧类型的方法签名可以保持兼容,也可以不兼容,这个不是关键的,视客观情况而定,至于具体的逻辑实现,先判断有没有 error,如果有就直接退出,如果没有就继续执行,并且在执行过程中保存可能出现的 error 以便后面操作使用,最后通过统一调用新类型的 error 来完成错误处理。提醒一下,此方案的缺点是要到最后才能知道有没有错误,好在如此的控制粒度在多数时候并无大碍。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对晓枫资讯的支持。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:29
  • 打卡月天数:2
  • 打卡总奖励:385
  • 最近打卡:2025-12-13 21:44:50
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
433
积分
60
注册时间
2023-1-1
最后登录
2025-12-13

发表于 2023-5-23 00:06:29 | 显示全部楼层
看看,学习学习~~~~
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

  • 打卡等级:即来则安
  • 打卡总天数:24
  • 打卡月天数:0
  • 打卡总奖励:313
  • 最近打卡:2025-11-15 12:38:53
等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
352
积分
54
注册时间
2023-3-28
最后登录
2025-11-15

发表于 2023-10-30 23:11:04 | 显示全部楼层
谢谢分享~~~~~
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
22
积分
24
注册时间
2022-12-25
最后登录
2022-12-25

发表于 2024-4-7 18:08:34 | 显示全部楼层
感谢楼主分享。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
15
积分
10
注册时间
2022-12-25
最后登录
2022-12-25

发表于 2024-10-9 23:29:44 | 显示全部楼层
顶顶更健康!!!
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~

  离线 

TA的专栏

等级头衔

等級:晓枫资讯-列兵

在线时间
0 小时

积分成就
威望
0
贡献
0
主题
0
精华
0
金钱
17
积分
14
注册时间
2022-12-25
最后登录
2022-12-25

发表于 2025-1-19 06:33:33 | 显示全部楼层
感谢楼主,顶。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
4楼
5楼
6楼

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表