您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關Google在構建靜態代碼分析工具方面的實例分析,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
軟件bug耗費開發者和軟件公司大量的時間和金錢。 以2014年為例,被廣泛使用的SSL協議實現中的一個(“goto fail”)bug導致可接受無效的SSL證書,另外一個與日期格式化相關的bug導致Twitter的大范圍服務中斷。此類錯誤通常可以被靜態分析檢測,其實事實上在閱讀代碼或文檔階段均可以快速識別,可以最終現實是情況仍然在生產環境實施發生。
之前的工作已經完善報道將bug檢測工具應用于軟件開發的經驗。但雖然開發人員使用靜態分析工具方面有如此多的成功案例,仍有以下原因導致工程師并不總情愿使用靜態分析工具或主動忽略工具產生的告警信息:
未合理整合。工具未集成到開發人員的工作流程中或者是程序運行時間太長;
無效的告警。告警信息可行性性差;
不值得信賴。用戶因為誤報而不再信任結果;
缺陷實際利用場景不清晰。報告的bug在理論上是可行的,但缺陷在實際利用場景下并不清晰;
修復成本過高。修復已檢測到的代碼缺陷的成本太高或有其他方面的風險;
告警不易理解。使用者并不了解告警信息的的具體信息和原理。
接下來本文描述了我們如何吸取Google在先前使用FindBug進行java語言分析方面以及學術文獻中的得到的經驗和教訓,最終在Google公司成功構建了軟件工程師日常使用的靜態分析基礎設施架構。借助吸收于工程師的意見建議,Google的工具可以在有問題的代碼被合入到公司級別的代碼倉庫之前檢測到每天工程師所修復的數千個問題。
在工具作用范圍方面,我們專注于將靜態分析融入為Google核心開發流程的一部分,并服務于大部分Google開發人員。許多靜態代碼分析工具在部署于google的20億行代碼級別下將相形見絀,因此大規模場景下運行復雜分析的技術的優先級并不高。
當然必須要考量到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開發人員使用各種各樣的編輯器,因此在調用構建工具之前很難一致地檢測所有開發人員的錯誤。雖然谷歌確實使用與流行的內部IDE集成的分析,但要求具有可分析的特定IDE前途漫漫,路險而艱。
測試。幾乎所有Google代碼都包含相應的測試環節,從單元測試到大規模集成測試。測試活動是系統構建中首先需融合的理念,就如編譯環節一樣都是獨立和分布式的。對于大多數項目,開發人員編寫并維護代碼的測試用例;項目通常沒有單獨的測試或QA組。
Google的持續構建和測試系統會在每次代碼提交時運行測試,會及時反饋由于開發人員的代碼更改導致構建失敗或測試用例不通過。它還支持在提交之前測試變更處以避免破壞項目依賴關系。
Code review。每次提交到Google的代碼都會首先通過code review。雖然任何開發人員都可以對Google任何代碼處進行更改,但代碼的所有者必須在提交合入之前review才批準更改。此外,即使是代碼所有者也必須在提交變更之前review其代碼。代碼審查通過一個與其他開發基礎設施緊密集成的集中式、基于Web的工具進行。靜態分析結果可在代碼審查中展現。
代碼發布。谷歌團隊頻繁發版,大部分的發布驗證和部署過程都是通過“push on green”的方法自動完成的,這意味著難以依靠進行辛勞的手動發布驗證過程。如果Google工程師在生產環境中發現錯誤,同必須中斷服務相比而言可以以相對較低的成本回退新版本并將其部署到生產服務器。
從2008年到2010年期間早期摸索研究階段,谷歌的靜態分析技術專注于使用FindBug進行Java分析:由馬里蘭大學的William Pugh和賓夕法尼亞州約克學院的DavidHovemeyer創建的獨立工具。其原理為分析已編譯的Java類文件并提取可以導致bug的代碼結構模型。截至2018年1月,FindBugs在google僅作為極少數工程師使用的命令行工具。一個名為“BugBot”的小型Google團隊與原作者Pugh合作,有三次大的嘗試試圖將FindBugs集成到Google開發流程中。
我們通過嘗試學到了以下幾點:
嘗試1:Bug dashboard。最初在2006年,FindBugs被整合為一個集中性的工具來每晚掃描整個谷歌代碼庫,將生成的結果入庫方便工程師通過dashboard進行檢視。盡管FindBugs在谷歌的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團隊在編譯代碼時啟用的檢查范圍; 許多問題不能被準確檢測或通用的問題修復環節仍然是擺在我們面前的問題。
一旦Error Prone團隊構建實現了在編譯時檢測問題所需的基礎設施架構,業已證明該方法切實有效,我們希望查找出更多有高影響因子的bug,bug不局限于我們做的編譯器錯誤檢查和為Java和C++以外的語言提供分析結果。靜態分析結果的第二個集成切入點是Google的代碼審查工具-Critique;靜態分析結果通過使用Tricorder在Google的程序分析平臺的Critique中顯示。截至2018年1月,Google的C ++和Java版本的編譯器錯誤均清零,所有分析結果都顯示在編譯器錯誤或在代碼審查階段。
與編譯時檢查不同,代碼審查期間顯示的分析結果允許囊括達到10%的有效誤報率。在代碼審查期間所期望的反饋并不總是完美無缺的,并且開發者在實際采用之前需評估相應的修復建議。 Google在代碼審計階段的檢查器應符合以下幾個標準:
易于理解。對于工程師來說是明白無誤易于理解的;
方案可行,易于修復。修復程序可能需要比編譯器檢查階段花費更多的時間、思考和工夫,檢查結果應包括有關如何界定問題的指導內容;
不到10%的有效誤報率。開發人員應該感受到檢查器在至少90%的情況下均找到實際bug缺陷;
對代碼質量產生重大影響。發現的問題可能不會影響程序正確運行,但開發人員應該認真對待它們并選擇修復它們。
有些問題嚴重到足以在編譯器中標記出來,致力于但降低或開發自動修復方案并非可行。例如有的修復問題可能需要對代碼進行重構。將這些檢查結果作為編譯器錯誤啟用將需要手動清理現有的實現,這在Google這樣大規模的代碼庫上是不可行的。分析工具在代碼審查中顯示這些檢查可避免引入新問題,允許開發人員決定是否采取措施進行恰當的修復。代碼審查也是報告相對不太重要的問題(如規范問題或簡化優化代碼)的良好時機。根據我們的經驗,在編譯階段出報告對開發人員總是難以欣然接受的,并且使得快速迭代和調試變得更加困難;舉例來說,一處針對代碼路徑不可達的檢測器可能會阻礙一處用于調試的代碼塊。但在代碼審查時,開發人員正在細心準備完成他們的代碼;他們正處在虛心接受的心態,更容易接受可讀性和風格細節的問題。
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已經分析了每天大約50000次代碼審查變更。在高峰時段每秒進行三次分析。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在構建靜態代碼分析工具方面的實例分析了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。