首頁  >  文章  >  運維  >  Google在建構靜態程式碼分析工具方面的實例分析

Google在建構靜態程式碼分析工具方面的實例分析

WBOY
WBOY轉載
2023-06-05 22:22:591210瀏覽

軟體bug耗費開發者和軟體公司大量的時間和金錢。以2014年為例,被廣泛使用的SSL協議實現中的一個(“goto fail”)bug導致可接受無效的SSL證書,另外一個與日期格式化相關的bug導致Twitter的大範圍服務中斷。此類錯誤通常可以被靜態分析檢測,其實事實上在閱讀程式碼或文件階段均可以快速識別,可以最終現實是情況仍在生產環境實施發生。

先前的工作已經完善通報將bug偵測工具應用於軟體開發的經驗。但雖然開發人員使用靜態分析工具方面有如此多的成功案例,仍有以下原因導致工程師並不總情願使用靜態分析工具或主動忽略工具產生的告警訊息:

  • 未合理整合。工具未整合到開發人員的工作流程中或程式運行時間太長;

  • 無效的警告。警報訊息可行性性差;

  • 不值得信賴。使用者因為誤報而不再信任結果;

  • 缺陷實際利用場景不清晰。報告的bug在理論上是可行的,但缺陷在實際利用情境下並不清晰;

  • #修復成本過高。修復已偵測到的程式碼缺陷的成本太高或有其他方面的風險;

  • 警告不易理解。使用者並不了解警告訊息的具體資訊和原則。

接下來本文描述了我們如何吸取Google在先前使用FindBug進行java語言分析方面以及學術文獻中的得到的經驗和教訓,最終在Google公司成功構建了軟體工程師日常使用的靜態分析基礎架構架構。借助吸收於工程師的意見建議,Google的工具可以在有問題的代碼被合入到公司級別的代碼倉庫之前檢測到每天工程師所修復的數千個問題。

在工具作用範圍方面,我們專注於將靜態分析融入為Google核心開發流程的一部分,並服務大部分Google開發人員。許多靜態程式碼分析工具在部署於google的20億行程式碼層級下將相形見絀,因此大規模場景下運行複雜分析的技術的優先順序並不高。

當然必須要考慮到Google外部開發人員在專業領域(例如航空航天和醫學設備領域)工作的可能會使用特定的靜態分析工具和工作流程。同樣開發專案涉及特定類型(例如內核程式碼和裝置驅動程式)的開發人員可能需要進行特定的分析方法。靜態分析方面已經有很多卓越的工作成果,我們並不認為我們所反饋的經驗和心得是獨一無二的,但我們堅信整理和分享我們在提高google的程式碼品質和改善開發體驗方面的工作是有所裨益的。

術語定義。我們使用以下術語定義:分析工具對原始程式碼運行一個或多種“檢查器”,並識別可能呈現軟體故障的“缺陷”。如果開發人員在看到問題後沒有採取積極行動,如果開發人員遇到識別出的缺陷並未採取適當的修復方式,我們將其視為「實際誤報」。如果靜態分析並未準確的識別報告到某項缺陷,但開發人員主動採取措施修改此處程式碼以提高可讀性和可維護性,那麼這就不算是有效的「誤報」。如果一個分析報告了實際的程式碼錯誤,但是開發人員因未理解這處程式碼問題因而沒有採取任何行動,就視為這是一個「實際誤報」。我們使用這種概念區別強調了研發角度的重要性。開發人員而不是工具作者對感知並直接影響工具的誤報率。

Google如何編譯建置軟體

下文我們將概述Google軟體開發流程的關鍵點。在Google,幾乎所有開發工具(開發環境除外)都是集中化和標準化的。這個基礎架構的許多部分都是由內部團隊擁有的Scratch建構的,保留了具備實驗性質的靈活性。

原始碼控制和程式碼所有權。 Google已開發並使用單一來源的程式碼管理系統。並試驗單分支儲存(幾乎)所有的Google專有程式碼。開發人員使用限制分支的「基於trunk」的開發方式,通常以版本發布而不是特性進行劃分。任何工程師經程式碼所有者的批准都可以更改任何程式碼。程式碼所有權是基於路徑劃分;目錄的擁有者對子目錄擁有同樣權限。

