iT邦幫忙

4

Week6 - 原來我Server錯誤處理本身的方案就是個錯誤(/゚Д゚)/ - 錯誤處理篇 [NodeJs轉Golang的爆炸之旅系列]

大家好,因為上次的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就有三種可能性,這樣的處理方案有時候就像提供錯誤資訊般。

小智你別鬧了,這到底該如何解決

我這邊依序列出我一路解決的方法:

  1. 一開始,我透過多個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的難閱讀有幾分相似,非常的難閱讀。

  1. 我還是用回原本的方法,並自訂這些error
async 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

  1. 好吧,那不自訂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:

// callback
function(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其實對我整個程序的流程是需要有不同的處理的,

所以與其說「把例外寫在正向邏輯裡會導致邏輯不清楚」,不如說「如果能夠配合更好的邏輯分層,把例外處理算為正向邏輯的一環,其實更能好好的處理邏輯」

大家是怎麼去看待錯誤邏輯的?我的想法是否與你的想法不同?歡迎大家討論,謝謝~

參考


尚未有邦友留言

立即登入留言