前言

  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. Linux基础实操五

    实操一:nginx服务 二进制安装nginx包1) 1)#yum clean all 2)#yum install epel-release -y 3)#yum install nginx -y 1) ...

  2. Linux基础实操二

    实操一: 1) 新建用户natasha uid为1000,gid为555,备注信息为“master” 2) 修改natasha用户的家目录为/Natasha 3) 查看用户信息配置文件的最后一行 ca ...

  3. JSP 指令 脚本元素 表达式 声明

    一.page指令 1. 可以使用page指令来控制JSP转换器转换当前JSP页 面的某些方面.例如,可以告诉JSP用于转换隐式对象 out的缓冲器的大小.内容类型,以及需要导入的Java 类型,等等. ...

  4. Windows 系统共享文件扫描

    近年来历次泄露的安全事故(工控安全),其主要原因就是内部网络自身的脆弱性问题.对于内部网络的安全检查是很必要的.传统上使用CMD命令  net view 就可以扫描在线的主机但是,主机设置取消QOS的 ...

  5. 伪Ap接入点

    1.创建一个伪造的Ap接入点,必须购买一个无线网卡的设备,接受功率在300Mbps ,低于这个传输速率的值,效果很差,都达到用户可以连接验证的效果.其芯片必须支持kali linux 内核系统. 2. ...

  6. AI学习吧-结算中心

    结算中心流程 在结算中心中,主要是对用户添加到购物车商品的结算,由于用户可能添加了多个课程,但是,结算时会选择性的进行支付.在结算时会选中课程id,和对应的价格策略.在后台,首先会对用户进行校验,验证 ...

  7. 将眼底图片生成的txt文件进行格式化处理

    # -*- coding: utf-8 -*- """ 将图片转换生成的txt文件进行格式化处理 """ import os import ...

  8. Python面向对象 三大特性 综合案例

    class Animal: # 所用的知识 Animal类的封装 -> Dog类,Cat类,Person类的继承->多态 # 把所有的共同属性和方法封装在一个公有类里面让子类继承父类的方法 ...

  9. 腾讯云Ubuntu安装可视化桌面

    1.安装图形界面 sudo apt-get update 更新 1).sudo apt-get install xinit 2).sudo apt-get install gdm  ( 登陆窗口,用于 ...

  10. Mongodb for .Net Core 驱动的应用

    一:我在做.net core 应用mongodb的sdk时,查阅了不少资料,故记录下来,以方便查阅.mongodb类库的版本 mongodb driver 2.4.3,一下方法均来自此版本文件 先看看 ...