大家好,因为上次的Golang核心处理的文章需要study的部分非常多,所以我还得再花些时间了解,再请大家见谅((´д`)),本週会先介绍Golang与Node.js错误处理可以讨论与借鉴的地方
你是否有写过以下的code
try { await request()} catch (error) { // 处理request的error程序...}
在Node.js里面使用try catch可以妥善的保护程式码,让request()这个非同步函式出错时可以直接跳到catch处执行例外处理,那问题来了,如果现在有多个request()呢?
try { await request1() await request2() await request2()} catch (error) { // 处理request的error程序...}
这时候发生了错误,当log印出
我因为timeout产生error而爆炸了
我们会发现一件事
到底是哪个request timeout了
三个request回传的error都有可能是timeout,是的,我们的确可以从stack log看出是哪个错误,但在对应处理上我们无法区分,有些人会直接回传timeout message,但这message就有三种可能性,这样的处理方案有时候就像提供错误资讯般。
小智你别闹了,这到底该如何解决
我这边依序列出我一路解决的方法:
一开始,我透过多个try catch来捕捉try { await request1()catch (error) { // 处理request1的error程序...}try { await request2()catch (error) { // 处理request2的error程序...}try { await request3()catch (error) { // 处理request3的error程序...}
这的确解决了我的问题,我可以精準的对不同request进行错误处理,但这程式码不觉得很丑吗?这样的使用似乎跟callback hell的难阅读有几分相似,非常的难阅读。
我还是用回原本的方法,并自订这些errorasync request1() { try { // request的程序... } catch (error) { if (error.name === 'TypeError' && error.message === 'Failed to fetch') { throw new customError1('TypeError') } else if (error.name === '其他error name' && error.message === '其他error message') { throw new customError1('其他error') } // ...许多的else if }}// request2, request3也都是各抛出自己的自订error
try { await request1() // 出错会抛出customError1 await request2() // 出错会抛出customError2 await request2() // 出错会抛出customError3} catch (error) { if (error instanceof customError1) { if (error.name === 'TypeError') { // ...处理request1的`TypeError`程序 } else if (error.name === '其他error name') { // ...处理request1的`其他error name`程序 } } else if (error instanceof customError2) { // ...处理request2的error程序 } else if (error instanceof customError2) { // ...处理request3的error程序 }}
这解决的难阅读的问题,但又引来了另个问题,就是除了我要自订这些error以外,我在request里面也得分析他原始的error,并且重新赋予给自订error,这其实有点麻烦,因为我的程式码会看到满满的自订error
好吧,那不自订error,我们透过error赋值const result1 = await request1().catch(error => error)if (result1 instanceof Error) { // 处理request1的error程序...}const result2 = await request2().catch(error => error)if (result2 instanceof Error) { // 处理request2的error程序...}const result3 = await request3().catch(error => error)if (result3 instanceof Error) { // 处理request3的error程序...}
好,我们可以不再自订那么多error了,但现在result有「成功与失败」两种逻辑,这很恼人,这导致了变数的职责很不单一,这个result实际上应该取名resultOrError,但这让一切变得很奇怪
想破头,直到看到Golang的做法
Golang的错误处理中,鼓励大家不使用try catch来处理,而是「好好定义会回传什么错误」
resp, err := http.Get("http://example.com/")if err != nil {// handle error}
原来可以这样,我们把这方法拿回Node.js
async request1() { try { // request的程序... return [result, null] } catch (error) { // ..处理error的程序 return [null, finalError] }}// request2, request3也都是回传阵列
const [result1, error1] = await request1()if (error1) { // 处理request1的error程序...}const [result2, error2] = await request2()if (error2) { // 处理request2的error程序...}const [result3, error3] = await request3()if (error3) { // 处理request3的error程序...}
这样做可以很直接的解决在多个requests之下,出现了什么错误必须要怎么处理的事情,而回传值的职责也更为分明,并且也相当简单。
我目前很喜欢这样的做法,坦白说,这样的做法很类似于以前的callback:
// callbackfunction(err, result) { if (error) { //do error handle } // do something}
我们单纯把callback hell解决,并且也能精确定位error,而github上也有人喜爱此作法,并且把这做法写成了一个await-to-js套件,大家可以下载使用
来探讨一下
不过,这与许多的语言不同,而这边也有几个有趣的例子可以拿来讨论。
Stack Overflow创始者的Joel Spolsky大神认为,使用try catch无疑会造成程式码的一种「跳跃」,这使得我们要对这些例外处理要处理特定状况变得很困难。
而这种跳跃的意思就是类似于"goto",在try範围内对外发送requests时,如果出现了错误,那会直接跳到catch的範围,如果你不说好这些跳到catch範围的error到底是什么,那你就会面临跟goto一样的状况,「哇赛,现在程式码进到goto到底是为什么」
而在与朋友讨论时,曾被问到:「重构一书的Martin Fowler大神在书中提到,程式码应该要可以逐行了解意图,我想问的是,使用Golang这样的方法是不是把「正向的逻辑」与「例外的逻辑」写在了同一个段落呢?」
我个人认为,这是关于设计者怎么去看待这个例外,
有些人认为:
既然是例外了,那本身就属于不正常的事,那就统一处理这类的例外。
有些人认为:
虽然他是一个程序上的例外,但他存在我整体的流程中,所以也是正向逻辑的一环。
而已我来说,我偏向于后者,虽然我不到经验老到,但我碰到许多的状况是
同种例外我需要做不同的处理
正如文章开头timeout的例子,不同request的timeout其实对我整个程序的流程是需要有不同的处理的,
所以与其说「把例外写在正向逻辑里会导致逻辑不清楚」,不如说「如果能够配合更好的逻辑分层,把例外处理算为正向逻辑的一环,其实更能好好的处理逻辑」
大家是怎么去看待错误逻辑的?我的想法是否与你的想法不同?欢迎大家讨论,谢谢~