前言

  go语言的error处理方式,在目前流行的编程语言中属于刺头。似乎天生就是用来有别于他人标记。TIOBE排行榜全十除了C语言,无一例外是try catch的阵营。而排在go之前的语言除了C与perl外,同样是try catch的忠实拥趸。那么go的设计者为什么要这么做呢,只是为博人眼球吗?

关于error

  在go语言的定义中,error不一定表示一个错误,它也可以表示其他信息。在标准库中可以看到如文件尾io.EOF的定义,而第三方库中亦有如jdbc驱动中的sql.ErrNoRows的使用,由此可见,在go中error完全可以看作是一种特殊的返回值,以帮助调用方获知被调用函数的执行情况而决定后续的代码逻辑。error的设计,按照流行的说辞就是让程序员面向异常编程,脑海里要时刻记得处理error。虽然这并没有什么不对,毕竟现在也提倡防御性编程,但就因为eerror的笼统且go支持多返回值,导致错误处理代码严重影响正常的业务逻辑,多返回值带来的判断组合成倍的增长。我认为go的error设计并没有摆脱C语言的影子,甚至只是换了个方式来表达而已,仍然是值模式。此时的go 2.0草案已着手解决这个问题,只是终究还是回归了类try catch模式。

//当前go 1的错误处理方式
f, err := os.Open(fileName)
if err != nil {
// handle error here
}
//使用GO 2草案中的错误处理方式
func CopyFile(src, dst string) error {
handle err {//类try-catch模式
return fmt.Errorf("copy %s %s: %v", src, dst, err)
} r := check os.Open(src)
defer r.Close() w := check os.Create(dst)
handle err {
w.Close()
os.Remove(dst) // (only if a check fails)
} check io.Copy(w, r)
check w.Close()
return nil
}

最佳实践

  吐槽归吐槽,但还是要回归主题。go的异常处理由error、panic、recover组成,可在作用上等同于java的Exception、throw、catch/finally。异常处理是任何一门编程语言都需要考虑如何处理的事情,特别是对于想要成为系统性、项目性的语言。go的异常处理的特殊性使得其在诞生之初,就被吐槽得最多,因而也被研究实践得最多。不同的人使用同一种语言敲出的代码可以有完全不一样的感觉,自然也就有良莠之分。所以语言永远都只是一种工具,如何使用工具及如何用好工具才是关键。就像用java抛出Exception,当throws的细分异常超过一个时,你会立刻马上想到抛出一个大Exception。这就是工具的滥用,相信设计者最初的目的决不在此。java异常的严谨性在于可以让调用者清楚地看到将要调用的方法的可控性,无异常抛出声明的方法可以放心调用,不需要处理异常;反正则可以知道被调用的方法会抛出哪些细分的异常,然后在调用的地方小心地处理这些异常(虽然java的try-catch语法很啰嗦,也很丑)。当然这一切都建立在所有的团队成员都深刻地认识到异常的最佳实践的前提下。go也不例外,也有一些最佳实践。

  java将异常分为错误(error)、未检视异常(unchecked exception)、已检视异常(checked exception),未检视、已检视异常这两个概念即使多年的老鸟也依然分不清楚,包括我在内。其实简单地划分就是当前可妥善处理或当前无能为力。已检视异常属于当前可妥善处理的范围,即

程序可对这类异常提供分支、恢复等处理方案的异常,比如FileNotFoundException,解决方案可以是读取备选文件或者跳过;而error、未检视异常则属于当前无能为力的异常(在写代码的时候不知道或者意料不到),这类异常通常是在运行时才能发现且通常是程序员的错或者依赖的运行环境异常(如操作系统、JVM)。而go在规范上则不区分,并且建议要妥善处理每一个方法返回的异常,哪怕是只打印一行日志。一个典型的go处理异常的例子如下:

package main

import (
"fmt"
"github.com/go-redis/redis"
) func main() {
defer func() {
//尝试处理panic
//注意: recover只在defer函数中有效
if p := recover(); p != nil {
err, ok := p.(error)
if ok {
//此处只是简单的打印error
fmt.Printf("panic, %s\n", err.Error())
}
//打印异常堆栈
fmt.Printf("panic stack: %s\n", string(debug.Stack()))
}
}()
//连接redis
client := redis.NewClient(&redis.Options{
Addr: "localhost",
DB: 0,
MinIdleConns: 10,
})
pong, err := client.Ping().Result()
if err != nil {
//异常发生, 程序中断执行,转而执行defer声明的函数
panic(err)
}
fmt.Printf("连接redis成功, ping: %s\n", pong)
}

  从上述代码中可以看到,如果调用了很多可能发生错误的方法,整个代码视界内,将会出现一堆的if err!= nil这样的语句,然后里面就一个打印错误日志的语句。总之让人有点不爽,但习惯了其实也还好。那么对于自己编写的方法,应该如何定义error呢?

最佳实践 方式 说明
位置 最后 返回值列表最后一个值且不要返回多个error
统一 同类型的错误,消息体保持一致 使用error.New创建error或者使用pkg/errors包
二值 bool 布尔逻辑类的方法使用bool代替error
释放资源 在操作资源时,定义defer函数用于资源释放 defer函数会被按先后顺序入栈,执行时按相反的顺序出栈执行
非法分支 按业务逻辑不应该出现的分支可使用panic中止执行,并交由recover处理 -
非法参数 对于Must类开头的函数,使用panic 这种设计可以避免调用方处理error,但调用方需小心处理panic,运行的程序不应该因为panic崩溃

  我认为异常处理的关键在于如何告知调用方你写的函数出错了,哪里出错了,出了什么错,然后交由调用方处理(语言给调用方提供工具中断程序或恢复异常等方式)。在这个指导方针下,我觉得java显然对调用方更为友好,从方法声明就可以知道方法会抛出哪些异常,不需要查看源代码,然后一个大代码块的try,catch各个细分的Exception(不分青红皂白直接catch Exception的程序员不是好程序员)。

  除此之外,go有很多增加魔力值的点,比如type、defer、类型推断、闭包、channel、无括号、cgo,不足之处在于标准库太弱、没有泛型、切片与数组易混。此外,go没有函数重载,虽然这并不是什么大问题。

