99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

編寫函數(shù)的實踐

2018-02-24 16:17 更新

我們已經(jīng)討論了如何處理異常,那么當你在編寫新的函數(shù)的時候,怎么才能向調(diào)用者傳遞錯誤呢?

最最重要的一點是為你的函數(shù)寫好文檔,包括它接受的參數(shù)(附上類型和其它約束),返回值,可能發(fā)生的錯誤,以及這些錯誤意味著什么。?如果你不知道會導致什么錯誤或者不了解錯誤的含義,那你的應用程序正常工作就是一個巧合。?所以,當你編寫新的函數(shù)的時候,一定要告訴調(diào)用者可能發(fā)生哪些錯誤和錯誤的含義。

Throw, Callback 還是 EventEmitter

函數(shù)有三種基本的傳遞錯誤的模式。

  • throw以同步的方式傳遞異常--也就是在函數(shù)被調(diào)用處的相同的上下文。如果調(diào)用者(或者調(diào)用者的調(diào)用者)用了try/catch,則異??梢圆东@。如果所有的調(diào)用者都沒有用,那么程序通常情況下會崩潰(異常也可能會被domains或者進程級的uncaughtException捕捉到,詳見下文)。

  • Callback 是最基礎的異步傳遞事件的一種方式。用戶傳進來一個函數(shù)(callback),之后當某個異步操作完成后調(diào)用這個 callback。通常 callback 會以callback(err,result)的形式被調(diào)用,這種情況下, err和 result必然有一個是非空的,取決于操作是成功還是失敗。

  • 更復雜的情形是,函數(shù)沒有用 Callback 而是返回一個 EventEmitter 對象,調(diào)用者需要監(jiān)聽這個對象的 error事件。這種方式在兩種情況下很有用。

  • 當你在做一個可能會產(chǎn)生多個錯誤或多個結(jié)果的復雜操作的時候。比如,有一個請求一邊從數(shù)據(jù)庫取數(shù)據(jù)一邊把數(shù)據(jù)發(fā)送回客戶端,而不是等待所有的結(jié)果一起到達。在這個例子里,沒有用 callback,而是返回了一個 EventEmitter,每個結(jié)果會觸發(fā)一個row?事件,當所有結(jié)果發(fā)送完畢后會觸發(fā)end事件,出現(xiàn)錯誤時會觸發(fā)一個error事件。

  • 用在那些具有復雜狀態(tài)機的對象上,這些對象往往伴隨著大量的異步事件。例如,一個套接字是一個EventEmitter,它可能會觸發(fā)“connect“,”end“,”timeout“,”drain“,”close“事件。這樣,很自然地可以把”error“作為另外一種可以被觸發(fā)的事件。在這種情況下,清楚知道”error“還有其它事件何時被觸發(fā)很重要,同時被觸發(fā)的還有什么事件(例如”close“),觸發(fā)的順序,還有套接字是否在結(jié)束的時候處于關閉狀態(tài)。

在大多數(shù)情況下,我們會把 callback 和 event emitter 歸到同一個“異步錯誤傳遞”籃子里。如果你有傳遞異步錯誤的需要,你通常只要用其中的一種而不是同時使用。

那么,什么時候用throw,什么時候用callback,什么時候又用 EventEmitter 呢?這取決于兩件事:

  • 這是操作失敗還是程序員的失誤?
  • 這個函數(shù)本身是同步的還是異步的。

直到目前,最常見的例子是在異步函數(shù)里發(fā)生了操作失敗。在大多數(shù)情況下,你需要寫一個以回調(diào)函數(shù)作為參數(shù)的函數(shù),然后你會把異常傳遞給這個回調(diào)函數(shù)。這種方式工作的很好,并且被廣泛使用。例子可參照 NodeJS 的fs模塊。如果你的場景比上面這個還復雜,那么你可能就得換用 EventEmitter 了,不過你也還是在用異步方式傳遞這個錯誤。

其次常見的一個例子是像JSON.parse這樣的函數(shù)同步產(chǎn)生了一個異常。對這些函數(shù)而言,如果遇到操作失?。ū热鐭o效輸入),你得用同步的方式傳遞它。你可以拋出(更加常見)或者返回它。

對于給定的函數(shù),如果有一個異步傳遞的異常,那么所有的異常都應該被異步傳遞??赡苡羞@樣的情況,請求一到來你就知道它會失敗,并且知道不是因為程序員的失誤??赡艿那樾问悄憔彺媪朔祷亟o最近請求的錯誤。雖然你知道請求一定失敗,但是你還是應該用異步的方式傳遞它。

通用的準則就是?你即可以同步傳遞錯誤(拋出),也可以異步傳遞錯誤(通過傳給一個回調(diào)函數(shù)或者觸發(fā)EventEmitter的?error事件),但是不用同時使用。以這種方式,用戶處理異常的時候可以選擇用回調(diào)函數(shù)還是用try/catch,但是不需要兩種都用。具體用哪一個取決于異常是怎么傳遞的,這點得在文檔里說明清楚。

