我们在使用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. 基础1:JS的原型和原型链究竟

    JS的原型和原型链究竟是什么? 1. 从JS创建一个对象开始说起: 1.1 工厂模式创建对象 (缺点是无法知道创建出来的对象是一个什么类型的对象) function createPerson(name ...

  2. 记一次twikoo引发的站点重大事故

    今天我测试私人博客的时候发现twikoo评论发生了错误,显示评论失败:0,我怀疑是我设置的twikoo安全域名有问题,所以我看了整个lssues,找到了我的解决方法! 1.关于配置安全域名后评论消失的 ...

  3. qt C2144 语法错误,需要在类型前添加;(分号)

    可能原因:有部分头文件未以";"结尾.

  4. 项目实践2:项目中的CSS网页布局(常用)

    好家伙, 整个网页做下来,最主要的,自然是css的网页布局(菜鸟好用啊) 我需要一个大概这样的布局: 然后上代码: <!DOCTYPE html> <html> <hea ...

  5. 02_Linux基础-文件系统层次结构-提示符-进程-命令格式-隐藏文件-通配符-绝对相对路径-{1..100}-ls-mkdir-其他基础命令

    02_Linux基础-ls-mkdir-cd-pwd-man-useradd-su-rm-tree-tab-passwd-w-ssh-touch-date-stat-cp-mv-du-文件系统层次结构 ...

  6. format添加未知个参数方法

    一个python巧妙技巧,分享给大家 我的需求是将一个dict的键都format输出,用到了*对字典解包 data = {a: 1, b: 2...} msg = f"{'{} '*len( ...

  7. luogu P2882 [USACO07MAR]Face The Right Way G

    题目描述 Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing forwar ...

  8. 详解字符编码与 Unicode

    人类交流使用 A.B.C.中 等字符,但计算机只认识 0 和 1.因此,就需要将人类的字符,转换成计算机认识的二进制编码.这个过程就是字符编码. ASCII 最简单.常用的字符编码就是 ASCII(A ...

  9. 学会Linux,看完这篇就行了!

    转载请注明出处️ 作者:测试蔡坨坨 原文链接:caituotuo.top/797ab07d.html 你好,我是测试蔡坨坨. 对于测试同学来说,Linux基本属于必学必会内容,招聘要求中基本都会出现L ...

  10. Elasticsearch:使用 IP 过滤器限制连接

    文章转载自:https://elasticstack.blog.csdn.net/article/details/107154165