系統建置。 Google程式碼庫中的所有程式碼都經過一個編譯環節獨立的Bazel版本進行編譯,也就是說,所有輸入都必須在原始碼控制中明確聲明和儲存以便建構環節的易於分發性和並行化處理。在Google的建置系統中Java規則依賴JDK和經原始碼管理的Java編譯器,並且可以透過快速引入新版本來為所有使用者更新這些二進位檔案。建置通常來自原始程式碼(透過head),幾乎很少有二進位元件簽入分支。因為所有開發人員都使用相同的建置系統,所以任何程式碼都可以編譯成功而不報錯。

分析工具。 Google所使用的靜態分析工具通常並不複雜。 Google基礎架構不支援在這樣層級的系統上執行流程間或基於程式的完整性分析,也沒有大規模使用進階靜態分析技術(例如separation logic技術)。即便是簡單的檢查器也需要分析基礎架構來支援對工作流程進行整合。已部署作為一般開發流程的分析器類型包括:

  • 樣式檢查器(例如Checkstyle,Pylint,和Golint);

  • ##可擴展bug查找編譯器(例如Error Prone,ClangTidy, Clang Thread SafetyAnalysis, Govet,和CheckerFramework),包括但不限於抽象語法樹模式匹配工具,基於類型的檢查器和檢測未調用變數的分析器。

  • 呼叫生產服務的分析器(例如檢查程式碼註解中提到的員工是否仍在Google上工作) );

  • 檢查建構輸出的屬性(例如產出二進位檔案的大小)。

Google的C linter可以捕捉「goto fail」漏洞,其檢查if語句是否後面有括號。基於模式匹配的檢查器會辨識出日期格式化錯誤,那麼導致Twitter宕機的程式碼就不會在Google編譯通過。谷歌開發人員也使用動態分析工具(如AddressSanitizer)來尋找緩衝區漏洞、使用ThreadSanitizerto查找資料競爭問題。這些工具在測試環節,有時甚至是有生產流量的環境中運作。

整合開發環境(IDE)。靜態分析問題在開發過程早期切入點是整合在IDE中。但是Google開發人員使用各種各樣的編輯器,因此在呼叫建置工具之前很難一致地偵測所有開發人員的錯誤。雖然Google確實使用與流行的內部IDE整合的分析,但要求具有可分析的特定IDE前途漫漫,路險而艱。

測試。幾乎所有Google程式碼都包含對應的測試環節,從單元測試到大規模整合測試。測試活動是系統建置中首先需融合的概念,就如編譯環節一樣都是獨立且分散式的。對於大多數項目,開發人員編寫並維護程式碼的測試案例;專案通常沒有單獨的測試或QA組。

Google的持續建置和測試系統會在每次程式碼提交時執行測試,會及時回饋由於開發人員的程式碼變更導致建置失敗或測試用例不通過。它還支援在提交之前測試變更處以避免破壞專案依賴關係。

Code review。每次提交到Google的程式碼都會先通過code review。雖然任何開發人員都可以對Google任何程式碼處進行更改,但程式碼的所有者必須在提交合入之前review才批准更改。此外,即使是程式碼擁有者也必須在提交變更之前review其程式碼。程式碼審查透過一個與其他開發基礎設施緊密整合的集中式、基於Web的工具進行。靜態分析結果可在程式碼審查中展現。

程式碼發布。 Google團隊頻繁發版,大部分的發布驗證和部署過程都是透過「push on green」的方法自動完成的,這意味著難以依靠進行辛勞的手動發布驗證過程。如果Google工程師在生產環境中發現錯誤,則同必須中斷服務相比而言可以以相對較低的成本回退新版本並將其部署到生產伺服器。

從FindBugs學習到的

從2008年到2010年期間早期摸索研究階段,Google的靜態分析技術專注於使用FindBug進行Java分析:由馬裡蘭大學的William Pugh和賓夕法尼亞州約克學院的DavidHovemeyer所創造的獨立工具。其原理為分析已編譯的Java類別檔案並提取可以導致bug的程式碼結構模型。截至2018年1月,FindBugs在google僅作為極少數工程師使用的命令列工具。一個名為「BugBot」的小型Google團隊與原作者Pugh合作,有三次大的嘗試試圖將FindBugs整合到Google開發流程中。

我們透過嘗試學到了以下幾點:

嘗試1:Bug dashboard。最初在2006年,FindBugs被整合為一個集中性的工具來每晚掃描整個谷歌程式碼庫,將產生的結果入庫方便工程師透過dashboard進行檢視。儘管FindBugs在Google的Java程式碼庫中發現了數百個錯誤,但是該dashboard效果微乎其微,因為錯誤訊息dashboard脫離於日常開發流程,而且同現有的其他靜態分析結果不能有機結合。

嘗試2:集中改進bug。

接下來BugBot團隊開始手動分類每晚查找到的新問題,辨識處理相對重要的bug報告。 2009年5月,數百名Google工程師參加了全公司範圍的「Fix it」週活動,聚焦解決FindBugs的告警問題。總共審查了3954個告警訊息(佔總數9473的42%),但實際上只修復了16%(640個)。實際44%的報告結果(1746個)已經提交了bug回饋追蹤。儘管Fixit活動證實FindBugs發現的許多問題都是現實存在的程式碼缺陷,但很大一部分並沒有重要到需要實際採取修復措施的地步。人工手動分類問題、提交bug報告在大規模環境下是難以持續進行的。

嘗試3:整合於程式碼審查。接下來BugBot團隊整合實現了這樣的系統:當review人員得到待review通知時,FindBugs將會自動運行並將掃描結果作為代碼審查的註釋展示出來,以上代碼review團隊已經針對編碼規範/風格問題已經實現完成。谷歌開發人員可以忽略誤報,並針對FindBugs的結果可信度進行篩選。該工具進一步嘗試僅顯示新的FindBugs告警,但有時會因為錯誤的分類將其視為新問題。隨著程式碼審查工具在2011年被替換,這種整合隨著壽終正寢,原因有兩個:實際工作中的高誤報率導致開發人員對工具失去信心,開發人員自由定制進行過濾導致各方對分析結果觀點不一致。

納入編譯流程

在FindBugs的實驗同時期,Google的C 開發流程透過向Clang編譯器新增新的檢查規則而不斷進步。 Clang團隊實現了的新的編譯器檢查器,包括修復建議信息,使用ClangMR以分佈式方法在整個Google代碼庫上運行經過更新的編譯器來優化檢查,並編碼實現修復了代碼庫中的存量bug問題。一旦程式碼庫已標記被修復存量問題,Clang團隊就會應用新檢查器將新問題標記為編譯器錯誤(而不是告警,Clang團隊發現谷歌開發人員會忽略警告)來中止構建,對此必須加以解決才能通過。 Clang團隊透過這項策略非常成功地改進了程式碼庫品質。

我們遵循這個思路並在javac編譯器基礎之上建構了一個簡單易用的基於模式分析的Java靜態分析工具,名為Error Prone。推出的第一個檢查規則名為PreconditionsCheckNotNull,用於檢測程式運行之初是否方法檢測入參為空,例如checkNotNull(“uid為null”,uid)而不是checkNotNull(uid,“uid was null”)。

為了在不破壞任何連續建置的情況下啟動PreconditionsCheckNotNull這樣的檢查器,Error Prone團隊使用它對基於javac的MapReduce程式對整個程式碼庫執行此類檢查,類比ClangMR,使用FlumeJava建置稱之為JavacFlume。 JavacFlume會產生一系列的修復建議,比對其中的不同,然後應用這些到整個程式碼庫進行修復。 Error Prone團隊使用內部工具Rosie,將大規模程式碼變更拆分為小的變更處,每個變更只會影響到單一專案並測試這些變更處,並將它們發送給相應的團隊進行程式碼審查。團隊僅審查適用於其程式碼的修復方案,並且僅在批准通過它們進行合入,Rosie才會提交實際變更。最終所以存量問題的修復和變更都通過,已有缺陷均已處理。團隊正式開啟了編譯器錯誤的方式。

當我們對收到這些補丁的開發人員進行調查反饋時,其中57%收到合入代碼的fix方案的人的樂於得到此類信息,,41%的人持中立態度。只有2%的人反應較為消極會說:“這樣僅僅會加重我的工作量”

編譯器檢查的價值

