靜態分析可以從另外一個角度來檢查程式碼的問題,例如未宣告的變數,比較不好的撰寫風格(這個是見仁見智)等等。這樣可以讓程式更健全,也更好維護。(有些問題單靠測試恐怕很難抓到)
工具
我知道這方面最有名的工具大概有:
JSLint內建了一個用Javascript寫的Javascript Parser,並且用Parser來分析Javascript原始碼,然後指出在程式碼中找到的問題。(如果你有看過「Beautiful Code」,裡面有一章是Crockford介紹遞迴下降Parser,並且寫了一個精簡版。正式版就用在JSLint以及ADSafe中。另一個有名的Javascript Parser in Javascript是Brendan Eich的Narcissus。)
Closure Linter使用了不同的技術,基本上他是一個使用Regular Expression的Tokenizer,使用的語言是Python。(看起來並不會做出AST)
看起來JSLint比較合胃口,也比較多人用,接下來就看看在node.js環境有沒有相關的工具。
查詢了一下,目前有一個套件叫做node-jslint,裡面包裝了JSLint(都是用Javascript寫的),就拿來試試看吧。
實做
看起來安裝很簡單,只要
npm install -g jslint
就會安裝好。安裝完畢後,以Windows為例,會在node\bin目錄(也就是node.exe所在的目錄)會產生一個jslint.cmd。之後只要執行:
jslint app.js
就可以跑出結果。
測試
接下來,就實際針對lib目錄中的伺服器核心程式做一下檢查,先看一下evolve.js有怎樣的問題:
lib\evolve.js
/*jslint node: true, es5: true */
1 9,42: Confusing use of '++'.
console.log(process.pid + '::' + ++count + ': ' + process.memoryUsage().heapUsed);
在字串串接中加上遞增容易讓人誤解,所以改一下程式吧...把++count改成(++count)看看...OK,沒問題了。接下來測試cache.js:
lib\evolve.js
/*jslint node: true, es5: true */
No errors found.
看起來沒有問題。接下來再看看router.js:
lib\router.js
/*jslint node: true, es5: true */
1 12,31: Expected '{' and instead saw 'path'.
if(path.indexOf('/')!==0) path = '/' + path;
2 20,31: Expected '{' and instead saw 'path'.
if(path.indexOf('/')!==0) path = '/' + path;
3 23,22: Expected '{' and instead saw 'a'.
if(!a[path]) a[path] = {};
4 29,30: Expected '{' and instead saw 'dir'.
if(dir.indexOf('/')!==0) dir = '/' + dir;
5 37,11: 'a' is already defined.
var a = cache.get(host, 'fs');
6 39,13: Move 'var' declarations to the top of the function.
for(var i in a) {
7 39,13: Stopping. (58% scanned).
看起來問題太多,沒跑完,先照他的建議改一改。
lib\router.js
/*jslint node: true, es5: true */
1 48,9: The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.
for(i in a) {
2 62,17: Expected '===' and instead saw '=='.
if(typeof a == 'undefined' || typeof b == 'undefined') {
3 62,44: Expected '===' and instead saw '=='.
if(typeof a == 'undefined' || typeof b == 'undefined') {
還有問題阿...繼續改到好...接下來看一看tools.js:
lib\tools.js
/*jslint node: true, es5: true */
1 1,25: Expected ';' and instead saw 'fs'.
var url = require('url')
2 2,23: Expected ';' and instead saw ','.
fs = require('fs'),
3 2,23: Expected an identifier and instead saw ','.
fs = require('fs'),
4 2,23: Stopping. (1% scanned).
...只scan了1%...不過看了一下程式,第一行的結尾就忘了加comma...靠,改一下:
lib\tools.js
/*jslint node: true, es5: true */
1 17,66: Expected ';' and instead saw '}'.
process.nextTick(function(){cb(true, respath)});
2 28,78: Expected ';' and instead saw '}'.
process.nextTick(function(){cb(true, tmp)});
......
9 93,87: Expected ';' and instead saw '}'.
process.nextTick(function(){tools.getRes(handle.result, conf.dirindex, cb)});
10 98,4: Mixed spaces and tabs.
var tmp1 = cookie_str.split(/[;,] */g),
11 99,4: Mixed spaces and tabs.
tmp2,
12 110,4: Mixed spaces and tabs.
if(request['headers']['Cookie']) {
13 110,16: ['headers'] is better written in dot notation.
if(request['headers']['Cookie']) {
14 110,27: ['Cookie'] is better written in dot notation.
if(request['headers']['Cookie']) {
15 111,21: ['cookie'] is better written in dot notation.
request['cookie'] = tools.cookieParser(request['headers']['Cookie']);
16 111,60: ['headers'] is better written in dot notation.
request['cookie'] = tools.cookieParser(request['headers']['Cookie']);
17 111,71: ['Cookie'] is better written in dot notation.
request['cookie'] = tools.cookieParser(request['headers']['Cookie']);
18 112,4: Mixed spaces and tabs.
} else {
19 113,21: ['cookie'] is better written in dot notation.
request['cookie'] = {};
問題多到無言,只好繼續改到沒有問題為止。
JSLint抓到的問題,其實主要是程式風格的問題,但是也能抓到細微的錯誤,像是連續變數宣告,變數後沒有有加上comma...單元及整合測試能通過是運氣好,因為V8夠聰明,如果是瀏覽器端的程式就很難說了。從這裡也可以看到JSLint的用處。
今天的範例檔(fillano-evolve-v0.0.14-0-g6928eac.zip),目前版本是v0.0.14。(這是把上述問題修改過的版本,可以跟前一個版本比較一下)