編寫新函數(shù)的具體建議

2018-02-24 16:17 更新

我們已經(jīng)談?wù)摿撕芏嘀笇?dǎo)原則,現(xiàn)在讓我們具體一些。

  1. 你的函數(shù)做什么得很清楚。

這點(diǎn)非常重要。每個(gè)接口函數(shù)的文檔都要很清晰的說(shuō)明: - 預(yù)期參數(shù) - 參數(shù)的類型 - 參數(shù)的額外約束(例如,必須是有效的IP地址)

如果其中有一點(diǎn)不正確或者缺少,那就是一個(gè)程序員的失誤,你應(yīng)該立刻拋出來(lái)。

此外,你還要記錄:

  • 調(diào)用者可能會(huì)遇到的操作失?。ㄒ约八鼈兊?code>name)
  • 怎么處理操作失?。ɡ缡菕伋?,傳給回調(diào)函數(shù),還是被 EventEmitter 發(fā)出)
  • 返回值
  1. 使用 Error 對(duì)象或它的子類,并且實(shí)現(xiàn) Error 的協(xié)議。

你的所有錯(cuò)誤要么使用 Error 類要么使用它的子類。你應(yīng)該提供namemessage屬性,stack也是(注意準(zhǔn)確)。

  1. 在程序里通過(guò) Error 的?name?屬性區(qū)分不同的錯(cuò)誤。

當(dāng)你想要知道錯(cuò)誤是何種類型的時(shí)候,用name屬性。 JavaScript內(nèi)置的供你重用的名字包括“RangeError”(參數(shù)超出有效范圍)和“TypeError”(參數(shù)類型錯(cuò)誤)。而HTTP異常,通常會(huì)用RFC指定的名字,比如“BadRequestError”或者“ServiceUnavailableError”。

不要想著給每個(gè)東西都取一個(gè)新的名字。如果你可以只用一個(gè)簡(jiǎn)單的InvalidArgumentError,就不要分成 InvalidHostnameError,InvalidIpAddressError,InvalidDnsError等等,你要做的是通過(guò)增加屬性來(lái)說(shuō)明那里出了問(wèn)題(下面會(huì)講到)。

  1. 用詳細(xì)的屬性來(lái)增強(qiáng) Error 對(duì)象。

舉個(gè)例子,如果遇到無(wú)效參數(shù),把?propertyName?設(shè)成參數(shù)的名字,把?propertyValue?設(shè)成傳進(jìn)來(lái)的值。如果無(wú)法連到服務(wù)器,用?remoteIp?屬性指明嘗試連接到的 IP。如果發(fā)生一個(gè)系統(tǒng)錯(cuò)誤,在syscal?屬性里設(shè)置是哪個(gè)系統(tǒng)調(diào)用,并把錯(cuò)誤代碼放到errno屬性里。具體你可以查看附錄,看有哪些樣例屬性可以用。

至少需要這些屬性:

name:用于在程序里區(qū)分眾多的錯(cuò)誤類型(例如參數(shù)非法和連接失?。?/p>

message:一個(gè)供人類閱讀的錯(cuò)誤消息。對(duì)可能讀到這條消息的人來(lái)說(shuō)這應(yīng)該已經(jīng)足夠完整。如果你從更底層的地方傳遞了一個(gè)錯(cuò)誤,你應(yīng)該加上一些信息來(lái)說(shuō)明你在做什么。怎么包裝異常請(qǐng)往下看。

stack:一般來(lái)講不要隨意擾亂堆棧信息。甚至不要增強(qiáng)它。V8引擎只有在這個(gè)屬性被讀取的時(shí)候才會(huì)真的去運(yùn)算,以此大幅提高處理異常時(shí)候的性能。如果你讀完再去增強(qiáng)它,結(jié)果就會(huì)多付出代價(jià),哪怕調(diào)用者并不需要堆棧信息。

你還應(yīng)該在錯(cuò)誤信息里提供足夠的消息,這樣調(diào)用者不用分析你的錯(cuò)誤就可以新建自己的錯(cuò)誤。它們可能會(huì)本地化這個(gè)錯(cuò)誤信息,也可能想要把大量的錯(cuò)誤聚集到一起,再或者用不同的方式顯示錯(cuò)誤信息(比如在網(wǎng)頁(yè)上的一個(gè)表格里,或者高亮顯示用戶錯(cuò)誤輸入的字段)。

  1. 若果你傳遞一個(gè)底層的錯(cuò)誤給調(diào)用者,考慮先包裝一下。

經(jīng)常會(huì)發(fā)現(xiàn)一個(gè)異步函數(shù)funcA調(diào)用另外一個(gè)異步函數(shù)funcB,如果funcB拋出了一個(gè)錯(cuò)誤,希望funcA也拋出一模一樣的錯(cuò)誤。(請(qǐng)注意,第二部分并不總是跟在第一部分之后。有的時(shí)候funcA會(huì)重新嘗試。有的時(shí)候又希望funcA忽略錯(cuò)誤因?yàn)闊o(wú)事可做。但在這里,我們只討論funcA直接返回funcB錯(cuò)誤的情況)

