NodeJS 实战系列:如何设计 try catch

nodejs,实战,系列,如何,设计,try,catch · 浏览次数 : 743

小编点评

**精心设计的 try catch 设计** **1. 明确捕获错误的范围和类型** * 使用 try catch 语句捕获所有可能出现的错误。 * 在捕获错误时,明确错误的类型,例如异常类或错误消息。 **2. 设计捕获的异常类型** * 捕获特定类型的错误,例如网络错误或数据库错误。 * 使用 catch 语句捕获非标准异常,例如错误代码或非数字类型。 **3. 捕获异常的详细信息** * 在捕获错误时,记录有关错误的详细信息,例如错误代码、消息和其他异常信息。 * 这有助于调试和分析错误。 **4. 处理捕获的错误** * 在捕获到错误时,使用 try catch 语句将其转换为可处理的类型,例如异常或字符串。 * 利用异常信息向用户显示错误消息或进行其他处理。 **5. 捕获意外错误** * 使用 try catch 语句捕获意外错误,例如未知异常或系统资源限制。 * 这有助于捕获不可预知错误。 **6. 考虑捕获跨功能错误** * 使用 try catch 语句捕获所有可能出现的错误,无论来自哪个模块或层级。 * 这有助于捕获跨功能错误。 **7. 在捕获错误之前处理异常** * 在捕获错误之前,可以使用 try catch 语句捕获异常并将其转换为可处理的类型。 * 这有助于确保错误处理程序正常运行。 **8. 使用适当的错误处理机制** * 根据不同的错误类型,选择合适的错误处理机制,例如日志记录、通知开发人员或抛出异常。 * 这有助于实现错误追踪和诊断。

正文

本文将通过一个 NodeJS 程序里无效的错误捕获示例,来讲解错误捕获里常见的陷阱。错误捕获不是凭感觉添加 try catch 语句,它的首要目的是提供有效的错误排查信息,只有精心设计的错误捕获才有可能完成这个使命。针对哪些方面去精心设计就是本篇文章里想讨论的内容

实战系列来自于个人开发以及运维 site2share 网站过程中的经验

设计陷阱,而非听天由命

为什么代码里需要 try catch?是为了阻止 bug 的发生的?当然不是,bug 其实是代码的副产品,bug 的数量取决于代码的质量而非 try catch 的数量。

说到底 try catch 只是用来查漏补缺的工具,如果你把 try catch 只是当作万能的膏药在代码里想贴就贴,那你可能多半贴不中真正的要害,也得不到期望的结果

在 site2share 中我需要集成 Redis 用于存储用户的 session 信息,自然需要在代码中使用第三方类库使用 Redis,无论是 node-redis (还是 ioredis),它们都提供事件机制用于反馈与 Redis Server 连接的当前状态。比如我们可以监听 error 事件:

redis.on('error', function () { });

为什么不监听看看呢。并且上线之后如偿所愿,在发现网站无法访问之后在日志中确实找到了

  1. 如题图所示大量的报错信息
  2. 每一个错误的调用栈

application insights 中捕获的错误信息

但它们统统都仅是 node-redis 类库内部的函数调用栈,我发现这些信息对我来说毫无用处,因为它们无法向我提供最关键的一类信息:上下文。所以这些信息都只是在告诉我在访问 /api/folder/:id 时 redis 出现了报错,然而下列这些问题的答案才更有助于我排查问题:

  • 是否只在指定 id 的情况下才会发生错误?
  • 请求 API 时用户是否处于登录的状态?
  • 连接状态是存在一定概率成功,还是稳定失败?
  • Azure Redis Server 的服务是否稳定?错误是否是由服务自身造成的

对于这些问题的答案我一无所知,更艰难的是我无法在本地开发环境中复现该错误。这个时候我才发现并非你收集的信息越多,你把问题解决的概率就越大,如果你始终缺失某条关键信息,再多的额外信息也于事无补。

