

捕捉常見的問題
尋找錯誤并修復它們不僅僅是我的一種激情,更是一種強迫癥。幾年前,作為一名QA開發(fā)人員,我為Wolfram語言創(chuàng)建了MUnit單元測試框架,這是一個用于編寫和運行語言單元測試的框架。從那時起,我創(chuàng)造了更多的工具來幫助開發(fā)人員編寫更好的Wolfram語言代碼,同時在這個過程中無縫地檢查出錯誤。
編寫好的測試需要大量的知識和大量的時間。由于我們需要能夠盡快測試和解決問題,以便按期發(fā)布新功能,我們轉(zhuǎn)向靜態(tài)分析,以便能夠做到這一點。


什么是靜態(tài)分析?
靜態(tài)分析是在運行源代碼之前對其進行檢查的過程,以試圖預測其行為并發(fā)現(xiàn)問題。作為一種測試方法,它是非常有用的。在代碼運行時發(fā)現(xiàn)問題并不總是可行的。運行代碼的成本也很高--如果代碼失敗了,那就更是如此。
考慮到構成Wolfram語言的大量代碼(有120萬行的內(nèi)核啟動Wolfram語言代碼,橫跨1900個文件,還有85萬行的paclet Wolfram語言代碼,橫跨3700個文件),必須要有一個策略來測試所有這些代碼的錯誤。Wolfram對Wolfram語言的每一個角落都有專門的測試--其中有些是我寫的!
CodeInspector paclet 是那些重要的靜態(tài)分析工具之一,它使開發(fā)人員能夠完成更好的工作。CodeInspector包含在最近發(fā)布的Mathematica 12.2中,它可以掃描Wolfram語言代碼并報告問題,而不需要用戶手動運行paclet。CodeInspector 與 CodeParser 和 CodeFormatter 一起構成 CodeTools 套件,供內(nèi)部和外部用戶使用,以提高其 Wolfram Language 代碼的質(zhì)量。
一般來說,靜態(tài)分析不能發(fā)現(xiàn)程序中所有可能的bug(這是通過Rice定理對停止問題的不可控性所產(chǎn)生的結果)。但是,靜態(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
CodeInspector 包含在 Mathematica 12.2 中。如果您使用的是舊版本的Mathematica,您可以通過評估以下內(nèi)容獲得CodeInspector:


為了以編程方式獲得以下代碼片斷中所有問題的列表:


...您可以運行這個測試:


要獲得測試中發(fā)現(xiàn)的所有問題的可視化摘要,請使用CodeInspectSummarize(包含在CodeInspector paclet中):


您甚至可以在命令行上使用CodeInspectSummarize:


有多種方法可以控制CodeInspectSummarize的輸出。為了做到這一點,我們需要對問題進行分類,這本身就是一個有趣的問題!這是因為我們需要在以可查詢的方式公開問題的許多屬性與建立一個易于人類使用的系統(tǒng)之間取得適當?shù)钠胶?。這是因為我們需要在以可查詢的方式暴露問題的許多屬性與擁有一個易于人類消費和理解的系統(tǒng)之間取得適當?shù)钠胶狻?/p>
我使用兩個維度,至少現(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報告的每個問題都有一個相關的ConfidenceLevel,表明該問題實際上是一個問題的信心。
默認情況下,CodeInspectSummarize 會報告 95% 或更高置信度的問題。
還有四種與問題相關的不同嚴重程度:
這些嚴重程度應該與ConfidenceLevel同時解釋。只有當問題不是假陽性時,嚴重程度才有意義。
CodeInspector 如何運行
Wolfram語言有一個強大的內(nèi)置模式匹配器,它可以用來對表達式進行靜態(tài)分析。
我設計了 CodeInspector 的規(guī)則引擎,以包括對被檢查代碼的相對位置的了解,因此我們可以在語法樹上移動到父節(jié)點并提出其他問題。這在編寫規(guī)則以確保某些語法出現(xiàn)在其他容器語法的詞法中時很有用。
例如:


這說明了一個常見的錯誤:忘記了&。
從#的位置開始,我們沿著樹尋找一個匹配的&:


沒有發(fā)現(xiàn)&,所以報告了一個問題。注意這個規(guī)則的置信度較低,我需要指定ConfidenceLevel → 0.8才能看到它。
您可以根據(jù)您關心的語法從不同的規(guī)則中選擇。例如,如果您想用一條規(guī)則來查找實數(shù)加到整數(shù)上的情況,那么您就不關心1.2+3與Plus[1.2, 3]的具體語法。
語法有三個不同的層次:
捕捉常見的問題
示例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ā)布代碼前將其修復。
CodeInspector 工作流程
CodeInspectSummarize 報告指定文件的問題的方式與報告指定字符串的問題的方式完全相同。
由于Wolfram語言代碼是解釋的,因此沒有編譯步驟,可能不清楚何時是掃描問題的最佳時機。在實踐中,我發(fā)現(xiàn)在構建小程序的時候是掃描的好時機。
我已經(jīng)為CMake編寫了腳本,在構建paclet之前掃描每個Wolfram Language文件。下面是當我的代碼中有錯別字時,我試圖構建 CodeInspector paclet 本身時的情況:


因此,我可以看到我的代碼中的錯別字,并立即在源代碼中修復它。否則,我就會用糟糕的代碼構建paclet,并在試圖運行代碼時遇到奇怪的錯誤。這凸顯了盡快發(fā)現(xiàn)并修復問題的眾多原因之一--通過測試CodeInspector本身來證明CodeInspector的意義。
CodeInspector中不斷有新的規(guī)則加入,您可以在GitHub上的CodeInspector資源庫中查看。目前的許多規(guī)則都是受到用戶建議的啟發(fā),所以如果您有任何想法或建議,請在評論區(qū)告訴我。
京ICP備09015132號-996 | 違法和不良信息舉報電話:4006561155
© Copyright 2000-2026 北京哲想軟件有限公司版權所有 | 地址:北京市海淀區(qū)西三環(huán)北路50號豪柏大廈C2座11層1105室
北京哲想軟件集團旗下網(wǎng)站:哲想軟件 | 哲想動畫