请关注公众号

不一样的go语言-error的更多相关文章

  1. GO语言Error处理

    Go语言没有提供像Java.C#.Python语言中的try...catch异常处理方式,而是通过函数返回值逐层往上抛.好处就是避免漏掉本应处理的错误.坏处是代码啰嗦. 错误与异常区别 错误指的是可能 ...

  2. c语言 error C4996: 'strupr': The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name

    问题: 在使用visual studio 2013,进行调试执行代码时,出现如下错误: error C4996: 'strupr': The POSIX name for this item is d ...

  3. c语言-error C2440: “static_cast”: 无法从“UINT (__thiscall CHyperLink::* )(CPoint)”转换为“LRESULT (__thiscall CWnd::* )(CPoint)”

    出现这个错误的原因可是“人力不可抗拒”之原因造成的,因为旧版本的 ON_WM_NCHITTEST 宏使用了 UINT (__thiscall CWzButton::* )(CPoint); 类型的类成 ...

  4. 07. Go 语言接口

    Go 语言接口 接口本身是调用方和实现方均需要遵守的一种协议,大家按照统一的方法命名参数类型和数量来协调逻辑处理的过程. Go 语言中使用组合实现对象特性的描述.对象的内部使用结构体内嵌组合对象应该具 ...

  5. golang error错误处理

    error定义 数据结构 go语言error是一普通的值,实现方式为简单一个接口. // The error built-in interface type is the conventional i ...

  6. flask框架(二):简单的登录demo

    一:main.py # -*- coding: utf-8 -*- # @Author : Felix Wang # @time : 2018/7/3 22:58 from flask import ...

  7. 3_JSP

    一. 引言 1.1 现有问题 在之前学习Servlet时, 服务器通过Servlet响应客户端页面, 有什么不足之处? 开发方式麻烦: 继承父类, 覆盖方法, 配置web.xml或注解 代码修改麻烦: ...

  8. [转载]VS2012编译C语言scanf函数error的解决方法

    在VS 2012 中编译 C 语言项目,如果使用了 scanf 函数,编译时便会提示如下错误: error C4996: 'scanf': This function or variable may ...

  9. 与非java语言使用RSA加解密遇到的问题:algid parse error, not a sequence

    遇到的问题 在一个与Ruby语言对接的项目中,决定使用RSA算法来作为数据传输的加密与签名算法.但是,在使用Ruby生成后给我的私钥时,却发生了异常:IOException: algid parse ...

随机推荐

  1. SpringMvc + Jsp+ 富文本 kindeditor 进行 图片ftp上传nginx服务器 实现

    一:html 原生态的附件上传 二:实现逻辑分析: 1.1.1 需求分析 Common.js 1.绑定事件 2.初始化参数 3.上传图片的url: /pic/upload 4.上图片参数名称: upl ...

  2. linux之各目录作用

    /opt目录 目录用来安装附加软件包,用户调用软件包程序放在目录/opt/package_name/bin下,package_name是安装软件包的名称 /etc目录 是用来放一些核心的配置文件 附各 ...

  3. svn上check下来的项目,用idea打开,菜单栏没有svn工具解决办法

    1.用idea打开你的项目(idea已经配置过小乌龟了) 2.菜单栏点击VCS,Enable Version Control Integration... 3.选择Subversion 4.这时候,菜 ...

  4. 字符串为空的比较 ==与equals() 区别(キ`゚Д゚´)!!基础很重要 !!!

    情况描述:我提交的代码,让老大审批了一次,讲真的,对于我来说受益匪浅,其中有一个印象很深的内容:一个字符串是否为空的判断,我以前敲代码一直都是这样写的,可是从来都没有意识到这个东西. 代码: if(s ...

  5. Python基础之面向对象进阶一

    一.isinstance(obj,cls)和issubclass(sub,super) 1.isinstance(obj,cls)检查obj是否是类 cls 的对象 class A: pass obj ...

  6. tomcat 报错处理

    一.tomcat报错找不到资源集市 原因:tomcat的配置文件sever.xml 里的 docbase配置被Eclispe修改了 解决方法:修改回来 <Context docBase=&quo ...

  7. laravel 多检索条件列表查询

    public function indexQuestions(Request $request, ResponseFactoryContract $response, QuestionModel $q ...

  8. 爬虫----beautifulsoup的简单使用

    beautifulSoup使用: 简单来说,Beautiful Soup是python的一个库,最主要的功能是从网页抓取数据. pip3 install beautifulsoup4 解析器 Beau ...

  9. CF1005F

    这题不错... 首先,不难看到他想让你求出的是最短路树 然后,考虑到所有边权均为1,所以可以采用bfs直接生成最短路树 至于方案的储存,可以在加边的时候同时记录边的编号,然后对每个点维护一个能转移他的 ...

  10. gerrit原理

      个人理解: 这个就是审核代码是否合理性的工具,一般是资深研发人工确认代码是否存在缺陷,通过发送邮件通知变化. 也可理解为这个是个git服务器,多一个代码审查的功能. 但是它是个web界面,方便管理 ...