編譯錯誤是在開發流程的早期顯示並已集成到開發人員工作流程。我們發現擴展編譯檢查器有效地提高了Google的程式碼品質。因為Error Prone中的檢查是內部針對javac的抽象語法樹而不是字節碼(與FindBugs不同)編寫的,所以團隊外部的開發者可以相對容易地進行檢查。利用這些外部貢獻對於提高Error Prone的整體影響力至關重要。截至2018年1月,共有162名作者提供了733項檢查器。

越早報告問題越好

Google的集中建置系統會記錄所有建置流程和建置結果,因此我們確保所有的使用者在指定時間視窗可看到其中的錯誤訊息。我們向最近遇到編譯器錯誤的開發人員和已收到針對相同問題修復建議進行修復的開發人員發送了調查回饋。谷歌開發者認為在編譯時標記出來的問題(與合入代碼時期的補丁相對)會捕獲更重要的錯誤;例如,調查參與者認為74%的問題在編譯時被標記為“切實問題”,而在合入代碼中發現的問題只有21%。此外,調查參與者認為在編譯時發現的問題中有6%(在合入程式碼階段中為0%)是「重要層級」。這個結果可以透過「倖存者偏差效應」來解釋; 也就是說在程式碼提交時錯誤很可能是由更高昂的手段(如測試和程式碼審查)捕獲的。將盡可能多的檢查前置到編譯器中是一種避免這些成本花銷的可靠方法。

編譯器檢查的標準

為了規模推廣我們的工作,因為中斷編譯將是一個較大的動作,所以我們已經定義了在編譯器中啟用檢查的標準,設定為嚴格高標註模式。 Google上的編譯器檢查應簡單易讀、可操作的且易於修復(盡量實現的錯誤訊息提示應該包括可通用實現的修復建議); 沒有產生有效誤報(分析動作不應中斷構建實際上正確的無誤代碼); 並僅報告反饋真實的bug而非風格或編碼規範方面的問題。衡量滿足這些標準的分析器的主要目標不僅僅是簡單檢測問題,而是在整個程式碼庫中自動修復這些編譯器錯誤。但是這些標準也限制了Error Prone團隊在編譯程式碼時啟用的檢查範圍; 許多問題不能被準確檢測或通用的問題修復環節仍然是擺在我們面前的問題。

在程式碼review階段展示警告訊息

一旦Error Prone團隊建立實現了在編譯時檢測問題所需的基礎設施架構,業已證明該方法切實有效,我們希望找到更多有高影響因子的bug,bug不限於我們做的編譯器錯誤檢查和為Java和C 以外的語言提供分析結果。靜態分析結果的第二個整合切入點是Google的程式碼審查工具-Critique;靜態分析結果透過使用Tricorder在Google的程式分析平台的Critique中顯示。截至2018年1月,Google的C 和Java版本的編譯器錯誤都清除,所有分析結果都顯示在編譯器錯誤或在程式碼審查階段。

程式碼審查檢查的標準

與編譯時檢查不同,程式碼審查期間顯示的分析結果允許囊括達到10%的有效誤報率。在程式碼審查期間所期望的回饋並不總是完美無缺的,並且開發者在實際採用之前需評估相應的修復建議。 Google在程式碼審計階段的檢查器應符合以下幾個標準:

易於理解。對工程師來說是明白無誤易於理解的;

方案可行,易於修復。修復程式可能需要比編譯器檢查階段花費更多的時間、思考和工夫,檢查結果應包括有關如何界定問題的指導內容;

不到10%的有效誤報率。開發人員應該感受到檢查器在至少90%的情況下均找到實際bug缺陷;

對程式碼品質有重大影響。發現的問題可能不會影響程式正確運行,但開發人員應該認真對待它們並選擇修復它們。

有些問題嚴重到足以在編譯器中標記出來,致力於但降低或開發自動修復方案並非可行。例如有的修復問題可能需要對程式碼進行重構。將這些檢查結果作為編譯器錯誤啟用將需要手動清理現有的實現,這在Google這樣大規模的程式碼庫上是不可行的。分析工具在程式碼審查中顯示這些檢查可避免引入新問題,讓開發人員決定是否採取措施進行適當的修復。程式碼審查也是報告相對不太重要的問題(如規範問題或簡化最佳化程式碼)的良好時機。根據我們的經驗,在編譯階段出報告對開發人員總是難以欣然接受的,並且使得快速迭代和調試變得更加困難;舉例來說,一處針對程式碼路徑不可達的檢測器可能會阻礙一處用於調試的程式碼區塊。但在程式碼審查時,開發人員正在細心準備完成他們的程式碼;他們正處於虛心接受的心態,更容易接受可讀性和風格細節的問題。

