- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章一些关于Go程序错误处理的相关建议由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
Go的错误处理这块是日常被大家吐槽较多的地方,我在工作中也观察到一些现象,比较严重的是在各层级的逻辑代码中对错误的处理有些重复.
比如,有人写代码就会在每一层都判断错误并记录日志,从代码层面看,貌似很严谨,但是如果看日志会发现一堆重复的信息,等到排查问题时反而会造成干扰.
今天给大家总结三点Go代码错误处理相关的最佳实践给大家.
这些最佳实践也是网上一些前辈分享的,我自己实践后在这里用自己的语言描述出来,希望能对大家有所帮助.
Go程序通过error类型的值表示错误 。
error类型是一个内建接口类型,该接口只规定了一个返回字符串值的Error方法.
1
2
3
|
type error interface {
Error() string
}
|
Go语言的函数经常会返回一个error值,调用者通过测试error值是否是nil来进行错误处理.
1
2
3
4
5
6
|
i, err := strconv.Atoi(
"42"
)
if
err != nil {
fmt.Printf(
"couldn't convert number: %v\n"
, err)
return
}
fmt.Println(
"Converted integer:"
, i)
|
error为nil时表示成功;非nil的error表示失败.
我们经常会定义符合自己需要的错误类型,但是记住要让这些类型实现error接口,这样就不用在调用方的程序里引入额外的类型.
比如下面我们自己定义了myError这个类型,如果不实现error接口的话,调用者的代码中就会被myError这个类型侵入。比如下面的run函数,在定义返回值类型时,直接定义成error即可.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package myerror
import (
"fmt"
"time"
)
type myError
struct
{
Code
int
When
time
.Time
What string
}
func (e *myError) Error() string {
return
fmt.Sprintf(
"at %v, %s, code %d"
,
e.When, e.What, e.Code)
}
func run() error {
return
&MyError{
1002,
time
.Now(),
"it didn't work"
,
}
}
func TryIt() {
if
err := run(); err != nil {
fmt.Println(err)
}
}
|
如果myError不实现error接口的话,这里的返回值类型就要定义成myError类型。可想而知,紧接着调用者的程序里就要通过myError.Code == xxx 来判断到底是哪种具体的错误(当然想要这么干得先把myError改成导出的MyError).
那调用者判断自定义error是具体哪种错误的时候应该怎么办呢,myError并未向包外暴露,答案是通过向包外暴露检查错误行为的方法来实现.
1
2
|
myerror.IsXXXError(err)
...
|
抑或是通过比较error本身与包向外暴露的常量错误是否相等来判断,比如操作文件时常用来判断文件是否结束的io.EOF.
类似的还有gorm.ErrRecordNotFound等各种开源包对外暴露的错误常量.
1
2
3
|
if
err != io.EOF {
return
err
}
|
先看一段简单的程序,看大家能不能发现一些细微的问题 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
func WriteAll(w io.Writer, buf []byte) error {
_, err := w.Write(buf)
if
err != nil {
log
.Println(
"unable to write:"
, err)
// annotated error goes to log file
return
err
// unannotated error returned to caller
}
return
nil
}
func WriteConfig(w io.Writer, conf *Config) error {
buf, err := json.Marshal(conf)
if
err != nil {
log
.Printf(
"could not marshal config: %v"
, err)
return
err
}
if
err := WriteAll(w, buf); err != nil {
log
.Println(
"could not write config: %v"
, err)
return
err
}
return
nil
}
func main() {
err := WriteConfig(f, &conf)
fmt.Println(err)
// io.EOF
}
|
上面程序的错误处理暴露了两个问题:
1.底层函数WriteAll在发生错误后,除了向上层返回错误外还向日志里记录了错误,上层调用者做了同样的事情,记录日志然后把错误再返回给程序顶层.
因此在日志文件中得到一堆重复的内容 。
unable to write: io.EOF could not write config: io.EOF ... 。
2. 在程序的顶部,虽然得到了原始错误,但没有相关内容,换句话说没有把WriteAll、WriteConfig记录到 log 里的那些信息包装到错误里,返回给上层.
针对这两个问题的解决方案可以是,在底层函数WriteAll、WriteConfig中为发生的错误添加上下文信息,然后将错误返回上层,由上层程序最后处理这些错误.
一种简单的包装错误的方法是使用fmt.Errorf函数,给错误添加信息.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func WriteConfig(w io.Writer, conf *Config) error {
buf, err := json.Marshal(conf)
if
err != nil {
return
fmt.Errorf(
"could not marshal config: %v"
, err)
}
if
err := WriteAll(w, buf); err != nil {
return
fmt.Errorf(
"could not write config: %v"
, err)
}
return
nil
}
func WriteAll(w io.Writer, buf []byte) error {
_, err := w.Write(buf)
if
err != nil {
return
fmt.Errorf(
"write failed: %v"
, err)
}
return
nil
}
|
fmt.Errorf只是给错误添加了简单的注解信息,如果你想在添加信息的同时还加上错误的调用栈,可以借助github.com/pkg/errors这个包提供的错误包装能力.
1
2
3
4
5
6
7
8
|
//只附加新的信息
func WithMessage(err error, message string) error
//只附加调用堆栈信息
func WithStack(err error) error
//同时附加堆栈和信息
func Wrap(err error, message string) error
|
有包装方法,就有对应的解包方法,Cause方法会返回包装错误对应的最原始错误--即会递归地进行解包.
1
|
func Cause(err error) error
|
下面是使用github.com/pkg/errors改写后的错误处理程序 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func ReadFile(path string) ([]byte, error) {
f, err := os.Open(path)
if
err != nil {
return
nil, errors.Wrap(err,
"open failed"
)
}
defer f.Close()
buf, err := ioutil.ReadAll(f)
if
err != nil {
return
nil, errors.Wrap(err,
"read failed"
)
}
return
buf, nil
}
func ReadConfig() ([]byte, error) {
home := os.Getenv(
"HOME"
)
config, err := ReadFile(filepath.Join(home,
".settings.xml"
))
return
config, errors.WithMessage(err,
"could not read config"
)
}
func main() {
_, err := ReadConfig()
if
err != nil {
fmt.Printf(
"original error: %T %v\n"
, errors.Cause(err), errors.Cause(err))
fmt.Printf(
"stack trace:\n%+v\n"
, err)
os.Exit(1)
}
}
|
上面格式化字符串时用的 %+v 是在 % v 基础上,对值进行展开,即展开复合类型值,比如结构体的字段值等明细.
这样既能给错误添加调用栈信息,又能保留对原始错误的引用,通过Cause可以还原到最初始引发错误的原因.
总结一下,错误处理的原则就是:
错误只在逻辑的最外层处理一次,底层只返回错误。 底层除了返回错误外,要对原始错误进行包装,增加错误信息、调用栈等这些有利于排查的上下文信息.
到此这篇关于Go程序错误处理的相关建议的文章就介绍到这了,更多相关Go程序错误处理内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://mp.weixin.qq.com/s/HuZn9hnHUBx3J4bAGYBYpw 。
最后此篇关于一些关于Go程序错误处理的相关建议的文章就讲到这里了,如果你想了解更多关于一些关于Go程序错误处理的相关建议的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我已经使用 vue-cli 两个星期了,直到今天一切正常。我在本地建立这个项目。 https://drive.google.com/open?id=0BwGw1zyyKjW7S3RYWXRaX24tQ
您好,我正在尝试使用 python 库 pytesseract 从图像中提取文本。请找到代码: from PIL import Image from pytesseract import image_
我的错误 /usr/bin/ld: errno: TLS definition in /lib/libc.so.6 section .tbss mismatches non-TLS reference
我已经训练了一个模型,我正在尝试使用 predict函数但它返回以下错误。 Error in contrasts<-(*tmp*, value = contr.funs[1 + isOF[nn]])
根据Microsoft DataConnectors的信息我想通过 this ODBC driver 创建一个从 PowerBi 到 PostgreSQL 的连接器使用直接查询。我重用了 Micros
我已经为 SoundManagement 创建了一个包,其中有一个扩展 MediaPlayer 的类。我希望全局控制这个变量。这是我的代码: package soundmanagement; impo
我在Heroku上部署了一个应用程序。我正在使用免费服务。 我经常收到以下错误消息。 PG::Error: ERROR: out of memory 如果刷新浏览器,就可以了。但是随后,它又随机发生
我正在运行 LAMP 服务器,这个 .htaccess 给我一个 500 错误。其作用是过滤关键字并重定向到相应的域名。 Options +FollowSymLinks RewriteEngine
我有两个驱动器 A 和 B。使用 python 脚本,我在“A”驱动器中创建一些文件,并运行 powerscript,该脚本以 1 秒的间隔将驱动器 A 中的所有文件复制到驱动器 B。 我在 powe
下面的函数一直返回这个错误信息。我认为可能是 double_precision 字段类型导致了这种情况,我尝试使用 CAST,但要么不是这样,要么我没有做对...帮助? 这是错误: ERROR: i
这个问题已经有答案了: Syntax error due to using a reserved word as a table or column name in MySQL (1 个回答) 已关闭
我的数据库有这个小问题。 我创建了一个表“articoli”,其中包含商品的品牌、型号和价格。 每篇文章都由一个 id (ID_ARTICOLO)` 定义,它是一个自动递增字段。 好吧,现在当我尝试插
我是新来的。我目前正在 DeVry 在线学习中级 C++ 编程。我们正在使用 C++ Primer Plus 这本书,到目前为止我一直做得很好。我的老师最近向我们扔了一个曲线球。我目前的任务是这样的:
这个问题在这里已经有了答案: What is an undefined reference/unresolved external symbol error and how do I fix it?
我的网站中有一段代码有问题;此错误仅发生在 Internet Explorer 7 中。 我没有在这里发布我所有的 HTML/CSS 标记,而是发布了网站的一个版本 here . 如您所见,我在列中有
如果尝试在 USB 设备上构建 node.js 应用程序时在我的树莓派上使用 npm 时遇到一些问题。 package.json 看起来像这样: { "name" : "node-todo",
在 Python 中,您有 None单例,在某些情况下表现得很奇怪: >>> a = None >>> type(a) >>> isinstance(a,None) Traceback (most
这是我的 build.gradle (Module:app) 文件: apply plugin: 'com.android.application' android { compileSdkV
我是 android 的新手,我的项目刚才编译和运行正常,但在我尝试实现抽屉导航后,它给了我这个错误 FAILURE: Build failed with an exception. What wen
谁能解释一下?我想我正在做一些非常愚蠢的事情,并且急切地等待着启蒙。 我得到这个输出: phpversion() == 7.2.25-1+0~20191128.32+debian8~1.gbp108
我是一名优秀的程序员,十分优秀!