差點忘了程序員的失誤?;貞浺幌?,它們其實是Bug。在函數(shù)開頭通過檢查參數(shù)的類型(或是其它約束)就可以被立即發(fā)現(xiàn)。一個退化的例子是,某人調(diào)用了一個異步的函數(shù),但是沒有傳回調(diào)函數(shù)。你應該立刻把這個錯拋出,因為程序已經(jīng)出錯而在這個點上最好的調(diào)試的機會就是得到一個堆棧信息,如果有內(nèi)核信息就更好了。

因為程序員的失誤永遠不應該被處理,上面提到的調(diào)用者只能用try/catch或者回調(diào)函數(shù)(或者 EventEmitter)其中一種處理異常的準則并沒有因為這條意見而改變。如果你想知道更多,請見上面的 (不要)處理程序員的失誤。

下表以 NodeJS 核心模塊的常見函數(shù)為例,做了一個總結(jié),大致按照每種問題出現(xiàn)的頻率來排列:

函數(shù) 類型 錯誤 錯誤類型 傳遞方式 調(diào)用者
fs.stat 異步 file not found 操作失敗 callback handle
JSON.parse 同步 bad user input 操作失敗 throw try/catch
fs.stat 異步 null for filename 失誤 throw none (crash)

異步函數(shù)里出現(xiàn)操作錯誤的例子(第一行)是最常見的。在同步函數(shù)里發(fā)生操作失敗(第二行)比較少見,除非是驗證用戶輸入。程序員失誤(第三行)除非是在開發(fā)環(huán)境下,否則永遠都不應該出現(xiàn)。

吐槽:程序員失誤還是操作失???

你怎么知道是程序員的失誤還是操作失敗呢?很簡單,你自己來定義并且記在文檔里,包括允許什么類型的函數(shù),怎樣打斷它的執(zhí)行。如果你得到的異常不是文檔里能接受的,那就是一個程序員失誤。如果在文檔里寫明接受但是暫時處理不了的,那就是一個操作失敗。

你得用你的判斷力去決定你想做到多嚴格,但是我們會給你一定的意見。具體一些,想象有個函數(shù)叫做“connect”,它接受一個IP地址和一個回調(diào)函數(shù)作為參數(shù),這個回調(diào)函數(shù)會在成功或者失敗的時候被調(diào)用?,F(xiàn)在假設用戶傳進來一個明顯不是IP地址的參數(shù),比如“bob”,這個時候你有幾種選擇:

  • 在文檔里寫清楚只接受有效的IPV4的地址,當用戶傳進來“bob”的時候拋出一個異常。強烈推薦這種做法。
  • 在文檔里寫上接受任何string類型的參數(shù)。如果用戶傳的是“bob”,觸發(fā)一個異步錯誤指明無法連接到“bob”這個IP地址。

這兩種方式和我們上面提到的關于操作失敗和程序員失誤的指導原則是一致的。你決定了這樣的輸入算是程序員的失誤還是操作失敗。通常,用戶輸入的校驗是很松的,為了證明這點,可以看Date.parse這個例子,它接受很多類型的輸入。但是對于大多數(shù)其它函數(shù),我們強烈建議你偏向更嚴格而不是更松。你的程序越是猜測用戶的本意(使用隱式的轉(zhuǎn)換,無論是JavaScript語言本身這么做還是有意為之),就越是容易猜錯。本意是想讓開發(fā)者在使用的時候不用更加具體,結(jié)果卻耗費了人家好幾個小時在Debug上。再說了,如果你覺得這是個好主意,你也可以在未來的版本里讓函數(shù)不那么嚴格,但是如果你發(fā)現(xiàn)由于猜測用戶的意圖導致了很多惱人的bug,要修復它的時候想保持兼容性就不大可能了。

所以如果一個值怎么都不可能是有效的(本該是string卻得到一個undefined,本該是string類型的IP但明顯不是),你應該在文檔里寫明是這不允許的并且立刻拋出一個異常。只要你在文檔里寫的清清楚楚,那這就是一個程序員的失誤而不是操作失敗。立即拋出可以把Bug帶來的損失降到最小,并且保存了開發(fā)者可以用來調(diào)試這個問題的信息(例如,調(diào)用堆棧,如果用內(nèi)核文件還可以得到參數(shù)和內(nèi)存分布)。

那么?domains?和?process.on('uncaughtException')?呢?

操作失敗總是可以被顯示的機制所處理的:捕獲一個異常,在回調(diào)里處理錯誤,或者處理EventEmitter的“error”事件等等。Domains以及進程級別的‘uncaughtException’主要是用來從未料到的程序錯誤恢復的。由于上面我們所討論的原因,這兩種方式都不鼓勵。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號