# 错误处理

## 问题

在后台开发中，针对错误处理，有两个维度的问题需要解决：

1. **函数内部的错误处理/返回**：函数在操作错误之后，怎么将这个错误信息优雅地返回；上层怎么处理
2. **服务/系统的错误信息返回**：微服务/系统在处理失败时，如何返回一个友好的错误信息，让调用方优雅地理解和处理。这是一个服务级的问题。

## 函数内部的错误处理 / 返回

### 错误返回

`Go` 在语言层面上提供了多返回值的形式，因此可以很方便的区分正常返回值和错误。

但要区别与 `panic` 和正常的业务错误。`panic` 是会导致协程中断的异常，地位相当于 `Linux` 下的 `Fatal`，不应乱用。

函数/模块透传错误时，应该采用 `Go` 的 `error wrapping` 模式，也就是 `fmt.Errorf("%w")`，或者 `errors.Wrap(err, "details")`

其他语言，需要设计特殊的返回值来标识错误，如使用负数、`failed` 等枚举值。也有的语言提供 `try / catch` 机制

### 错误处理、断言

上层处理错误时，往往需要判断错误类型。有如下流派：

#### 值比较流派

```go
var ErrNotFound = errors.New("not found")

if err == ErrNotFound {
    // something wasn't found
}

// go1.13里可以用 errors.Is，等价于上面的写法
if errors.Is(err, ErrNotFound) {
    // something wasn't found
}
```

#### 类型断言流派

```go
type NotFoundError struct {
    Name string
}

func (e *NotFoundError) Error() string { return e.Name + ": not found" }

if e, ok := err.(*NotFoundError); ok {
    // e.Name wasn't found
}

// go1.13里可以用 errors.As，等价于上面的写法
var e *QueryError
if errors.As(err, &e) {
    // err is a *QueryError, and e is set to the error's value
}
```

## 服务/系统的错误信息返回

服务/系统层面的错误信息返回，大部分协议都可以看成是 `code-message-translate` 模式或者是其变体：

* code 是数字或者预定义的字符串，可以视为整型或者是字符串类型的枚举值。
  * 如果是数字的话，大部分情况下是使用0表示成功，小部分则采用一个比较规整的十进制数字表示成功，比如1000、10000等。
  * 如果是预定义的字符串，那么是使用“success”、“OK”等字符串表示成功，或者是直接以空字符串、甚至是不返回字符串字段来表示成功。
* message 字段则是错误信息的具体描述，大部分情况下都是一个人类可读的句子。
  * 一般而言，只有当 code 表示错误的时候，这个message字段才有返回的必要
* translate 字段是对错误信息的用户友好翻译

**问题**

这个模式的问题是：翻译的文案很难完整地覆盖所有的错误用例，或者是错误极其罕见，导致只有 `message`，没有枚举到它的 `code` 和 `translate`

可以对 `message` 做 `MD5` 后取后四位作为关键字代码。然后将 `translate` 的提示信息展示为："未知错误，错误代码30EV，如需协助，请联系XXX"。

至于后台侧，还是需要实实在在地将这个哈希值和具体的错误信息记录在日志或者其他支持搜索的渠道里。当用户提供该代码时，可以快速定位。

**参考**

[腾讯云加社区 - 拒绝千篇一律，这套Go*错误处理*的完整*解决*方案值得一看！](https://zhuanlan.zhihu.com/p/413041029)

[Tony Bai - Go 1.13中的错误处理](https://tonybai.com/2019/10/18/errors-handling-in-go-1-13/)
