編譯期型別檢查 in ClojureScript

^_^肥仔John發表於2017-10-10

前言

 話說"動態型別一時爽,程式碼重構火葬場",雖然有很多不同的意見(請參考),但我們看到勢頭強勁的TypeScript和Flow.js,也能感知到靜態型別在某程度上能幫助我們寫出更健壯的程式碼(當然要基於充分的單元測試上啦)。
 ClojureScript與JavaScript一樣採取動態型別,但由於需要通過Google Closure Compiler編譯後才能執行,因此我們可以如同JS那樣藉助GCC的註解來引入編譯時型別檢查,達到同樣靜態型別的效果。

配置專案設定

GCC的編譯時型別檢查僅當optimizationssimpleadvanced時有效。我們以:cljsbuild下的dev配置為例

:cljsbuild 
  {:builds
   [{:id "dev"
     :main type-check.core
     :output-to "resouces/public/js/type_check.js"
     :optimizations :simple
     :source-map "resources/public/js/type_check.js.map"
     :closure-warnings            ;; 設定GCC編譯時型別檢查
       {:check-types :warning     ;; 務必設定為warning
        :undefined-names :off     ;; 遮蔽goog庫的異常資訊
        :externs-validation :off  ;; 遮蔽goog庫的異常資訊
        :missing-properties :off  ;; 遮蔽goog庫的異常資訊
        }}]}

請注意,:check-types必須設定為:warning,若設定為:error時,就會報Math.imul引發的JSC_DUP_VAR_DECLARATION_TYPE_MISMATCH異常,導致專案其他程式碼均不能被編譯。希望大神指點迷津~~

註解語法

首先GCC用到的註解語法僅為JSDoc的子集,所以直接看GCC的註解即可,而ClojureScript一般就用如下幾個

@private {Type}
標識私有成員,且該成員的資料型別

@type {Type}
標識成員的資料型別

@param {Type} varname Description
標識函式的型參的資料型別,引數名和描述

@return {Type} Description
標識函式返回值的資料型別和描述

@throws {Type}
標識函式可能丟擲異常型別

接下來就是重點了,我們寫了這麼多還不就是想引入資料的型別描述嗎?那關鍵就是上述程式碼中Type到底應該怎麼寫了!
1.標量型別number,string,boolean,null,undefined
注意
一、標量型別預設表示變數或引數的實際值為不可為null(non-nullable)。若要標識為可為null(nullable),那麼只需前置一個問號?即可(?number,?string
2.物件型別Object,Function,Number,String,Boolean,Date和其他Cljs或自定義的物件型別。
注意
一、對於非全限定的物件型別,會自動展開為當前名稱空間的型別(如當前名稱空間為my-proj.core,那麼MyArray會展開為my-proj.core/MyArray
二、物件型別預設表示變數或引數的實際值可為null(nullable)。若要標識為不可為null(non-nullable),那麼只需前置一個感嘆號!即可(如!Object,!Date等)
3.組合型別,如(number|string),即是實際值可為數字也可為字串。
4.集合/字典,Array<Type>表示為陣列型別且其元素型別可以繼續遞迴下去,Object<Type>表示為物件型別且鍵型別為Type,Object<Type1,Type2表示為物件型別且鍵型別為Type1而值型別為Type2
5.函式型別
function(Type1,Type2),表示函式含資料型別為Type1和Type2兩個形參。
function(Type1,Type2):Type3,表示函式含資料型別為Type1和Type2兩個形參,且返回值型別為Type3。
function(...Type),表示函式含資料型別為Type的可變形參,注意可變形參必須作為最後一個形參出現。
function(Type=),表示函式含可選的資料型別為Type的形參,注意可選形參後不能宣告必填的形參。
注意注意!

  1. 形參和逗號間千萬不要留空格,否則編譯時會報警告的哦!
  2. Type為function()時不能在宣告返回值型別,否則編譯時輝報警告!
@param {function(*,function(*):number)} 是不允許的
@param {function(*,function(*))}        只能這樣寫啦

6.什麼型別都可以,*

例項

1.封裝chrome.runtime.onMessage玩玩

(defn on-msg
  "@param {function(*,window.MessageSend,function(*))} handler
   @return {null}"
  [handler]
  (let [this (.. js/chrome -runtime -onMessage)]
    (.addListener this
                  (fn [a b c]
                    (handler a b c)
                    true))))

注意:window.MessageSend既不是GCC內建的型別也不是我們自定義型別,而是外部定義的資料型別,因此我們需要新增externs檔案讓GCC識別。
因此得到的配置如下

:cljsbuild
  {:builds
   [{:id "dev"
     :main type-check.core
     :output-to "resouces/public/js/type_check.js"
     :optimizations :simple
     :source-map "resources/public/js/type_check.js.map"
     :externs ["externs/chrome.js" "externs/chrome_extensions.js"]
     :closure-warnings            ;; 設定GCC編譯時型別檢查
       {:check-types :warning     ;; 務必設定為warning
        :undefined-names :off     ;; 遮蔽goog庫的異常資訊
        :externs-validation :off  ;; 遮蔽goog庫的異常資訊
        :missing-properties :off  ;; 遮蔽goog庫的異常資訊
        }}]}

總結

如官網所講,這部分的內容仍在發展階段,所以還有很多不完善的地方。不過也不影響我們現在就開始使用,因此良好的程式碼註釋從來都需要的!
尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/7625414.html ^_^肥仔John

參考

https://clojurescript.org/reference/compile-time-type-checking
https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler
https://github.com/google/closure-compiler/wiki/Types-in-the-Closure-Type-System
https://github.com/google/closure-compiler/wiki/Warnings

相關文章