在這個(gè)例子里,可以考慮包裝這個(gè)錯(cuò)誤而不是直接返回它。包裝的意思是繼續(xù)拋出一個(gè)包含底層信息的新的異常,并且?guī)袭?dāng)前層的上下文。用?verror?這個(gè)包可以很簡(jiǎn)單的做到這點(diǎn)。

舉個(gè)例子,假設(shè)有一個(gè)函數(shù)叫做?fetchConfig,這個(gè)函數(shù)會(huì)到一個(gè)遠(yuǎn)程的數(shù)據(jù)庫(kù)取得服務(wù)器的配置。你可能會(huì)在服務(wù)器啟動(dòng)的時(shí)候調(diào)用這個(gè)函數(shù)。整個(gè)流程看起來(lái)是這樣的:

1.加載配置 1.1 連接數(shù)據(jù)庫(kù) 1.1.1 解析數(shù)據(jù)庫(kù)服務(wù)器的DNS主機(jī)名 1.1.2 建立一個(gè)到數(shù)據(jù)庫(kù)服務(wù)器的TCP連接 1.1.3 向數(shù)據(jù)庫(kù)服務(wù)器認(rèn)證 1.2 發(fā)送DB請(qǐng)求 1.3 解析返回結(jié)果 1.4 加載配置 2 開始處理請(qǐng)求

假設(shè)在運(yùn)行時(shí)出了一個(gè)問(wèn)題連接不到數(shù)據(jù)庫(kù)服務(wù)器。如果連接在 1.1.2 的時(shí)候因?yàn)闆](méi)有到主機(jī)的路由而失敗了,每個(gè)層都不加處理地都把異常向上拋出給調(diào)用者。你可能會(huì)看到這樣的異常信息:

myserver: Error: connect ECONNREFUSED

這顯然沒(méi)什么大用。

另一方面,如果每一層都把下一層返回的異常包裝一下,你可以得到更多的信息:

myserver: failed to start up: failed to load configuration: failed to connect to database server: failed to connect to 127.0.0.1 port 1234: connect ECONNREFUSED。

你可能會(huì)想跳過(guò)其中幾層的封裝來(lái)得到一條不那么充滿學(xué)究氣息的消息:

myserver: failed to load configuration: connection refused from database at 127.0.0.1 port 1234.

不過(guò)話又說(shuō)回來(lái),報(bào)錯(cuò)的時(shí)候詳細(xì)一點(diǎn)總比信息不夠要好。

如果你決定封裝一個(gè)異常了,有幾件事情要考慮:

  • 保持原有的異常完整不變,保證當(dāng)調(diào)用者想要直接用的時(shí)候底層的異常還可用。

  • 要么用原有的名字,要么顯示地選擇一個(gè)更有意義的名字。例如,最底層是 NodeJS 報(bào)的一個(gè)簡(jiǎn)單的Error,但在步驟1中可以是個(gè) IntializationError 。(但是如果程序可以通過(guò)其它的屬性區(qū)分,不要覺(jué)得有責(zé)任取一個(gè)新的名字)

  • 保留原錯(cuò)誤的所有屬性。在合適的情況下增強(qiáng)message屬性(但是不要在原始的異常上修改)。淺拷貝其它的像是syscallerrno這類的屬性。最好是直接拷貝除了?namemessagestack以外的所有屬性,而不是硬編碼等待拷貝的屬性列表。不要理會(huì)stack,因?yàn)榧词故亲x取它也是相對(duì)昂貴的。如果調(diào)用者想要一個(gè)合并后的堆棧,它應(yīng)該遍歷錯(cuò)誤原因并打印每一個(gè)錯(cuò)誤的堆棧。

在Joyent,我們使用?verror?這個(gè)模塊來(lái)封裝錯(cuò)誤,因?yàn)樗恼Z(yǔ)法簡(jiǎn)潔。寫這篇文章的時(shí)候,它還不能支持上面的所有功能,但是會(huì)被擴(kuò)???以期支持。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)