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

我们在使用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中的异常的文章就介绍到这了,更多相关Golang处理异常内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言error的设计理念及背景演化详解

    目录 背景 各语言中 Error 的演化 C语言 C++ Go 中 Error 的理念 1. 区分 Error 和 Exception 2.Error是一个接口 总结 背景 作为一门相对新兴的语言,Go 可以说是站在巨人的肩膀上.从 Go 语法上,我们可以看出设计者对其有许多严肃的思考.其中 Error 的处理就是极具标志性的一项. 值得注意的是,Go 中的 Error 一直是一个有争议的内容.很多议案不断的提出又推翻,即便是官方的 error 库也在多个版本中不断迭代.本文内容基于 1.19.

  • Golang错误处理方式异常与error

    目录 一.异常 二.错误 Go 提供了两种处理错误 方式, 一 种是借助 panic和 recover 的抛出捕获机制, 另一种使用error 错误类型 一.异常 1.go没有try/catch,而是使用panic/recover. panic包出异常,后续代码不再执行 recover再defer中捕获异常,使groutine(即程序)能够正常退出. 1)只有panic,而没有recover,程序宕机无法正常退出 package main import "fmt" func send(

  • Go error的使用方式详解

    目录 概述 error使用方式 1.直接判等 2.组合error接口,构建更强大的error接口 3.Errno模式 4.Go1.13的Wrap模式 5. Go版本低时的链式error 概述 当我们需要在Go项目中设计error,就不得不先知道Go error几种常用方法.标准库是一个非常好的学习方式,除此之外Go1.13的errors特性也需要掌握. error使用方式 1.直接判等 这里的判等又分为变量判等和类型判等.适用于pkg中预先定义好了多个error变量或类型,err只可能是这些变量

  • Go错误和异常CGO fallthrough处理教程详解

    目录 fallthrough 代码示例 执行结果 CGO 错误&异常 处理错误:error 处理异常:panic&recover panic&defer defer执行顺序 总结 对比Java.C++ 错误异常互相转换 fallthrough 在一个 switch 块内,每个 case 无需声明 break 来终止,如果想顺序执行使用fallthrough: 如果我们想强制执行满足条件case的后一个case,也可以通过设置fallthrough的方式: 代码示例 package

  • Go语音开发中常见Error类型处理示例详解

    目录 前言 透明错误处理策略 带来的问题 哨兵(Sentinel)错误处理策略 带来的问题 1.对errors.Error()的依赖 2.定义的错误类型会被公开 Error types 带来的问题 不透明错误处理策略(Opaque errors) 带来的问题 总结 前言 上文我们了解了 Error 在Go 中的设计理念.单从理念上来看, Error 的设计似乎有利于逻辑处理.但实际上,在开发过程中,我们还会遇到各种各样的困难.为了优化开发的流程,开发者们发明了很多处理 Error 的做法.今天我

  • Go中的错误和异常处理最佳实践方法

    目录 错误 认识错误 自定义错误 实现原理 异常 认识异常 处理异常 异常处理原则 异常处理实践 错误 认识错误 在Go中,错误是一种表示程序错误状态.包含了在程序在运行时.编译时的状态信息.一般我们在编写Go代码中,都会碰到如下的处理方式. file, err := os.Create("test.txt") fmt.Println(file) if err != nil { fmt.Println(err) return } 我们使用os库创建一个名为test.txt的文件,该方法

  • Go语言学习笔记之错误和异常详解

    目录 错误 异常 总结 错误 Go 语言通过内置的错误接口提供了非常简单的错误处理机制,Error 类型是一个接口类型. type error interface { Error() string } 可以在编码中通过实现 error 接口类型来生成错误信息. 函数通常在最后的返回值中返回错误信息. // 定义一个 DivideError 结构 type DivideError struct { dividee int divider int } // 实现 `error` 接口 func (d

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

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

  • 在 Golang 中实现一个简单的Http中间件

    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很灵活的通过中间件的形式添加一些功能到管道中,一次请求和响应在管道中的执行过程如下 首先, 我定义了三个测试的中间件 Middleware1,2,3 如下 func Middleware1(next http.Handler) http.Handler { return http.HandlerFunc(func(w

  • Golang中实现简单的Http Middleware

    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很灵活的通过中间件的形式添加一些功能到管道中,一次请求和响应在管道中的执行过程如下 首先, 我定义了三个测试的中间件 Middleware1,2,3 如下 func Middleware1(next http.Handler) http.Handler { return http.HandlerFunc(func(w

  • 在 Golang 中实现一个简单的Http中间件过程详解

    本文主要针对Golang的内置库 net/http 做了简单的扩展,通过添加中间件的形式实现了管道(Pipeline)模式,这样的好处是各模块之间是低耦合的,符合单一职责原则,可以很灵活的通过中间件的形式添加一些功能到管道中,一次请求和响应在管道中的执行过程如下 首先, 我定义了三个测试的中间件 Middleware1,2,3 如下 func Middleware1(next http.Handler) http.Handler { return http.HandlerFunc(func(w

  • golang中的defer函数理解

    目录 golang的defer 什么是defer 理解defer defer什么时间执行(defer. return.返回值 三者的执行顺序) defer输出的值,就是定义时的值.而不是defer真正执行时的变量值(注意引用情况) 多个defer,执行顺序 defer的函数一定会执行么? panic情况 os.Exit情况 kill情况(Ctrl+C) 参考文献 golang的defer 什么是defer defer的的官方文档:https://golang.org/ref/spec#Defer

  • Golang中Channel实战技巧与一些说明

    目录 Channel 的一些实战说明 关于 close Channel close Channel 的一些说明 v, ok := <-ch 判断是否 close 优雅判断是否 close 的封装 for-range 读取 Channel 数据 select 读写 Channel 数据 Channel 的读写超时机制[select + timeout] TryEnqueue 无阻塞写 Channel 数据 Channel 常见错误和根因分析 fatal error: all goroutines

  • Golang中基于HTTP协议的网络服务

    目录 一.HTTP协议的网络服务 1.1 使用http.Get函数访问HTTP协议的网络服务 1.2 使用缺省客户端DefaultClient(类型为*http.Client ) 1.3 使用http.Client访问HTTP协议的网络服务 二.http.Client中的Transport字段 (1)http.Transport类型中的DialContext字段 (2)http.Transport类型中的其它字段 三.为什么会出现空闲的连接 3.1 空闲连接的产生 3.2 杜绝空闲连接的产生 四

  • Golang异常处理之优雅地控制和处理异常

    目录 panic和recover使用 使用示例 注意 子函数panic主函数recover 子协程panic主函数recover 使用总结 使用panic的几点担心 性能 性能对比 安全 panic和recover使用 Go语言不支持传统的 try…catch…finally 这种异常,因为Go语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱.在Go语言中,设计者们推荐使用多值返回来返回错误.遇到真正的异常的情况下(比如除数为 0了).才使用Go中引入的Exception处理

  • golang中defer的关键特性示例详解

    前言 大家都知道golang的defer关键字,它可以在函数返回前执行一些操作,最常用的就是打开一个资源(例如一个文件.数据库连接等)时就用defer延迟关闭改资源,以免引起内存泄漏.本文主要给大家介绍了关于golang中defer的关键特性,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 一.defer 的作用和执行时机 go 的 defer 语句是用来延迟执行函数的,而且延迟发生在调用函数 return 之后,比如 func a() int { defer b() return

  • Golang中如何使用lua进行扩展详解

    前言 最近在项目中需要使用lua进行扩展,发现github上有一个用golang编写的lua虚拟机,名字叫做gopher-lua.使用后发现还不错,借此分享给大家,下面话不多说了,来一起看看详细的介绍吧. 数据类型 lua中的数据类型与golang中的数据类型对应关系作者已经在文档中说明,值得注意的是类型是以L开头的,类型的名称是以LT开头的. golang中的数据转换为lua中的数据就必须转换为L开头的类型: str := "hello" num := 10 L.LString(st

随机推荐