如果之前主要是用Java做业务系统 ,那么想用go重写的话还是比较痛苦的,最主要的原因就是你会发现要啥没啥,需要自己重写(造轮子)。下面列举了一些需要施工的基础设施。
错误处理
在Java中,只要你没有刻意的使用4参数的Exception构造方法去定义自己的异常类,那么默认情况下都是会记录调用栈的,这样基本上就能马上定位到事故第一现场,排查效率很高。Go则不然,如果使用默认的error机制,那么在报错的时候你得到的只是一个简单的字符串,没有任何现场信息。我在调试的时候最大的痛苦也是如此,报错了,但一时很难快速定位到出错的代码,如果是比较陈旧的项目,那就更不知道这个错误是在哪返回的了。不仅如此,因为go里如果遇到panic且没有被"捕获",那么就会直接导致进程退出,整个服务直接崩溃,这也是不可接受的。
为了解决错误现场的问题,我们可以自己定义一个结构体,它在实现error
接口的同时,再添加一个PrevError
的字段用于记录上层错误,类似于Java Exception的cause()
方法:
type Error struct {
Message string
PrevError error
}
然后定义一个Wrap()
方法,在遇到错误时,先将先前的错误传进去,然后再填写一条符合本层逻辑的描述信息:
// prevError: 原始错误
// src: 可以填写源文件名
// desp: 新error对象的错误描述
func Wrap(prevError error, src string, desp string) error {
var msg string
if "" != src {
msg = "[" + src + "] " + desp
} else {
msg = desp
}
err := &Error{
Message: msg,
PrevError: prevError,
}
return err
}
if nil != err {
return er.Wrap(err, sourceFile, "failed to convert id")
}
注意第二个参数src
, 这里可以直接通过硬编码的形式将当前源文件名传进去,这样日志中就会出现
[xxxx.go] failed to convert id
方便错误排查。相比较标准库的runtime.Call()
方法我更倾向于自己手动把文件名传进来,由于行号会经常变动就不传了,而文件名很少改动,因此这是开销最低的记录现场的方法。
有了自定义的错误以后,在最上层(一般是你的HTTP框架的H