1. 程式人生 > >大話js程式碼靜態檢查

大話js程式碼靜態檢查

1.背景

首先必須承認,靜態程式碼檢查不能解決所有問題!比如說,QA不能指望著靠靜態程式碼檢查來發現rd的程式碼邏輯的bug。而對於javascript,可能就是程式碼某處少了個分號,或者是某些編碼的bad –practice。這些問題可能很小很小,但是對使用者體驗足以造成巨大影響。因此,如果這種檢查真的能發現問題,那麼還是很有必要的。

之後的一個問題是成本:為了發現一個潛在的問題,我們要付出多少精力?靜態檢查給我們的印象是:飛速的掃描一遍程式碼然後返回一大堆資訊——就像一個可能蘊藏金子的沙堆,我們必須有耐心才能在這個沙堆中找到有價值的資訊。顯然這一過程的成本由以下兩部分組成:程式碼掃描+判別有效性。第一個過程往往十分迅速,秒級而已。而第二個過程往往需要人工的介入,這是成本消耗的關鍵點。試想,如果靜態檢查返回過多無用資訊,導致人工檢查耗時過長,則這種檢查的收益就得不償失了。

綜上所述,靜態程式碼檢查面臨的挑戰是:準確性和低成本。

2初識靜態檢查規則與工具

靜態檢查工具jslint&jshint

為了應對QA前端js知識儲備不足的短板,我們除了加強自身的學習之外,一個有效的方法就是“站在巨人的肩膀上”。

1、jslint

前端領域的大牛Douglas Crockford編寫了JsLint[3],將他認為的那些重要的js程式設計實踐作為靜態檢查項,他提出的某些程式設計實踐也確實被人們所接受,因為這確實是容易引起問題的關鍵點(例如,在應該使用===的地方不要使用==等)。不過JsLint也存在一些問題,如它的某些檢查過於嚴苛;遇到for-in語句後會停止檢測;且非開源。。這些問題都使得人們對JSLint“敬而遠之”。

2、jshint

為解決jslint現存的問題,有了JSHint這個專案,此開源專案作為JSLint的一個分支,允許使用者自定義檢查項,使用起來更加輕量級。有了JSHint,程式設計師就可以根據自己的程式設計習慣來定製個性化的檢查策略。文獻[1] 講述了JSHint與JSLint的區別。

JsHint本質上是一個js庫(jshint.js)。筆者也曾膜拜此檔案的程式碼,發現它實際上是一個js的詞法分析器,裡面定義了各種規則,然後對輸入的程式碼字串做切詞和匹配,每發現一個“錯誤”(或是壞的程式設計實踐)就放到結果陣列中儲存起來。因此,如果你會寫js程式碼的話,完全可以在js檔案中引用jshint.js。例如,你可以使用JSHINT函式來進行程式碼檢查,如下所示:

var result = JSHINT(source, options);

其中JSHINT是一個全域性函式,第一個引數source : 必選項。表示需要檢查的程式碼,js或者json,可以傳一個字串或者一個數組。如果傳字串,需要用’\r’或者’\n’來分隔一行一行的程式碼;如果傳陣列,則每一個數組元素表示一行的程式碼。第二個引數option : 可選項。表示程式碼檢查的配置項,具體的配置項含義參見文獻[2],使用key/value字典表示,key就是要檢查的配置項名稱,value是bool型別。返回值:如果程式碼沒有問題,JSHINT會返回一個true;否則返回false。當返回值是false的時候可以使用JSHINT.errors獲取出錯的原因,JSHINT.data包含了本次檢查更詳細的資訊。另外,使用JSHINT.report可以生成錯誤資訊的html報告。

認識檢查規則