Tricorder

Tricorder設計理念旨在易於擴展,並支援包括靜態和動態分析工具的眾多不同類型的程式分析工具。我們在Tricorder中展示了一些無法作為編譯器錯誤啟用的Error Prone檢查器。 Error Prone也創造了一套新的C 分析元件,它與Tricorder整合稱之為ClangTidy。 Tricorder分析器的報告支援超過30種語言的結果,支援簡單的語法分析如樣式檢查器,利用Java,JavaScript和C 的編譯器信息,並且可以直接與生產資料集成(例如關於當前正在運行的任務作業)。 Tricorder持續在Google取得成功是因為它是支援分析器編寫者的一個生態平台的插件模型,並在程式碼審查過程中高亮顯示可行的修復方案,並提供回饋管道以改進分析器並確保分析器開發人員對正向回饋採取措施。

使用戶能夠做出貢獻。截至2018年1月,Tricorder包括146個分析器,其中125個來自Tricorder團隊之外,7個插件系統用於數百個額外檢查(例如ErrorProne和ClangTidy,它們是包括在七個分析插件系統中的兩個)。

審閱者參與進來提供修復建議。

Tricorder檢查器可以提供為程式碼審查工具提供程式碼review人員和開發者可見的合理修復建議。審閱者可以透過點擊分析結果上的「請修復」按鈕來要求開發者修復缺陷程式碼。直到他們的所有評論(手動和自動發現的)都解決之前,review者通常都不會批准合入程式碼變更。

迭代來自使用者的回饋。除了「請修復」按鈕,Tricorder還提供了一個「無用」按鈕,評論者或提議者可以點擊表示他們不認同分析發現的結果。點擊動作會自動附帶提交bug追蹤器中的錯誤,並將其指向分析器所屬團隊。 Tricorder團隊跟進這些「無用」的點擊標記,計算「請修復」與「無用」之間的點擊比率。如果分析器的比例超過10%那麼Tricorder團隊會停用該分析器直到作者對其進行改進。雖然Tricorder團隊很少有永久性得停用分析器,但它已經停用了一些分析器(在幾個場景下),直到分析器作者刪除和修改完成那些結果繁雜無用的檢查器。

提交的錯誤通常能改善分析器效果,從而大大提高開發人員對這些分析器的滿意度;例如,Error Prone團隊在2014年開發了一個檢查項,它會標記出來當Guava中傳遞太多參數傳遞給類似printf這樣的函數。類似printf的函數其實不接受所有printf說明符,只接受%S。大約每週一次Error Prone團隊將收到一個「無用」bug,聲稱該分析不正確,實際上bug匹配程式碼中的格式統配符數量與實際傳遞的參數數量相符。而當使用者試圖傳遞%s以外的通配佔位符時,任何情況下分析器其實都是正確誤區的。因此團隊將程式碼檢視說明文字更改為直接聲明該函數僅接受%s佔位符並停止獲取有關該檢查的錯誤。

Tricorder的使用規模。截至2018年1月,Tricorder已經分析了每天約50,000次代碼審查變更。在高峰時段每秒進行三次分析。 review者每天點擊「請修復」超過5000次,作者每天應用自動修復方案約3000次。 Tricorder分析器每天收到250次「無用」點擊回饋。

程式碼審查分析的成功表明它在Google的開發人員工作流程中佔據了「最佳位置」。在編譯時顯示的分析結果必須達到相對到的品質和準確度,而依靠分析器不可能滿足來繼續識別嚴重問題。在review和程式碼合入之後,開發人員進行更改所面臨的阻力會增加。因此,開發人員對已經測試和發布的程式碼進行修改時會比較糾結並且不太可能去解決低危險和不太重要的問題。許多其他軟體開發組織中的分析專案(例如針對Android / iOS應用程式的Facebook Infer分析)也強調程式碼審查是報告分析結果的關鍵切入點。

擴展分析器

隨著Google開發者對Tricorder分析器的結果取得認可,他們繼續要求深入擴展分析器。 Tricorder以兩種方式解決這個問題:允許在專案層級客製化並在開發流程的其他環節中添加展示分析結果。在本節中,我們也討論Google尚未利用更複雜的分析技術作為其核心開發流程的部分原因。

專案級自訂

並非所有請求的分析器對於整個Google程式碼庫中都具有同等價值;例如某些分析器有較高的誤報率有關,因此具有相應的高誤報率的檢查器可能需要在特定的項目啟用配置才有效。這些分析器僅對適合的團隊才有用。

為了實現這些需求,我們的目標是使Tricorder達成可客製化。我們先前為FindBugs定制的經驗實踐效果較差;基於使用者層級的客製化導致團隊內部和團隊之間出現差異化導致工具使用率下降。因為每個使用者都可以看到不同的問題視圖,所以沒有辦法確保每個從事相同專案工作的人都能看到特定的問題。如果開發人員從他們團隊的程式碼中刪除了所有未使用的導包,那麼即使其他一個開發人員在刪除未使用的導包方面不一致,但變更會很快被回退拒絕。

為了避免此類問題,Tricorder僅允許在專案層級進行配置,確保對特定專案進行變更的任何人都能看到與該專案相關的分析結果一致視圖。維護結果視圖的一致性使得幾種類型的分析器能夠執行以下動作:

產生二分結果。例如,Tricorder包括用於協定緩衝區定義的分析器,其識別不向後相容的變化。開發人員團隊使用它來確保序列化形式的協定緩衝區中的持久訊息,但對於不以此形式儲存資料的團隊而言則很煩人。另一個例子是有分析器建議使用對於不能使用這些函式庫或語言功能的項目,對Guava或Java程式碼實作沒有意義;

需要特定的設定或程式碼內註解。例如,如果他們的程式碼被適當地註釋,團隊只能使用Checker Framework的null ness去分析。另一個分析器是在合理配置後,將檢查特定Android二進位檔案的二進位大小和函數呼叫次數的成長,並警告開發人員是否為預期成長或是否接近限制範圍;

#支援特定領域的語言(DSL)和特定於團隊的編碼指南。一些Google軟體開發團隊開發了一些小型DSL並且希望運行的相關檢查器。其他團隊已經實現在可讀性和可維護性方面的最佳實踐並希望繼續執行這些檢查;

同時是資源高度利用化的。依照包含動態分析的結果混合分析的案例。這樣的分析為一些團隊提供部分高價值,但對所有人來說成本太高或耗時太多。

截至2018年1月,Google內部大約有70個可選分析,其中2500個項目至少啟用了其中一個。整個公司的數十個團隊正在積極開發新的分析器,大多數隸屬於都在開發工具組之外。

其他工作流程整合點

隨著開發人員對這些工具的信任度增高,他們也要求進一步整合到工作流程中。 Tricorder現在透過提供命令列工具,持續整合系統和程式碼審閱工具提供分析結果。

命令列支援。 Tricorder團隊為開發人員添加了命令列支持,這些開發人員實際上是程式碼管理員,經常瀏覽並清理團隊程式碼庫中的各種警告分析。這些開發人員也非常熟悉每個分析器將產生的修復類型,並且高度信任特定分析器。因此開發人員可以使用命令列工具自動應用給定分析中的所有修復並進行清理變更;

程式碼提交門檻。有些團隊希望特定的分析器可以阻止程式碼提交而不是僅僅出現在程式碼審查工具中。通常要求阻止提交的能力是由具有高度自訂檢查器且保證沒有誤報的團隊提出,通常用在自訂DSL或庫中。

程式碼展示結果。程式碼展示最適合顯示大型專案(或整個程式碼庫)中問題規模。例如,瀏覽有關已棄用API的程式碼時的分析結果可以顯示遷移工作需要多少工作量;或某些安全性和隱私分析是全球性的,需要專業團隊在確定是否有問題之前審查結果。由於預設不會顯示分析結果,因此程式碼瀏覽器允許特定團隊啟用分析視圖,然後掃描整個程式碼庫並審核結果,而這並不會幹擾其他開發人員對這些分析器的注意力。如果分析結果具有關聯修復,則開發人員只需點擊程式碼瀏覽工具即可套用此修復。程式碼瀏覽器也非常適合顯示生產資料利用的分析結果,因為在程式碼提交和運行之前這些資料均不可用。

複雜分析

