广告位联系
返回顶部
分享到

教你如何优雅处理Golang中的异常

Golang 来源:互联网 作者:佚名 发布时间:2022-11-03 21:36:03 人浏览
摘要

我们在使用Golang时,不可避免会遇到异常情况的处理,与Java、Python等语言不同的是,Go中并没有try...catch...这样的语句块,我们知道在Java中使用try...catch...这种模式不仅能分离的错误与

我们在使用Golang时,不可避免会遇到异常情况的处理,与Java、Python等语言不同的是,Go中并没有try...catch...这样的语句块,我们知道在Java中使用try...catch...这种模式不仅能分离的错误与返回值和参数,也提供了结构化处理异常的可能,通过面向对象的思想,我们可以自定义错误类、子类,它们又可以包装其他错误,确保错误上下文不会丢失。但是在Go中,异常是作为函数返回值,返回给调用方的,这个时候我们如何才能更好的处理异常呢?

对于异常的处理,我们应该把握三个原则:

  • 不重复处理异常;
  • 异常信息中需要包含完整调用栈;
  • 要提供异常的上下文信息;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

func read(filePath string) (string, error) {

  content ,err := ioutil.ReadFile(filePath)

  if err != nil {

    log.Printf("Read file err: %v", err)

    return "", err

  }

  return string(content), nil

}

 

func parse(content string) (Employ, error) {

  // 解析文件得到Employ对象

}

 

func checkAttr(attr interface{}) error {

  // 校验对象属性

}

 

func commitEmployInfoFromFile(filePath string) error {

  content, err := read(filePath)

  if err != nil {

    return errors.New("Read object file error")

  }

  employ, err := parse(content)

  if err != nil {

    return errors.New("Parse object content error")

  }

 

  if err = checkAttr(employ.Name); err != nil {

    return err

  }

  if err = checkAttr(employ.Age); err != nil {

    return err

  }

  if err = checkAttr(employ.Salary); err != nil {

    return err

  }

  return nil

}

我们分析上面的代码,可以很明显看到read函数中违背了【不重复处理异常】的原则,虽然这里仅仅是打印,但是只要你向上抛异常,调用方很有可能再次打印,这就导致日志中存在大量重复信息,不便于分析。因为我们修改read函数:

1

2

3

4

5

6

7

func read(filePath string) (string, error) {

  content ,err := ioutil.ReadFile(filePath)

  if err != nil {

    return "", err

  }

  return string(content), nil

}

再来看看这一部分代码,日志中仅仅打印了错误信息,但是缺少错误堆栈,这样非常不利于问题代码的定位。

1

2

3

4

5

6

7

8

content, err := read(filePath)

  if err != nil {

    return errors.New("Read object file error")

  }

  employ, err := parse(content)

  if err != nil {

    return errors.New("Parse object content error")

  }

上面的代码还有一个问题,那就是错误信息都是简单的字符串信息,缺少上下文信息,比如:

1

errors.New("Read object file error")

我们只能知道是文件读取出错了,但无法得知是哪个文件有问题,因此我们最好加入文件信息到日志中。改良后的代码如下:

1

2

3

4

5

6

7

8

content, err := read(filePath)

if err != nil {

  return fmt.Errorf("Read object file %v error: %v", filePath, err)

}

employ, err := parse(content)

if err != nil {

  return fmt.Errorf("Parse object content error: %v", err)

}

最后,我们再看看这一段代码,这种写法非常常见,很多刚使用Golang的朋友都觉得非常头痛,由于Golang中没有throw或raise机制,所以会导致代码中使用大量if对错误进行处理,非常不优雅。

1

2

3

4

5

6

7

8

9

if err = checkAttr(employ.Name); err != nil {

    return err

  }

  if err = checkAttr(employ.Age); err != nil {

    return err

  }

  if err = checkAttr(employ.Salary); err != nil {

    return err

  }

对于这类代码我们可以使用匿名函数进行简化,我们将checkAttr和err的判断封装在匿名函数check中,一旦某一次check出现error,则都不会在进行后续的属性校验。

1

2

3

4

5

6

7

8

9

10

11

12

check := func(attr interface{}){

    if err != nil{

      return

    }

    err = checkAttr(attr)

  }

 

  check(employ.Name)

  check(employ.Age)

  check(employ.Salary)

   

  return err

当然,这种方式是还需要创建一个匿名函数以及一个error变量,这会让我们的commitEmployInfoFromFile函数显得不太干净,我们可以进一步优化:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

type EmployChecker struct {

  err error

}

 

func (c *EmployChecker) check(attr interface{}) {

  if c.err == nil {

    c.err = checkAttr(attr)

  }

}

 

func commitEmployInfoFromFile(filePath string) error {

  content, err := read(filePath)

  if err != nil {

    return fmt.Errorf("Read object file %v error: %v", filePath, err)

  }

  employ, err := parse(content)

  if err != nil {

    return fmt.Errorf("Parse object content error: %v", err)

  }

 

  checker := EmployChecker{}

  checker.check(employ.Name)

  checker.check(employ.Age)

  checker.check(employ.Salary)

  err = checker.err

 

  return err

}

当然,这种方式是有一定局限性的,它只能在对于同一个业务对象的不断操作下可以简化错误处理,对于多个业务对象的话,还是得需要各种 if err != nil的方式。

其实,对于Go的异常处理,我们不能说Golang不支持try catch,那它就不行,君不见try catch嵌套有多可怕,我们没必要一味追求代码的简洁,从而使用各种技巧去“优化”它,只要代码不冗余,清晰,简单就可以了


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://www.cnblogs.com/alalazy/p/16849272.html
相关文章
  • Golang源码分析之golang/sync之singleflight

    Golang源码分析之golang/sync之singleflight
    1.1. 项目介绍 golang/sync库拓展了官方自带的sync库,提供了errgroup、semaphore、singleflight及syncmap四个包,本次分析singlefliht的源代码。 singleflih
  • 教你如何优雅处理Golang中的异常
    我们在使用Golang时,不可避免会遇到异常情况的处理,与Java、Python等语言不同的是,Go中并没有try...catch...这样的语句块,我们知道在Java中
  • Go语言k8s kubernetes使用leader election实现选举

    Go语言k8s kubernetes使用leader election实现选举
    在kubernetes的世界中,很多组件仅仅需要一个实例在运行,比如controller-manager或第三方的controller,但是为了高可用性,需要组件有多个副本,
  • golang中的defer函数理解
    golang的defer 什么是defer defer的的官方文档:https://golang.org/ref/spec#Defer_statements go语言中defer可以完成延迟功能,当前函数执行完成后再执行defer的
  • Windows系统中搭建Go语言开发环境图文介绍

    Windows系统中搭建Go语言开发环境图文介绍
    本文详细讲述如何在 Windows 系统上搭建 Go语言的开发环境,以供借鉴或参考。文章将介绍Go语言的VSCode、GoLand、Vim三种开发环境,大家可以灵
  • 深入理解Golang channel的应用
    channel是用于 goroutine 之间的同步、通信的数据结构 channel 的底层是通过 mutex 来控制并发的,但它为程序员提供了更高一层次的抽象,封装了
  • 基于GORM实现CreateOrUpdate的方法
    CreateOrUpdate 是业务开发中很常见的场景,我们支持用户对某个业务实体进行创建/配置。希望实现的 repository 接口要达到以下两个要求: 如果
  • Golang中的内存逃逸的介绍
    什么是内存逃逸分析 内存逃逸分析是go的编译器在编译期间,根据变量的类型和作用域,确定变量是堆上还是栈上 简单说就是编译器在编译
  • Golang自旋锁的介绍
    自旋锁 获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。 它是为实现保护共享资源而提出的
  • Go语言读写锁RWMutex的源码

    Go语言读写锁RWMutex的源码
    在前面两篇文章中初见 Go Mutex、Go Mutex 源码详解,我们学习了Go语言中的Mutex,它是一把互斥锁,每次只允许一个goroutine进入临界区,可以保
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计