Wolfram 語言中的靜態(tài)分析工具
尋找錯誤并修復它們不僅僅是我的一種激情,更是一種強迫癥。幾年前,作為一名QA(質(zhì)量檢測)開發(fā)人員,我為 Wolfram 語言創(chuàng)建了 MUnit 單元測試框架,這是一個用于編寫和運行語言單元測試的框架。從那時起,我創(chuàng)造了更多的工具來幫助開發(fā)人員編寫更好的 Wolfram 語言代碼,同時在這個過程中檢查出錯誤。
編寫好的測試需要大量的知識和大量的時間。由于我們需要能夠盡快測試和解決問題,以便按期發(fā)布新功能,我們轉(zhuǎn)向靜態(tài)分析,以便能夠做到這一點。
靜態(tài)分析是在運行源代碼之前對其進行檢查的過程,以試圖預測其行為并發(fā)現(xiàn)問題。作為一種測試方法,它是非常有用的。在代碼運行時發(fā)現(xiàn)問題并不總是可行的。運行代碼的成本也很高--如果代碼失敗了,那就更是如此。
考慮到構(gòu)成 Wolfram 語言的大量代碼(有120萬行的內(nèi)核啟動 Wolfram 語言代碼,橫跨1900個文件,還有85萬行的程序包 Wolfram 語言代碼,橫跨3700個文件),必須要有一個策略來測試所有這些代碼的錯誤。Wolfram 公司對 Wolfram 語言的每一個角落都有專門的測試(其中有些是我寫的!)
CodeInspector paclet 是那些重要的靜態(tài)分析工具之一,它使開發(fā)人員能夠完成更好的工作。CodeInspector 包含在最近發(fā)布的 Mathematica 12.2中,它可以掃描 Wolfram 語言代碼并報告問題,而不需要用戶手動運行 paclet。CodeInspector 與 CodeParser 和 CodeFormatter 一起構(gòu)成 CodeTools 套件,供內(nèi)部和外部用戶使用,以提高其 Wolfram Language 代碼的質(zhì)量。
一般來說,靜態(tài)分析不能發(fā)現(xiàn)程序中所有可能的 bug (這是通過 Rice 定理對停止問題的不可控性所產(chǎn)生的結(jié)果)。但是,靜態(tài)分析仍然可以提供大量的重要信息
例如,很容易看出這里的測試中不需要 &&True。
這可能是遺留的調(diào)試代碼,或者僅僅是邏輯上的一個錯誤。靜態(tài)分析工具可能會警告說,&& True 不需要,可以去掉或改成別的東西。雖然靜態(tài)分析工具不能辨別作者的意圖,但它們可以找到值得調(diào)查的 "可能的問題 "的類別。
創(chuàng)建一個靜態(tài)分析工具來測試 Wolfram 語言中的錯誤,有一系列非常具體的挑戰(zhàn)。作為一種編碼語言,Wolfram 語言具有難以置信的動態(tài)和靈活性。雖然這通常被認為是對開發(fā)人員的一種獎勵,但它確實使抽象建模非常困難。函數(shù)可以在運行時被重新定義,而且在 Wolfram語言中精確定義一個值的概念也很復雜。
鑒于語言本身的局限性,CodeInspector 基于語法樹的模式匹配進行輕量級靜態(tài)分析。這類似于其他語言的 "提示工具"。事實上,CodeInspector paclet 的原名是 Lint! 但很快就發(fā)現(xiàn),它所做的工作不僅僅是檢查,所以它被改名為 CodeInspector)。)
CodeInspector 目前有大約兩百條內(nèi)置規(guī)則,可以應用于被檢查的代碼。這些規(guī)則從常見的語法問題(如缺少逗號)到更隱蔽的問題(如在符號求解器中使用 Q 函數(shù))。許多規(guī)則包括修復代碼的建議。
CodeInspector 包含在 Mathematica 12.2 中。如果您使用的是舊版本的 Mathematica,您可以通過運行以下內(nèi)容獲得 CodeInspector:
為了以編程方式獲得以下代碼片斷中所有問題的列表:
...您可以運行這個測試:
要獲得測試中發(fā)現(xiàn)的所有問題的可視化摘要,請使用 CodeInspectSummarize(包含在 CodeInspector paclet 中):
您甚至可以在命令行上使用 CodeInspectSummarize:
有多種方法可以控制 CodeInspectSummarize 的輸出。為了做到這一點,我們需要對問題進行分類,這本身就是一個有趣的問題!這是因為我們需要在以可查詢的方式公開問題的許多屬性與建立一個易于人類使用的系統(tǒng)之間取得適當?shù)钠胶狻_@是因為我們需要在以可查詢的方式暴露問題的許多屬性與擁有一個易于人類消費和理解的系統(tǒng)之間取得適當?shù)钠胶狻?br>
我使用兩個維度,至少現(xiàn)在是這樣:嚴重程度和信心等級。如果輸出顯示有問題,嚴重性表示每個問題有多嚴重。這個問題會不會影響到用戶?它是否會意外地發(fā)射核彈頭?知識就是力量,特別是當您需要了解手頭問題的影響時。
ConfidenceLevel表示該問題實際上是一個問題而不是一個假陽性的置信度。ConfidenceLevel 是一個介于 0.0 和 1.0 之間的真實值。ConfidenceLevel →0.0 意味著對所報告的問題完全沒有信心,而 ConfidenceLevel →1.0 意味著眼前肯定有問題,比如函數(shù)中不匹配的括號。ConfidenceLevel 為 0.5 意味著大約有一半的時間出現(xiàn)這種問題,是一個假陽性。在括號不匹配的情況下,ConfidenceLevel 是1.0。CodeInspector 中更多的實驗性規(guī)則會有更低的 ConfidenceLevel,當我添加啟發(fā)式方法來消除假陽性時,我會增加問題的 ConfidenceLevel。為我的目的重新使用 ConfidenceLevel 符號可能是對符號的濫用,但它很方便。
因為 Wolfram 語言是如此的動態(tài),很難判斷一個所謂的 bug 實際上是一個錯誤。即使在前面的示例中,If 語句也可能是故意編寫的。僅語法錯誤,例如:
......可以百分百確定地被標記出來。請注意,即使是 "明顯的 "問題,如:
...不一定有 ConfidenceLevel → 1.0。因此,CodeInspector 報告的每個問題都有一個相關(guān)的 ConfidenceLevel,表明置信該問題實際上是一個問題。
默認情況下,CodeInspectSummarize 會報告 95% 或更高置信度的問題。
還有四種與問題相關(guān)的不同嚴重程度:
Remark 是代碼風格的問題,而不是其他問題。
Warning 是一個可能不會產(chǎn)生不正確結(jié)果的問題,但仍然是不正確的。
Error 是一個將執(zhí)行不正確的代碼并給出不正確的結(jié)果的問題。
Fatal 是一個無法恢復的錯誤,如語法錯誤。
這些嚴重程度應該與 ConfidenceLevel 同時詮釋。只有當問題不是假陽性時,嚴重程度才有意義。
這說明了一個常見的錯誤:忘記了 &。
從#的位置開始,我們沿著樹尋找一個匹配的 &:
沒有發(fā)現(xiàn)&,所以報告了一個問題。注意這個規(guī)則的置信度較低,我需要指定ConfidenceLevel → 0.8才能看到它。
您可以根據(jù)您關(guān)心的語法從不同的規(guī)則中選擇。例如,如果您想用一條規(guī)則來查找實數(shù)加到整數(shù)上的情況,那么您就不關(guān)心 1.2+3 與 Plus [1.2, 3] 的具體語法。
語法有三個不同的層次:
具體的語法:空白很重要。
聚合語法:瑣事已被刪除,您關(guān)心的是實際使用的運算符。
抽象語法:更抽象的問題,如未使用的變量、壞符號、壞函數(shù)調(diào)用等。
示例1:
在這個例子中,我忘了在行末加一個分號,所以整個表達式被當作 a=1*a+b 處理。這是不正確的,會導致代碼運行時的無限遞歸:
示例2:
在這個例子中,我忘了給 PatternTest 插入一個問號。
CodeInspector 會捕捉到 Q 函數(shù)被當作頭的情況,并建議插入一個問號:
示例3:
在這個例子中,我試圖用 ImageDimensions 的輸出來指定 ImageSize,但這兩個函數(shù)的單位并不相同。ImageSize 選項期望的是點,但 ImageDimensions 則返回像素:
CodeInspector 會定期在 Wolfram Research 的開發(fā)人員編寫的內(nèi)部代碼上運行。以下是最近遇到的兩個問題,被 CodeInspector 發(fā)現(xiàn)并修復。這些問題很微妙,通過編寫測試很難發(fā)現(xiàn)。
問題1:
需要用括號來包住整個右邊的內(nèi)容。原來的代碼相當于:
這當然不是作者的本意。
問題2:
inc后面的額外下劃線_意味著{__}被當作inc的可選值。但我們的目的是讓 inc 匹配模式 {__}。CodeInspector 能夠發(fā)現(xiàn)這些問題,并在發(fā)布代碼前將其修復。
CodeInspectSummarize 報告指定文件的問題的方式與報告指定字符串的問題的方式完全相同。
由于 Wolfram 語言代碼是解釋的,因此沒有編譯步驟,可能不清楚何時是掃描問題的最佳時機。在實踐中,我發(fā)現(xiàn)在構(gòu)建小程序的時候是掃描的好時機。
我已經(jīng)為 CMake 編寫了腳本,在構(gòu)建 paclet 之前掃描每個 Wolfram Language 文件。下面是當我的代碼中有錯別字時,我試圖構(gòu)建 CodeInspector paclet 本身時的情況:
因此,我可以看到我的代碼中的錯別字,并立即在源代碼中修復它。否則,我就會用糟糕的代碼構(gòu)建 paclet,并在試圖運行代碼時遇到奇怪的錯誤。這凸顯了盡快發(fā)現(xiàn)并修復問題的眾多原因之一--通過測試 CodeInspector 本身來證明 CodeInspector 的意義。
工程師必備
- 項目客服
- 培訓客服
- 平臺客服
TOP




















