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

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

  • 不重复处理异常;

  • 异常信息中需要包含完整调用栈;

  • 要提供异常的上下文信息;

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函数:

func read(filePath string) (string, error) {
content ,err := ioutil.ReadFile(filePath)
if err != nil {
return "", err
}
return string(content), nil
}

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

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")
}

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

errors.New("Read object file 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)
}

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

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,则都不会在进行后续的属性校验。

check := func(attr interface{}){
if err != nil{
return
}
err = checkAttr(attr)
} check(employ.Name)
check(employ.Age)
check(employ.Salary) return err

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

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嵌套有多可怕,我们没必要一味追求代码的简洁,从而使用各种奇技淫巧去“优化”它,只要代码不冗余,清晰,简单就可以了。

优雅处理Golang中的异常的更多相关文章

  1. Go语言为何说它优雅?-- Golang中的几个常用初始化设计

    对象池化设计: 将池对象通过Channel方式进行借出与还入,利用Go本身的特性还能实现更多限定需求.比如利用select的分支可以进行优雅的限流.超时熔断等操作.   思路:将需要池化的对象通过Ch ...

  2. [译]Golang中的优雅重启

    原文 Graceful Restart in Golang 作者 grisha 声明:本文目的仅仅作为个人mark,所以在翻译的过程中参杂了自己的思想甚至改变了部分内容,其中有下划线的文字为译者添加. ...

  3. iota: Golang 中优雅的常量

    阅读约 11 分钟 注:该文作者是 Katrina Owen,原文地址是 iota: Elegant Constants in Golang 有些概念有名字,并且有时候我们关注这些名字,甚至(特别)是 ...

  4. Golang中设置函数默认参数的优雅实现

    在Golang中,我们经常碰到要设置一个函数的默认值,或者说我定义了参数值,但是又不想传递值,这个在python或php一类的语言中很好实现,但Golang中好像这种方法又不行.今天在看Grpc源码时 ...

  5. Golang中的自动伸缩和自防御设计

    Raygun服务由许多活动组件构成,每个组件用于特定的任务.其中一个模块是用Golang编写的,负责对iOS崩溃报告进行处理.简而言之,它接受本机iOS崩溃报告,查找相关的dSYM文件,并生成开发者可 ...

  6. 延宕执行,妙用无穷,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中defer关键字延迟调用机制使用EP17

    先行定义,延后执行.不得不佩服Go lang设计者天才的设计,事实上,defer关键字就相当于Python中的try{ ...}except{ ...}finally{...}结构设计中的finall ...

  7. 夯实Java基础系列10:深入理解Java中的异常体系

    目录 为什么要使用异常 异常基本定义 异常体系 初识异常 异常和错误 异常的处理方式 "不负责任"的throws 纠结的finally throw : JRE也使用的关键字 异常调 ...

  8. 在Golang中如何正确地使用database/sql包访问数据库

    本文记录了我在实际工作中关于数据库操作上一些小经验,也是新手入门golang时我认为一定会碰到问题,没有什么高大上的东西,所以希望能抛砖引玉,也算是对这个问题的一次总结. 其实我也是一个新手,机缘巧合 ...

  9. Golang中如何正确的使用sarama包操作Kafka?

    Golang中如何正确的使用sarama包操作Kafka? 一.背景 在一些业务系统中,模块之间通过引入Kafka解藕,拿IM举例(图来源): 用户A给B发送消息,msg_gateway收到消息后,投 ...

随机推荐

  1. 套接字传输(TCP简单使用)

  2. [CF1519D] Maximum Sum of Products (暴力)

    题面 有两个长为 n n n 的序列 a a a 和 b b b,至多反转 a a a 的一个子区间,最大化 ∑ i = 1 n a i ⋅ b i \sum_{i=1}^na_i\cdot b_i ...

  3. Java基础——02

    今日学习 Java API Scanner package cn.lsl.day03.demo01; //导包 import java.util.Scanner; public class demo0 ...

  4. kettle通过SSH连接Mysql数据库(SSH隧道)

    kettle通过SSH连接Mysql数据库(SSH隧道) Kettle无法直接连接使用SSH通道的MySQL,通过SSH隧道(推荐)或者需要借助工具PuTTY(不用时需要注销不推荐),具体操作如下所示 ...

  5. Django CSRF验证失败. 请求被中断.

    当页面中form使用POST方式向后台提交时,报如下错误: 禁止访问 (403) CSRF验证失败. 请求被中断. Help Reason given for failure: ​ CSRF toke ...

  6. Kibana管理

    这里是用来管理您的 kibana 运行时配置的地方,包括初始化配置和后续的索引模式配置.高级设置等.您可以调整 kibana 自身的行为,也可以编辑您通过 kibana 保存的查询.视图.仪表板等各种 ...

  7. 使用ConfigMap配置您的应用程序

    转载自:https://kuboard.cn/learning/k8s-intermediate/config/config-map.html ConfigMap 作为 Kubernetes API ...

  8. git-flow模型

    git-flow 是在 git branch 和 git tag 基础上封装出来的代码分支管理模型,把实际开发模拟称 master develop feature release hotfix sup ...

  9. Python中list列表的常见操作

    Python的list是一个列表,用方括号包围,不同元素间用逗号分隔. 列表的数据项不需要具有相同的类型.(列表还可以嵌套,即列表中的列表) 每个元素都有一个索引(表示位置),从0开始:可以用索引-1 ...

  10. WPF绘制圆形调色盘

    本文使用writeableBitmap类和HSB.RGB模式来绘制圆形的调色盘. 开源项目地址:https://github.com/ZhiminWei/Palette RGB为可见光波段三个颜色通道 ...