高效的Golang架构研发效率--对于error的处理

130 阅读3分钟

一、怎样的错误处理和编码是合适的

error对于go是必选题,因为这就是go对异常错误的处理方式。既然是必选题,那么怎样处理才是优秀的设计呢?

1.1 error缺乏堆栈信息怎么办

以下代码代码的问题,在于当main中调用test发生错误的时,我们根本无法知道内部哪个方法发生了问题。当问题是作为使用者main,需要知道test里面的哪行代码出了问题吗?

支持方:社区

其他语言说,这是需要的:因为我们都能做到,我们可以直接在 v,err:=test()调用时抛出错误信息和堆栈信息。 虽然Go语言对错误的设计非常简洁,但是对于我们开发者来说,很明显是不足的,比如我们需要知道出错的更多信息,在什么文件的,哪一行代码?只有这样我们才更容易的定位问题。

还有比如,我们想对返回的error附加更多的信息后再返回,比如以上的例子,我们怎么做呢?我们只能先通过Error方法,取出原来的错误信息,然后自己再拼接,再使用errors.New函数生成新错误返回。

如果我们以前做过java开发,我们知道Java的异常是可以嵌套的,也就是说,通过这个,我们很容易知道错误的根本原因,因为Java的异常,是一层层的嵌套返回的,不管中间经历了多少包装,我们可以通过cause找到根本错误的原因。

反对方:某歌

test不是告诉你发生了某个错误了吗?你确定还需要某一行?error不已经告诉你不能正常调用了吗?我们力求简洁,别再烦我。

func m1() (int, error) {
	time.Sleep(1 * time.Microsecond)
	return 1, errors.New("error1")
}
func m2() (int, error) {
	time.Sleep(2 * time.Microsecond)
	return 2, errors.New("error2")
}
func test() (string, error) {
	v1, err1 := m1()
	if err1 != nil {
		return "", err1
	}
	v2, err2 := m2()
	if err2 != nil {
		return "", err2
	}
	return strconv.Itoa(v1 + v2), nil
}
func main(){
    v,err:=test()
}

社区

既然你不给我们,我们自己来一个吧 github.com/pkg/errors 这个库本质能做什么呢?

image.png

1.包装error(且带有堆栈信息),且能找到原始的error

_, err := ioutil.ReadAll(r)
if err != nil {
        return errors.Wrap(err, "read failed")
}
e := errors.WithStack(err)

2.和golang的标准库很贴近,提供UnWrap,New等方法,但注意google故意不提供Wrap方法,仅仅提供fmt.Errof %w的方法,恶心到了吧。且目前这个库缺少Join方法,暂时这个库没人维护了。但其实调整还是容易的。

switch err := errors.Cause(err).(type) {
case *MyError:
        // handle specifically
default:
        // unknown error
}

3.官方的fmt.Errorf("%w",err)生成的error没有堆栈信息 用pkg包的error我们最后可以通过如下得到堆栈信息,因为堆栈被设计进去了。

%s,%v //功能一样,输出错误信息,不包含堆栈
%q //输出的错误信息带引号,不包含堆栈
%+v //输出错误信息和堆栈

1.2 zap ,logrus对error堆栈信息的处理

package main

import (
  "github.com/sirupsen/logrus"
)

func main() {
  logrus.SetReportCaller(true) //开启调用堆栈信息,但是我们只能记录到记日志的位置。

  logrus.Info("info msg")
}

$ go run main.go time="2020-12-07T21:46:03 08:00" level=info msg="info msg" func=main.main file="D:/code/golang/src/github.com/darjun/go-daily-lib/logrus/caller/main.go:10"

package main

import (
	"encoding/json"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

func main() {
	rawJSON := []byte(`{
		"level": "debug",
		"encoding": "json",
		"outputPaths": ["stdout", "/tmp/logs"],
		"errorOutputPaths": ["stderr"],
		"initialFields": {"foo": "bar"},
		"encoderConfig": {
		  "messageKey": "message",
		  "levelKey": "level",
		  "levelEncoder": "lowercase",
                  "callerKey": "caller",//主要这里
		}
	  }`)

	var cfg zap.Config
	if err := json.Unmarshal(rawJSON, &cfg); err != nil {
		panic(err)
	}

	cfg.EncoderConfig.EncodeCaller = zapcore.FullCallerEncoder

	logger, err := cfg.Build()
	if err != nil {
		panic(err)
	}
	defer logger.Sync()

	logger.Info("logger construction succeeded")
}