prop description
bitwise 如果是true,則禁止使用位運算子
curly 如果是true,則要求在if/while的模組時使用TAB結構
debug 如果是true,則允許使用debugger的語句
eqeqeq 如果是true,則要求在所有的比較時使用===和!==
evil 如果是true,則允許使用eval方法
forin 如果是true,則不允許for in在沒有hasOwnProperty時使用
maxerr 預設是50。 表示多少錯誤時,jsLint停止分析程式碼
newcap 如果是true,則建構函式必須大寫
nopram 如果是true,則不允許使用arguments.caller和arguments.callee
nomen 如果是true,則不允許在名稱首部和尾部加下劃線
onevar 如果是true,則在一個函式中只能出現一次var
passfail 如果是true,則在遇到第一個錯誤的時候就終止
plusplus 如果是true,則不允許使用++或者- -的操作
regexp 如果是true,則正則中不允許使用.或者[^…]
undef 如果是ture,則所有的區域性變數必須先宣告之後才能使用
sub 如果是true,則允許使用各種寫法獲取屬性(一般使用.來獲取一個物件的屬性值)
white 如果是true,則需要嚴格使用空格用法。

不足

牛人的東西很好,但實際使用起來發現有如下的問題:

(1)靜態程式碼檢查規則配置需要修改Jshint的Js原始碼,修改代價較高,且容易出錯;

(2)檢查結果誤報較多,且可讀性差,找到真正是問題的結果需要排查幾K的報錯資訊,效率較低。

3.jshunter:一款基於jshint的靜態程式碼檢查工具

為解決Jshint以上2個突出問題以及擴充套件更多的特性滿足ECOM WEB類產品JS靜態程式碼檢查的準確性、有效性,基於Jshint和Rhino開發了Jshunter這一款工具用於js程式碼語法類錯誤檢查。

Jshunter提供的解決方案如下:

(1)檢查規則靈活配置。Jshunter支援將所有檢查項放到一個配置檔案中,使用者可以通過true或false來自定義個性化的檢查項,而不必修改Jshint原始碼;

(2)清晰醒目的結果報表。Jshunter從繁雜的檢查結果字串中提出關鍵資訊,以Html檔案的形式展現,對掃描結果嚴重程度給出LEVEL標識。非常適用於持續整合的開發模式,作為Hudson上的一個報表呈現;示意圖如下:

(3)支援Html內嵌js程式碼的檢查,提高檢查準確性。業界的Jshint不支援Html內嵌Js程式碼檢查,存在漏檢、漏測的風險。Jshunter解決了這個問題,不但能檢查Js檔案,還能檢查Html中內嵌的檔案;

(4)掃描結果過濾,減少冗餘資訊,提高結果分析效率。通過黑名單過濾和錯誤級別設定,Jshunter支援使用者設定結果提醒級別,目前支援warning,error和ignore,其中被設定ignore的錯誤不會在報表中呈現,減少冗餘資訊的檢視與分析。

具體說來,之前困擾的根源就在於我們解析jshint.js的環境是瀏覽器,受限於瀏覽器的環境,我們不能進行外部檔案讀寫。如果需要將jshint命令列化我們需要找到這樣一個環境:既能解析js程式碼,又能進行檔案讀寫。因此我們找到了rhino——一個能解析js程式碼的java環境[4]。利用java環境,我們讀取外部檔案提取待檢查的js程式碼,之後利用rhino執行大牛寫的jshint.js。

此工具依賴python環境,無需安裝。使用也極其簡單,只需要進入jshunter的主目錄下執行:./hint.py /rest/file/path /path/to/check/*.js就可以了。這裡hint.py是主程式的程式碼,/rest/file/path是該工具生成的報表檔名,/path/to/check/*.js是待檢查的檔案(支援單個檔案和多個檔案的程式碼檢查)。程式執行後會根據主目錄中的check.cfg配置檔案進行檢查,該檔案各個檢查項的具體含義見註釋的描述。另外,如果不希望看到某些提示資訊,可以將這些資訊放到ignore.list黑名單檔案中。也就是說,通過check.cfg和ignore.list,使用者可以定製符合自己產品線特點的檢查策略。

4.小結

本文介紹了和js程式碼靜態檢查技術相關的內容。程式碼檢查的成本很低,且確實能發現一些潛在的問題。Jshint是業界一款開源的靜態程式碼檢查工具,但在易用性方面存在2個突出的問題,針對目前存在的問題,我們基於JSHint開發的一個命令列方式的程式碼檢查工具jshunter,支援自定義的檢查策略配置和黑名單過濾機制,並且能生成格式美觀的報表。