GOLANG错误处理最佳方案
原文:https://gocn.io/article/348 GOLANG的错误很简单的,用error接口,参考golang error handling: if f,err := os.Open("test.txt"); err != nil {
return err
}
实际上如果习惯于C返回错误码,也是可以的,定义一个整形的error: type errorCode int
func (v errorCode) Error() string {
return fmt.Sprintf("error code is %v",v)
}
const loadFailed errorCode = 100
func load(filename string) error {
if f,err := os.Open(filename); err != nil {
return loadFailed
}
defer f.Close()
content : = readFromFile(f);
if len(content) == 0 {
return loadFailed
}
return nil
}
这貌似没有什么难的啊?实际上,这只是error的基本单元,在实际的产品中,比如有个播放器会打印一个这个信息: Player: Decode failed.
对的,就只有这一条信息,然后呢?就没有然后了,只知道是解码失败了,没有任何的线索,必须得调试播放器才能知道发生了什么。看我们的例子,如果 error code is 100
这些信息是不够的,这是一个错误库很流行的原因,这个库是errors,它提供了一个Wrap方法: _,err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err,"read failed")
}
也就是加入了多个error,如果用这个库,那么上面的例子该这么写: func load(filename string) error {
if f,err := os.Open(filename); err != nil {
return errors.Wrap(err,"open failed")
}
defer f.Close()
content : = readFromFile(f);
if len(content) == 0 {
return errors.New("content empty")
}
return nil
}
这个库给每个error可以加上额外的消息 在多层函数调用中,甚至可以每层都加上自己的信息,例如: func initialize() error {
if err := load("sys.db"); err != nil {
return errors.WithMessage(err,"init failed")
}
if f,err := os.Open("sys.log"); err != nil {
return errors.Wrap(err,"open log failed")
}
return nil
}
在 empty content
main.load
/Users/winlin/git/test/src/demo/test/main.go:160
main.initialize
/Users/winlin/git/test/src/demo/test/main.go:167
main.main
/Users/winlin/git/test/src/demo/test/main.go:179
runtime.main
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
load sys.db failed
这样就可以知道是加载 例如,AAC的一个库,用到了ASC对象,在解析时需要判断是否数据合法,实现如下(参考code): func (v *adts) Decode(data []byte) (raw,left []byte,err error) {
p := data
if len(p) <= 7 {
return nil,nil,errors.Errorf("requires 7+ but only %v bytes",len(p))
}
// Decode the ADTS.
if err = v.asc.validate(); err != nil {
return nil,errors.WithMessage(err,"adts decode")
}
return
}
func (v *AudioSpecificConfig) validate() (err error) {
if v.Channels < ChannelMono || v.Channels > Channel7_1 {
return errors.Errorf("invalid channels %#x",uint8(v.Channels))
}
return
}
在错误发生的最原始处,加上堆栈,在外层加上额外的必要信息,这样在使用时发生错误后,可以知道问题在哪里,写一个实例程序: func run() {
adts,_ := aac.NewADTS()
if _,_,err := adts.Decode(nil); err != nil {
fmt.Println(fmt.Sprintf("Decode failed,err is %+v",err))
}
}
func main() {
run()
}
打印详细的堆栈: Decode failed,err is invalid object 0x0
github.com/ossrs/go-oryx-lib/aac.(*AudioSpecificConfig).validate
/Users/winlin/go/src/github.com/ossrs/go-oryx-lib/aac/aac.go:462
github.com/ossrs/go-oryx-lib/aac.(*adts).Decode
/Users/winlin/go/src/github.com/ossrs/go-oryx-lib/aac/aac.go:439
main.run
/Users/winlin/git/test/src/test/main.go:13
main.main
/Users/winlin/git/test/src/test/main.go:19
runtime.main
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/proc.go:185
runtime.goexit
/usr/local/Cellar/go/1.8.1/libexec/src/runtime/asm_amd64.s:2197
adts decode
错误信息包含:
如果这个信息是客户端的,发送到后台后,非常容易找到问题所在,比一个简单的 加上堆栈会不会性能低?错误出现的概率还是比较小的,几乎不会对性能有损失。使用复杂的error对象,就可以在库中避免用logger,在应用层使用logger打印到文件或者网络中。 对于其他的语言,比如多线程程序,也可以用类似方法,返回int错误码,但是把上下文信息保存到线程的信息中,清理线程时也清理这个信息。对于协程也是一样的,例如ST的thread也可以拿到当前的ID,利用全局变量保存信息。对于goroutine这种拿不到协程ID,可以用 一个C++的例子,得借助于宏定义: struct ComplexError {
int code;
ComplexError* wrapped;
string msg;
string func;
string file;
int line;
};
#define errors_new(code,fmt,...)
_errors_new(__FUNCTION__,__FILE__,__LINE__,code,##__VA_ARGS__)
extern ComplexError* _errors_new(const char* func,const char* file,int line,int code,const char* fmt,...) {
va_list ap;
va_start(ap,fmt);
char buffer[1024];
size_t size = vsnprintf(buffer,sizeof(buffer),ap);
va_end(ap);
ComplexError* err = new ComplexError();
err->code = code;
err->func = func;
err->file = file;
err->line = line;
err->msg.assign(buffer,size);
return err;
}
#define errors_wrap(err,...)
_errors_wrap(__FUNCTION__,err,##__VA_ARGS__)
extern ComplexError* _errors_wrap(const char* func,ComplexError* v,...) {
ComplexError* wrapped = (ComplexError*)v;
va_list ap;
va_start(ap,ap);
va_end(ap);
ComplexError* err = new ComplexError();
err->wrapped = wrapped;
err->code = wrapped->code;
err->func = func;
err->file = file;
err->line = line;
err->msg.assign(buffer,size);
return err;
}
使用时,和GOLANG有点类似: ComplexError* loads(string filename) {
if (filename.empty()) {
return errors_new(100,"invalid file");
}
return NULL;
}
ComplexError* initialize() {
string filename = "sys.db";
ComplexError* err = loads(filename);
if (err) {
return errors_wrap("load system from %s failed",filename.c_str());
}
return NULL;
}
int main(int argc,char** argv) {
ComplexError* err = initialize();
// Print err stack.
return err;
}
比单纯一个code要好很多,错误发生的概率也不高,获取详细的信息比较好。 另外,logger和error是两个不同的概念,比如对于library,错误时用errors返回复杂的错误,包含丰富的信息,但是logger一样非常重要,比如对于某些特定的信息,access log能看到客户端的访问信息,还有协议一般会在关键的流程点加日志,说明目前的运行状况,此外,还可以有json格式的日志或者叫做消息,可以把这些日志发送到数据系统处理。 对于logger,支持 // C++ style
logger(int level,void* ctx,const char* fmt,...)
// GOLANG style
logger(level:int,ctx:context.Context,format string,args ...interface{})
这样在文本日志,或者在消息系统中,就可以区分出哪个会话。当然在error中也可以包含context的信息,这样不仅仅可以看到出错的错误和堆栈,还可以看到之前的重要的日志。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |