不一样的go语言-error
前言
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的更多相关文章
- GO语言Error处理
Go语言没有提供像Java.C#.Python语言中的try...catch异常处理方式,而是通过函数返回值逐层往上抛.好处就是避免漏掉本应处理的错误.坏处是代码啰嗦. 错误与异常区别 错误指的是可能 ...
- 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 ...
- c语言-error C2440: “static_cast”: 无法从“UINT (__thiscall CHyperLink::* )(CPoint)”转换为“LRESULT (__thiscall CWnd::* )(CPoint)”
出现这个错误的原因可是“人力不可抗拒”之原因造成的,因为旧版本的 ON_WM_NCHITTEST 宏使用了 UINT (__thiscall CWzButton::* )(CPoint); 类型的类成 ...
- 07. Go 语言接口
Go 语言接口 接口本身是调用方和实现方均需要遵守的一种协议,大家按照统一的方法命名参数类型和数量来协调逻辑处理的过程. Go 语言中使用组合实现对象特性的描述.对象的内部使用结构体内嵌组合对象应该具 ...
- golang error错误处理
error定义 数据结构 go语言error是一普通的值,实现方式为简单一个接口. // The error built-in interface type is the conventional i ...
- flask框架(二):简单的登录demo
一:main.py # -*- coding: utf-8 -*- # @Author : Felix Wang # @time : 2018/7/3 22:58 from flask import ...
- 3_JSP
一. 引言 1.1 现有问题 在之前学习Servlet时, 服务器通过Servlet响应客户端页面, 有什么不足之处? 开发方式麻烦: 继承父类, 覆盖方法, 配置web.xml或注解 代码修改麻烦: ...
- [转载]VS2012编译C语言scanf函数error的解决方法
在VS 2012 中编译 C 语言项目,如果使用了 scanf 函数,编译时便会提示如下错误: error C4996: 'scanf': This function or variable may ...
- 与非java语言使用RSA加解密遇到的问题:algid parse error, not a sequence
遇到的问题 在一个与Ruby语言对接的项目中,决定使用RSA算法来作为数据传输的加密与签名算法.但是,在使用Ruby生成后给我的私钥时,却发生了异常:IOException: algid parse ...
随机推荐
- Confluence 6 数据库表-系统信息(System information)
这些表格有存储数据相关的状态和 Confluence 站点的相关配置信息. confversion 被用来在升级系统的时候确定那个数据库的版本应该使用,这个表格只对数据库升级有影响. pluginda ...
- Confluence 6 从一个 XML 备份中导入一个空间
有下面 2 中方法可以导入一个空间——通过上传一个文件,或者从你 Confluence 服务器上的一个目录中导入.上传文件仅仅是针对一个小站点的情况.为了取得最好的导入结果,我们推荐你从服务器上的目录 ...
- Confluence 6 编辑和删除用户宏
编辑一个用户宏 希望对一个用户宏进行编辑: 进入 > 基本配置(General Configuration) > 用户宏(User Macros) 在相关的宏的边上,单击 编辑(Edit ...
- Python if __name__ == '__main__':(以主程序形式执行)
在外部调用某个模块时,可能会将只能在本模块执行的代码给执行了,有没有什么办法让某些特定的代码指定只能在自身运行时才执行被调用时不执行呢?使用if __name__ == '__main__':. 示例 ...
- js--基础(对象、数组、函数、if语句、while语句、do while语句、continue语句、break语句)
三.流程控制:1.单行语句var age =20;//单行语句 2.复合语句花括号包含起来的与聚集和叫做复合语句,一对花括号表示一个复合语句 ,处理时可以当成一个单行语句来看待,一般复合句与叫做代码块 ...
- shell 脚本加密
日常编写shell脚本时会写一些账号和密码写入脚本内,但是不希望泄露账号密码,所以对shell脚本进行加密变成可执行文件. 主要使用 shc 对 Linux shell 脚本加密,shc是一个专业的加 ...
- 一脸懵逼学习Hive的安装(将sql语句翻译成MapReduce程序的一个工具)
Hive只在一个节点上安装即可: 1.上传tar包:这个上传就不贴图了,贴一下上传后的,看一下虚拟机吧: 2.解压操作: [root@slaver3 hadoop]# tar -zxvf hive-0 ...
- jQuery分步步骤
插件描述:jQuery上一步.下一步,分步步骤,兼容性如下: 使用方法 1.引入样式和脚本 <link rel="stylesheet" type="text/cs ...
- webpack学习笔记--配置devServer
devServer 1-6 使用DevServer 介绍过用来提高开发效率的 DevServer ,它提供了一些配置项可以改变 DevServer 的默认行为. 要配置 DevServer ,除了在配 ...
- nginx 服务脚本编写模板
编写nginx服务脚本:脚本内容如下: [root@www ~]# cat /etc/init.d/nginx #!/bin/bash # nginx Startup script for the N ...