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
返回的是一個特殊的包裝對象 Checker
。ide
當參數 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);
注:
not 是一個特殊屬性,會返回一個特殊對象 NotChecker
,這個對象使用 try
執行原對象的檢查方法,catch
到異常則認爲檢查經過。而且 NotChecker
的檢查方法返回的是原對象而不是本身,因此 not.match
以後鏈接 length
時,已經再也不 not
的做用範圍。
因爲德摩根定律的存在,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);
注:
上面的代碼中,has
是一個特殊方法,它檢驗參數中是否包含指定的屬性(own property),若是包含,就返回一個包裝該屬性的 Checker,不然拋檢查失敗的異常。
owner
是一個特殊屬性,它返回包裝上一層對象的 Checker 對象。因此咱們能夠在調用 has
檢查屬性以後,調用 owner
「跳回去」繼續檢查上層對象。
爲了檢驗上面的想法,我實現了一個 js 庫 param-check,代碼位於:
https://github.com/yusangeng/param-check
由於只是一個語言切換是產生的 idea,因此目前這個庫還不完善,實際能有多大意義還很差說,對性能和編程範式的影響還須要評估。