这又回到了我之前所说的信息应该是双向的,即我收集的信息务必让我有采取行动和回溯的能力

所以捕获错误同样需要设计。或者退一步说,即使我不太确定错误会在哪里发生而需要在大范围内对错误进行捕获,也需要保证错误要提供有效信息:

  • 除了看到错误消息,我还希望得到调用栈信息
  • 如果我有了调用栈,那么我还希望得到具体的数据 id;
  • 如果有了数据 id,那么我还希望能够得到 ORM 背后生成的 SQL 语句

再退一步说,如果无法得到直接有效信息,间接的有效信息也是可以接受的,例如你可以利用服务供应商或者基础设施的自带日志来协助排查问题;再不济如果只能硬着头皮阅读代码的话,被精心设计的函数名也非常重要。

那么如何设计好的 try catch呢?看起来你需要懂你的函数,你需要知道它可能的输入和期待的输出是什么,你需要知道它在执行过程中会和哪些服务打交道,你需要知道它的风险在哪。很有意思的是,我们从函数出发,想尽可能完美地捕获报错,但完美的答案又在函数本身当中。

涉及错误捕获的诀窍在实现代码中

最后,如果程序在意料之外挂掉或者抛错,顺其自然好了。千万不要想法设法当作什么事情都没有发生然后继续执行下去。因为我们无法得知错误究竟带来的影响是什么,会带来怎样的连锁反应。抱有侥幸心理不如就此止损——请快速失败,快速恢复

说实话我很难找到关于 handle error 设计方面的书籍或者文章,很惊讶这块领域内的空白(我都能找到好几本依赖注入图书)。当你在读技术教程比如《.NET Core in Action》或者《ASP.NET MVC 4 in Action》 时,它们只会告诉你在框架中存在这样或者那样的 error handling 机制,至于如何使用才是最佳实践,并不在它们的范畴内。

"接住"错误

为什么用“接住”而非“抓住”,是因为前者是被动后者是主动的,大部分情况下你都不太可能主动的、预测性的识别到某个bug。但我们不能因为如此就任由它们发生,我们需要:

  • 抹去错误中的敏感信息
  • 让错误信息变得更加友好
  • 记录错误

在处理这些事物方面,我们需要集中化处理错误,目前绝大部分框架都支持这类操作。比如对于 .NET CORE 来说,我们可以通过在最外层添加 middleware 来解决这个问题

.NET 中捕获错误的中间件

error handing middleare 只能作为程序处理错误的最后一道防线,对于不可知的错误尤其有效。然而对于一些可以前置,可以提前捕获的错误来说,我们又应该如何处理呢?

我的经验是,需要在系统内建立一套机制或者说通道,让 exception 按照指定的方向高效的流动起来才是首要任务。举个例子

try {
    await getUserInfo()
} catch(e) {
    throw new LoadUserInfoException()
}

第一个问题是,我们是否真的需要 try catch?不一定,理想情况下即使错误在当前代码块没有被捕获,它发生的意外错误也应该掉落进最后一道防线中,然后翻译为能够暴露给外部的信息,随后程序立即中断,快速重启。

退一步说,即使你按照以上代码有意进行 catch,你要如何处理这个新 throw 出来的错误呢?最好的办法是我们无需关心。LoadUserInfoException 中可以事先定义这个错误的状态吗的 message,上面所说的程序中提前建立好的机制,会自动将这个错误按照状态码和message进行翻译,返回给客户端。并非所有场景都需要有意屏蔽错误信息,有时候将恰当的错误信息返回给客户端能够让用户自主的排查问题更好。

上面涉及的自动捕获、对错误进行翻译、直达客户端,以及你能够想到的跨功能需求,比如收集错误日志,都应该是程序中的基础设施,具体的开发人员无需关心,无需对于每一个错误都手动执行这一系列步骤。

