javascript 參數檢驗(一):實現一個方便的參數檢驗工具

綜述

javascript 屬於弱類型語言,參數的類型錯誤只能在運行期發現。當你須要 expose 「很是健壯」的接口給外部,或者在調試較大項目的時候,你可能會懷念強類型語言的類型約束,或者 assert 一類東西。javascript

正由於 js 沒有類型約束,也沒有 assert 這樣的「契約型」斷言工具,因此同一我的寫出的 js 代碼,健壯性經常是不穩定的,有時約束多,有時約束少,有時候返回 null,有時候拋異常,而且約束代碼也經常不統一放在函數入口處。java

本文嘗試編寫一種參數檢查工具,期待能緩解相似問題。git

參數檢查

假設,咱們須要給全部接口統一添加穩定的約束,以及約束破壞後統一的反饋行爲(好比崩潰),除了語言原生支持(據說 Eiffel 有這個能力,有興趣的能夠 google 下),最直接的方法就是設計一個相似 assert 的參數檢查函數 check,在每一個函數入口處調用 check 檢查參數,若是檢查失敗則執行既定的失敗反饋。github

若是全部的函數都這樣編寫,就能夠保證全部函數嚴格執行約束,約束破壞後馬上中止運行,並打印相應的信息。正則表達式

接口

咱們很容易大體設想一個 check 接口的模樣——編程

check.setCheckFailedCallback(function (e) {});

function test(a) {
    check(a).檢查1(條件1).檢查2(條件2)……
}

有幾個細節須要討論一下:segmentfault

  • 上面的代碼使用了鏈式調用,鏈式調用的必要性是很顯然的——咱們須要一種組合檢查步驟的方式。爲了實現鏈式調用,check 返回的是一個特殊的包裝對象 Checkeride

  • 當參數 a 經過全部檢查後,代碼向下執行。若是有一個檢查沒有經過,此時須要執行一個反饋。因爲外層代碼可能存在 try 塊,因此這裏拋異常是不可靠的,或者說咱們要想一個辦法拋出一個「不可 catch」的異常。這裏採用的最簡單的辦法,上層設置回調函數 checkFailedCallback,檢查失敗後自行處理結果,同時拋出一個異常。函數

  • check(a) 這種寫法,其實是作不到的。js 裏沒有宏,因此沒有辦法接受一個變量同時拿到變量的名稱。若是要打印出檢查失敗的參數名,須要寫成 check(a, 'a')。這種寫法有點累贅,可能有更好的方案,我還在思考。工具

邏輯組合

剛纔說到鏈式調用能夠用來組合檢查步驟,可是隻有一種組合方式顯然是不行的。由於檢查步驟之間的關係可能有三種:與、或、非。咱們要想辦法使用同一的規則把三種關係表達清楚。

具體就不解釋了,分享一下個人規則:

鏈式調用實現「與」

// a 是 number 型,而且大於 1 小於 3
check(a, 'a').is('number').gt(1).lt(3);

參數表實現「或」

// a 是 number 型,而且位於 [0, 1) || (1, 2] 區間上
check(a, 'a').is('number').within('[0, 1)', '(1, 2]');

注:因爲參數表實現「或」,因此這裏「或」的優先級永遠比「與」高,若是須要「與」比「或」高,則須要一點技巧,具體見我這篇文章

not 屬性實現「非」

// a 是字符串而且不符合正則表達式 /^[\w][\w\d]+$/
check(a, 'a').is('string').not.match(/^[\w][\w\d]+$/);

// a 是字符串而且不符合正則表達式 /^[\w][\w\d]+$/, 而且長度等於 10
check(a, 'a').is('string').not.match(/^[\w][\w\d]+$/).length().eq(10);

注:

  1. not 是一個特殊屬性,會返回一個特殊對象 NotChecker,這個對象使用 try 執行原對象的檢查方法,catch 到異常則認爲檢查經過。而且 NotChecker 的檢查方法返回的是原對象而不是本身,因此 not.match 以後鏈接 length 時,已經再也不 not 的做用範圍。

  2. 因爲德摩根定律的存在,not 後的參數表實際上在表達"與"的關係,好比:

    check(a, 'a').not.is('string', 'number').

    表示的是參數 a 既不爲 string 也不爲 number。

其餘

另外,爲了方便使用,還須要實現一些另外的接口,好比:

// a 包含屬性 foo,大於 1 小於 3; 同時包含屬性 bar, 大於 2 小於 4
check(a, 'a').has('foo').gt(1).lt(3).owner.has('bar').gt(2).lt(4);

注:

  1. 上面的代碼中,has 是一個特殊方法,它檢驗參數中是否包含指定的屬性(own property),若是包含,就返回一個包裝該屬性的 Checker,不然拋檢查失敗的異常。

  2. owner 是一個特殊屬性,它返回包裝上一層對象的 Checker 對象。因此咱們能夠在調用 has 檢查屬性以後,調用 owner「跳回去」繼續檢查上層對象。

代碼

爲了檢驗上面的想法,我實現了一個 js 庫 param-check,代碼位於:
https://github.com/yusangeng/param-check

由於只是一個語言切換是產生的 idea,因此目前這個庫還不完善,實際能有多大意義還很差說,對性能和編程範式的影響還須要評估。

相關文章
相關標籤/搜索