闽公网安备 35020302035485号
class BaseController {
errorHandler(err) {
this.response.sendJSON({code: 500, message: '系统异常'})
}
}
这意味着这个系统的所有抛出的错误都会转换为“系统异常”!而最糟糕的是,甚至没有记录任何日志!为了方便后续开发人员定位错误,各种日志被添加到业务层代码中,使得业务代码不堪重负。class BaseController {
errorHandler(err) {
// 生成异常标识符并记录日志。
let flag = random()
log(err, flag)
this.response.sendJSON({"code": 500, "message": `系统异常(${flag})`})
}
}
在系统异常后添加一个标识符。当出现问题时,可以根据这个标识符快速定位和排查日志。对于拥有完善日志系统的项目(如 ELK),大大提高了程序员的生存状态。但是,上述代码有什么问题?if (balance < amount) {
throw new NotEnoughException('卡余额不足。')
}
余额不足是一个非常常见的场景,但用户看到的提示是:“系统异常(1877618)”。此时,我不知道用户和程序员是否崩溃了,但至少你的老板是崩溃的。// 全局:定义统一的错误代码和错误消息。
const OK = 200
const SYS_ERR = 500
const NOT_FOUND = 404
const NOT_ENOUGH = 405
const map = {
200: "OK",
500: "系统错误",
404: "资源未找到",
405: "余额不足"
}
// 堆代码 duidaima.com
// 错误代码转文本
function error(code) {
return map[code]
}
业务层代码:if (balance < amount) {
// 此自定义异常类只允许传递错误代码,并在内部使用 error() 函数将其转换为文本。
throw new MyException(NOT_ENOUGH)
}
控制器:class BaseController {
errorHandler(err) {
log(err)
this.response.sendJSON({"code": err.code, "message": err.message})
// 或者:this.response.sendJSON({"code": err.code, "message": error(err.code)})
}
}
这种错误处理原则是通过错误代码统一项目的代码和消息。开发人员不能在程序中定义自己的错误描述。我称这些程序员为“错误代码”信徒。“错误代码”组的主要担忧是,如果允许开发人员在代码中定义错误描述,可能会导致“哈姆雷特”问题,即每个人的描述可能不同,还可能导致敏感信息泄漏。// 添加可选参数 "message",以允许自定义描述输入。
class MyException {
constructor(code, message = '') {
this.code = code
this.message = message
}
}
期望程序有如下调用:if (balance < amount) {
throw new MyException(NOT_ENOUGH, '卡余额不足,目前可用余额:' + balance)
}
追求自由:反“错误代码”可能涉及敏感信息:例如,当出现 SQL 操作错误时,可能会将整个 SQL 语句暴露给外部。
try {
...
} catch (e) {
switch (e.message) {
case '用户不存在':
...
case ...
}
}
如果有一天后端程序员心血来潮,将“用户不存在”改为“用户信息不存在”,系统就会崩溃。创建这样脆弱系统的程序员应被钉在第1024柱羞耻柱上!然而,在钉他们之前,我们应该听听他们痛苦的呼声:接口返回的错误代码混乱,已经有八个不同的错误代码表示“用户不存在”,将来也可能会更多。为了“系统稳定”,最终决定基于消息进行匹配。const USER_NOT_EXISTS = 406代码中只能使用错误代码常量。
throw new Exception('用户不存在', USER_NOT_EXISTS)
禁止使用文字常量。// 用户不存在。
class UserNotExistsException extends Exception {
constructor(message) {
super(message)
this.code = ErrCode.USER_NOT_EXISTS
}
}
使用:if (!User.find(uid)) {
// 这种写法更有表现力,开发人员不需要关注错误代码
throw new UserNotExistsException(`用户不存在(uid:${uid})`)
}
异常处理机制的示例const OK = 200 const SYS_ERR = 500 const NOT_FOUND = 404 const NOT_ENOUGH = 405 const USER_NOT_EXISTS = 406 ...业务异常基类:
class BussinessException extends Exception {
...
}
异常类定义:class UserNotExistsException extends BussinessException {
constructor(message) {
super(message)
this.code = ErrCode.USER_NOT_EXISTS
}
}
业务层使用:if (!User.find(uid)) {
throw new UserNotExistsException(`用户不存在。(uid:${uid})`)
}
基础控制器捕获异常:class BaseController {
...
errorHandler(err) {
// 是否是业务异常
const isBussError = err instanceof BussinessException
// 是否是数据库异常
const isDBError = err instanceof DBException
// 生成用于跟踪异常日志的随机字符串
const flag = isBussError ? '' : random()
let message = err.message
if (isDBError) {
// 数据库异常,脱敏处理
message = `数据异常(flag:${flag})`
} else if (!isBussError) {
// 非业务异常记录标识符
message += `(flag:${flag})`
}
// 记录错误(日志应记录原始消息)
log(err.message, isBussError ? '' : err.stackTrace(), flag)
// 返回给调用方
this.response.sendJSON({"code": err.code, "message": message})
}
function log(message, stackTrace, flag) {
...
}
...
}
约定机制if (!User.find(uid)) {
throw new Exception('系统异常', 500)
}
一行代码将使你回到原点!因此,异常处理机制是基于约定的(团队约定)。