在Google上廣泛部署的所有靜態分析都相對簡單,儘管有些團隊針對特定領域(例如Android應用程式)的專案特定分析框架進行流程間分析。 Google規模的過程分析在技術上是可行的。但是實施起來這樣的分析非常具有挑戰性。上面說到所有Google的程式碼都存貯在單獨的整體的原始碼倉庫中,因此從概念上講程式碼倉庫中的任何程式碼都可以是任意二進位檔案的一部分。因此可以想像這樣一種情況,其中特定程式碼審查的分析結果將需要分析整個程式碼倉庫。儘管Facebook的Infer專注於過程間分析,將基於分離邏輯的分析器擴展到數百萬行的程式碼庫,但將這種分析器擴展到Google的數十億行程式碼倉庫仍然需要大量的工程化工作。截至2018年1月,實施一個更複雜的分析系統並不是Google的優先考慮因素:

大量投資。前期基礎設施投資將是令人望而卻步的;

需要努力降低誤報率。分析團隊必須開發技術,以顯著降低許多分析器的誤報率和/或嚴格限制該顯示出來哪些錯誤訊息,就如圖infer做的一樣;

還有更多要實施。分析團隊仍然有更多「簡易」的分析器需要去實作和整合;

高昂的前期成本。我們發現這種「簡單」分析器的性價比很高,這是FindBugs的核心動機。相較之下,即使確定更複雜的檢查器的成本ROI,前期成本也很高。

請注意,對於在專業領域(例如航空航太和醫療設備)或特定專案(例如設備驅動程式和手機應用程式)上工作的Google以外的開發人員,此ROI可能會有很大差異。

心得

我們嘗試將靜態分析融入Google工作流程的學費教導以下我們寶貴的經驗教訓:

發現bug缺陷很容易。當程式碼庫夠龐大時,它幾乎包含任何可以想像得到的程式碼模式。即使在具有完整測試覆蓋率和嚴格的程式碼審查流程的成熟程式碼庫中錯誤也在若隱若現。有時候問題在本地檢查中並不明顯,有時由看似人畜無害的重構所引入錯誤。例如考慮以下程式碼片段使用類型為long的欄位f,

##result =

31 * result

  • (int) (f ^ (f >>> 32));

想像下如果開發人員將f的型別改為int會發生什麼事。程式碼繼續編譯,但向右偏移32變成no-op操作,欄位與自身進行異或,變數的hash值變成常數0。結果是f不再影響hashCode方法產生的值。任何能夠計算f類型的工具都可以正確地檢測到向右偏移超過31的情況,我們在Google的程式碼庫中修復了31項出現該錯誤的程式碼,同時在Error Pone中將該檢查納入編譯器錯誤。

由於發現錯誤很容易,Google使用簡單的工具來偵測錯誤類型。接下來分析編寫者根據運行Google程式碼的結果進行微調。

大多數開發人員不會如他們所想的那樣使用靜態分析工具。隨著許多商業工具的發展,Google最初依賴FindBugs的實施,工程師選擇訪問集中的dashboard來查看他們專案中發現的問題,但是其中很少有人真正去這樣查看。尋找已合入程式碼中的錯誤(可能已部署並在沒有使用者可感知到問題的情況下運行)為時已晚。為了確保大多數或所有工程師都能看到靜態分析警告,必須將分析工具整合到工作流程中,並預設為每個人啟用。 Error Prone等項目不提供錯誤dashboard,而是透過額外的檢查器擴展編譯器,並且在程式碼審查時展示分析結果。

開發者的感受至關重要。根據我們的經驗和材料積累,許多嘗試將靜態分析整合到軟體開發組織的嘗試都失敗了。在Google管理階層通常沒有授權工程師使用靜態分析工具。從事靜態分析的工程師必須透過有效實際數據證明其影響力。要使靜態分析專案成功開發者必須感知到他們從中受益並享受使用它的價值。

為了建立成功的分析平台,我們建構了可為開發人員提供高價值的工具。 Tricorder團隊會仔細檢視已修復的問題,實際研究以了解開發人員的感受,使得透過分析工具提交bug更為便捷,並使用所有這些資料來持續改進。開發人員需要建立對分析工具的信任。如果一個工具浪費了開發人員時間的誤報和回饋低階問題,那麼開發人員就會失去信心並忽略結果。