正如下图所示,无论你的 controller、service、SDK 之间的调用层次如何,各个模块中被抛出的异常都一视同仁的被处理。然而开发人员只需要关心下图左上方的部分,至于错误信息如何向右流向客户端,则无需关心

程序中的错误处理流

.NET Core 里的 middleware 和 NodeJS 里的 error handler 都能可以达到这个效果

你也可以通过知乎我的个人网站访问这篇文章

与NodeJS 实战系列:如何设计 try catch相似的内容:

NodeJS 实战系列:如何设计 try catch

本文将通过一个 NodeJS 程序里无效的错误捕获示例,来讲解错误捕获里常见的陷阱。错误捕获不是凭感觉添加 try catch 语句,它的首要目的是提供有效的错误排查信息,只有精心设计的错误捕获才有可能完成这个使命。针对哪些方面去精心设计就是本篇文章里想讨论的内容

NodeJS 实战系列:模块设计与文件分类

我们从一个最简单的需求开始,来探索我们应该从哪些方面思考模块设计,以及如何将不同的文件分类。之所以说“思考”,是因为我在这篇文章里更多的是提供一类解决问题的范式,而非统一的标准答案,能够为你提供一丁点的启发就好

NodeJS 实战系列:个人开发者应该如何选购云服务

这文章至少值一千元,因为这是我保守估计花出去的冤枉钱(请自行脑补一个苦笑的 emoji) 文章中会穿插选择云服务的一些建议,当然也会提供一些“薅羊毛”的技巧。不过在此之前我们要想清楚一件更重要的事情:我为了什么购买云服务 做产品还是做技术 这个问题不仅决定了你接下来的购买策略,还是你编码开始的前提。

NodeJS 实战系列:DevOps 尚未解决的问题

本文将通过展示 NodeJS 应用里环境变量的提取过程,来一窥 DevOps 技术是如何应用在现在云平台上的运维工作中的。同时我也想让大家在这里看到 DevOps 的另外一面,即它并非全能,从本地开发到持续部署再到实际运行,有一些运维鸿沟依然还未被填平。“人工操作”依然是工作中的最大风险。

在nodejs中体验http/2

前言 2015年,HTTP/2 发布,直到2021年公司的项目才开始在实践中应用;自己对http2诸多特点的理解只存在于字面上,于是尝试在nodejs中实践一下,加深自己的理解。 多路复用 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗,这在大量请求同时发出的情

『手撕Vue-CLI』添加终端用户交互

前言 经过上一篇文章的梳理,实现了可以从 GitHub 上拉取模板项目名称,已经可以得知可使用的模板有哪些了,那么我觉得是不是要进行选择呢?所以这一篇文章就来实现终端用户交互,让用户可以自己选择想要使用的模板。 实现 在 NodeJS 当中,已经有人为我们封装好了一个库,叫做 inquirer,可以

NODEJS通过发送json数据查询目标服务,实现服务器状态监控,发现异常发送到微信群提醒

root@aea87fa6e6a2:/home/node# cat login2.js const request = require('request-promise'); const moment = require('moment'); const cron = require('node-c

Nodejs 命令行调用 exec 与 spawn 差异

Nodejs 命令行调用 exec 与 spawn 差异 比如在前端工程项目中 Nodejs 要调用命令行命令如: yarn electron:build exec 调用 yarn 命令,为了能使命令行能实时打印输出正在编译的命令 以异步形式调用 exec 使用 stdout.on 方式监听标准输出

nestjs入门学习总结(三):集成typeorm并实现一个curd操作

### typeorm熟悉 TypeORM 是一个ORM框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5,ES6,ES7,

学node 之前你要知道这些

初识nodejs 19年年底一个偶然的机会接到年会任务,有微信扫码登录、投票、弹幕等功能,于是决定用node 来写几个服务,结果也比较顺利。 当时用看了下koa2的官方文档,知道怎么连接数据库、怎么映射表实体,怎么处理http,怎么处理异常等,就可以直接写起来了。从应用层面上来说 nodejs 入门