不限於發現錯誤,修復它們。要推廣靜態分析工具,典型的方法是列舉程式碼庫中存在的大量問題。目的是透過指出糾正潛在錯誤或去在未來阻止bug發生來影響採取措施。但是如果開發人員受到不激勵他們採取行動,那麼這種潛在預期結果仍將無法實現。這是一個基本缺陷:分析工具透過它們識別的問題數來衡量它們的實用性,而流程整合會因為只有極少數的bug修復而失敗。相反Google靜態分析團隊會和找bug一樣也負責相應地修復工作,並將其作為是否成功閉環的標準。專注於修復錯誤確保了工具提供可行的建議並最大限度地減少誤報。在許多情況下,修復錯誤就像透過自動化工具找到它們一樣容易。即使對於難以解決的問題,過去五年的研究也凸顯了自動建立靜態分析問題修復的新技術。

分析器開發需群策齊力。雖然特定的靜態分析工具需要專家開發人員編寫分析,但專家可能很少實際上並不知道哪些檢查會產生較大的影響因子。此外分析器專家通常不是特定領域專家(例如使用API​​,語言和安全性的專家)。透過FindBugs整合只有少數Google員工了解如何編寫新檢查器,因此小型BugBot團隊必須自己完成所有工作。這限制了添加新檢查的速度並事實上不能由其他人從他們的領域知識貢獻而獲益。像Tricorder這樣的團隊現在專注於降低開發人員提供的檢查標準,不需要事先具備靜態分析經驗。例如Google工具Refaster允許開發人員透過在程式碼片段之前和之後指定範例來編寫檢查器。由於貢獻者在自己調試錯誤程式碼之後經常有動力做出貢獻,因此新的檢查會逐步節省開發人員時間。

結論

我們的體會心得是重視整合於開發流程是靜態分析工具實施的關鍵。雖然檢查器工具作者可能認為開發人員應該面對他們編寫的程式碼中存在缺陷清單感到高興,但實際上我們並未發現這樣的清單會激勵開發人員去修復這些缺陷。作為分析工具開發人員,我們必須透過實際糾正的缺陷方面來定義衡量效果,而不是給開發人員數字。這意味著我們的責任遠遠超出了分析工具本身。

我們倡議一個專注於儘早推動工作流程整合的系統。盡可能將檢查器作為編譯器錯誤啟用。為了避免中斷建置工具編寫者首先承擔修復程式碼庫中所有現有問題的任務,允許我們不斷前行一步一步地提高Google程式碼庫的品質。由於我們在編譯器中呈現錯誤告警,開發人員在編寫程式碼後立即對其進行處理,以便他們仍然可以進行及時更改。為實現這一目標,我們開發了基礎架構用於運行分析並在整個龐大的Google程式碼庫中產生修復程式。我們也受益於程式碼審查和允許更改數百個文件的提交自動化,當然還有工程文化,其通常容許合入遺留程式碼的變更,因為改進程式碼勝過對修改風險的厭惡。

程式碼審查是提交程式碼之前顯示分析警告的最佳切入點。為了確保開發人員能夠接受分析結果Tricorder僅在開發人員在提交更改之前的修改程式碼階段才會展示問題,並且Tricorder團隊應用一系列標準來選擇要顯示的警告。 Tricorder進一步在程式碼審查工具中收集統計數據,該工具用於偵測分析器產生大量無效警告的根因。

為了克服警告被忽視,我們努力重新贏得得谷歌工程師的信任,發現谷歌開發人員有強烈的偏見去忽視靜態分析,任何誤報率不理想的報告都給他們不作為的理由。分析團隊非常謹慎只有在根據描述客觀標準對其進行審查後才能將檢查結果作為錯誤或警告顯示,因此開發人員很少被分析結果淹沒,混淆或煩惱。調查和回饋管道是此過程的重要品質控制方法。現在開發人員已經對分析結果重新抱持信任感,Tricorder團隊正在滿足在Google開發人員工作流程中更多介入更多分析的需求。

我們在Google上建立了一個成功的靜態分析基礎架構,在編譯時和程式碼審查期間可防止每天有數百個錯誤進入Google程式碼庫。我們希望其他人可以從我們的經驗中獲益,將靜態分析成功整合到自己的工作流程中。

以上是Google在建構靜態程式碼分析工具方面的實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除