對於一個軟件開發團隊,可以通過哪些代碼質量指標和掃描方法讓團隊產出規範、安全、高質量的代碼?讓開發團隊運行的安全、透明、可靠?本文總結了其中一些實踐和工具,包含常見代碼質量掃描工具、代碼質量指標、第三方依賴管理、安全運維等幾個方面,主要適用於 Java/JavaScript 技術棧的 web 項目,希望對於想要規範化自己的項目的 Tech Lead 有所幫助。
對於一個軟件開發團隊,可以通過哪些代碼質量指標和掃描方法讓團隊產出規範、安全、高質量的代碼?讓開發團隊運行的安全、透明、可靠?
本文總結了其中一些實踐和工具,包含常見代碼質量掃描工具、代碼質量指標、第三方依賴管理、安全運維等幾個方面,主要適用於 Java/JavaScript 技術棧的 web 項目,希望對於想要規範化自己的項目的 Tech Lead 有所幫助。
代碼掃描和常見質量指標
“禍患常積於忽微”,往往一些奇怪的 bug 都是一些不規範的小問題造成的。德國飛機渦輪機的發明者帕布斯·海恩提出的一個在航空界關於飛行安全的法則,法則指出: 每一起嚴重事故的背後,必然有 29 次輕微事故和 300 起未遂先兆以及 1000 起事故隱患。應用於軟件開發中,如果項目中代碼混亂不堪,必然會在某個時候最終爆發大量的問題。
這裡整理了一些常見的掃描工具和代碼質量指標,可以在搭建項目基礎設施時引入,用於自動化的檢查代碼中潛在的問題,達到控制代碼產出質量的目的。
掃描工具
checkstyle
checkstyle 是常用於 java 項目的掃描工具,檢查源代碼是否與代碼規範相符,檢查項目主要包括:Javadoc 註釋、imports、過長的類和方法、空格、重複文件、圈複雜度等,默認使用 sun 的代碼規則,也可以配置自定義的代碼規則,例如阿里就發佈了相應的檢查規則。
findbugs
通過 Bug Patterns 的概念,尋找代碼中可能出現的 bug,檢查項目主要包括:不良編程習慣導致的問題、性能問題、安全問題、線程問題等。例如,應使用 equals 判斷相等,而不是 “ =” 操作符、流需要關閉、線程資源需要釋放等問題。findbugs 的模式庫對編程經驗也有較好的提升作用。還可以導入和編寫自己的 Bug Patterns 完善檢查機制。
simian
simian 是一個用於檢查重複和相似代碼的工具,它的重複檢查類似於論文查重,會提示一定的相似度。可以單獨運行,也可以作為 checkstyle 插件來使用,相對來來說比較小眾。
pmd
pmd 是一款跨語言的通用靜態掃描工具,具備一部分 checkstyle、findbugs 的功能,不再贅述。
ESlint/TSlint
前端界的 checkstyle , TSlint 設計用來做 TypeScript 類型檢查,ESlint 作為代碼風格檢查工具。不過現在 ESlint 也提供了TypeScript 類型檢查功能,基本上 ESlint 能整合這兩個功能。由於性能問題, TypeScript 也採用了 ESLint 作為 TSlint替代的檢查工具。
SonarQube
SonarQube 是一款用於代碼質量管理的開源工具,它主要用於管理源代碼的質量。 SonarQube 和上面的工具不太一樣,SonarQube 設計目的是提供一個平臺,通過插件的方式提供對各個語言進行支持,也可以和 checkstyle、pmd、simian 等工具進行集成。SonarQube 一般需要單獨部署成一個服務,提供數據庫,可以記錄掃描結果等信息。
npm audit
npm audit 是 npm 6 之後的版本 自帶的一個前端安全掃描工具,可以掃描 npm 依賴中的潛在的漏洞威脅。這些引入的漏洞可能威脅用戶開發的機,另外也可能被帶入 bundle 文件發佈到線上,帶來安全問題。目前 npm audit 會在 npm install 完成後自動執行,需要留意安全威脅報告。
Fortify SCA
Fortify SCA(Source Code Analyzer) 是一款非常優秀的代碼安全掃描工具,用於分析代碼中潛在的安全問題。通過調用語言的編譯器或者解釋器把代碼(Java、C、C++等源代碼)轉換成一種中間媒體文件 NST(Normal Syntax Trcc),然後通過模式匹配相關的方式抓取存在於漏洞庫中的漏洞。例如,上傳的文件沒有做檢查等 XSS 攻擊。
OWASP Dependency-Track
開放式 Web 應用程序安全項目(OWASP)是一個非營利組織,提供了很多安全標準、數據庫、社區和培訓。其中一個工具就是 OWASP Dependency-Track,可以對第三方依賴包中的知名漏洞進行檢查,掃描結果受到漏洞數據庫的更新影響。
archunit 架構規範檢查
前面的檢查是代碼層面,archunit 可以用於代碼架構檢查,可以定義規則檢查每個包中的實現是否符合規範。例如,controller 包中的類不能實現 service 的接口,repository 下的類必須實現 Repository 接口。通過 archunit 可以減少 codereview 的工作量,避免項目的結構被破壞。
統計工具
sloccount、sourcemointor 這兩個工具可以用於統計代碼數量,包括行數、文件數、註釋等。除了在項目中掃描 bug 之外,配置代碼統計工具可以對項目有一個整體的認知。
其他的掃描工具還很多,例如 coverity、codemars、binscope、synk、appscan、retire.js 等工具,不再一一列舉。
最佳搭配
這幾款工具之間的功能有所重疊,在實際工作中,我們可以根據上面推薦的關注的點,重點清除這些問題。這些掃描工具全部用上除了會帶來團隊壓力和維護成本之外,代碼質量不會隨著引入的插件增多。除開有質量團隊的大廠提供這些掃描平臺外,敏捷團隊往往不會太大,團隊持續關注一個精簡的掃描組合更好。
Java 後端:
- checkstyle Java 代碼風格守護,Java 項目至少應該配置一個默認的 checkstyle 規則。至少讓項目乾淨,沒有無用、重複的代碼,以及超大的類和方法。建議做到每次提交代碼前檢查。
- findbugs 常見不規範的代碼檢查,一些空指針、equals 檢查非常有用,而且 IDE 的插件也很好用。
前端:
- eslint 守護 JavaScript 代碼風格,eslint 搭配一個 .editorconfig ,可以方便的讓編輯器保持同 eslint 一致的代碼風格。
- npm audit 項目中第三方包的威脅掃描,npm 自帶無需額外安裝,npm 6 以後自運行,需要關注並修復報出的安全問題。
安全:
- fortify 掃描代碼中的漏洞,用它檢查出來的大部分安全問題都是注入攻擊、XSS 等攻擊,這些問題明顯可以在開發過程中避免。可以作為 Jenkins 插件配置,和單元測試作為同一階段運行。
- OWASP 插件 用來掃描第三方依賴漏洞,因為項目中的依賴不會像源代碼一樣頻繁變化,推薦使用 Jekins 插件,定期執行即可。
為什麼不用 SonarQube 呢,SonarQube 是一個非常優秀的代碼質量開放平臺,需要單獨的配置安裝,需要花費額外的時間維護,對於小團隊來說成本較高,如果有專門的質量團隊可以考慮維護一套。
常用代碼質量指標參考
- 編譯告警數,大部分程序員基本上忽略 warning,但是編譯器出現了告警是一種不好的體現,意味著軟件可能工作,但是存在不好的實踐,而這種不確定性,會帶來不確定的 bug 最終讓人一頭霧水。編譯過程中的告警,儘量消除掉,編譯告警的值推薦消除到 0。
- 平均函數代碼行數,過大的函數會導致閱讀困難,而且往往過大的函數職責不夠單一,一般將一個方法代碼行數控制到 30 - 50 行。
- 平均文件代碼行,和平均函數代碼行一樣,過長的文件一樣難以維護,一般一個文件10多個方法,因此文件的代碼行數一般控制到 300 - 500 行。
- 冗餘代碼,有時候我們代碼中可能存在未使用的方法、變量等代碼,這讓維護者一頭霧水,通常需要清零。
- 總文件重複率,出現重複文件的次數。除了編寫單元測試的情況下,業務代碼不應該出現重複代碼,推薦值為 0。
- 總代碼重複度,代碼的重複度檢查,限於掃描工具的識別模式,需要有一定的容忍度,推薦值在 5% - 10%
- 平均函數圈複雜度,圈複雜度用來衡量一個模塊判定結構的複雜程度。如果一個方法內部有大量的 if 語句嵌套,意味著這個方法的實現質量低下,且程序複雜度高不利於維護,推薦值小於 5%。
- 安全告警,如果配置了安全掃描工具,例如 Fortify,安全威脅應該被清零。
- 代碼缺陷,如果配置了缺陷掃描工具,例如 Findbugs,需要清零。
第三方依賴規範化
軟件開發過程中,不可避免的需要引入第三方或者開源軟件包作為庫或者框架引入。“第三方” 其實不是一個軟件工程術語,現今在軟件行業裡面的理解是:第一方為自研的軟件,第二方為內部發布的軟件,第三方為從社區或者外部商業途徑引入的軟件包。
對於個人開發者而言,面向“搜索引擎”編程往往將來源不明的代碼片段和程序包引入到項目中。對於企業來說,考慮到的不僅僅是功能是否能實現,還要考慮引入時帶來的成本和問題,例如是否需要授權、開源協議是否合理、是否會帶來安全威脅。
企業對於第三方依賴的引入分為幾種情況:
- 作為開發工具引入,例如 gcc、Jenkins,基本沒有開源協議問題,但是需要注意開發機、CI 會有安全風險。Jenkins 曾出現過漏洞,CI 服務器被當做遠程礦機使用。
- 作為服務部署使用(SaaS ),部分開源協議會限制這種使用方式,第三方依賴的安全問題會威脅服務器。
- 通過軟件包再發布,大部分開源軟件對這種使用方式有較多要求,例如 GPL 開源協議具有傳染性,要求使用了 GPL 的項目也要開源。
- 拷貝源代碼引入項目,非常不推薦這種方式,儘量通過包管理的方式引入。
引入第三方依賴需要充分考慮,儘可能最小成本的引入。在一個 React 的前端項目中,有不熟悉的工程師,為了使用一個簡單的手風琴效果,引入了整套 bootstrap。不僅破壞了使用 React 的最佳實踐,而且讓輸出的 bundle 文件大小激增數倍,造成首屏加載的性能問題。
常見商業友好的開源協議
商業用戶常用的開源協議實際上只有6種左右,即 LGPL、Mozilla、GPL、BSD、MIT、Apache,另外還有極其寬鬆的 The Unlicense,但採用的開源軟件不多。
GitHub 提供了一個 license 清單的列表 https://choosealicense.com/licenses/,
我根據開源協議的寬鬆程度,整理了一個列表,方便查看:
幾乎所有的開源協議有一個共同的注意事項:採用該開源協議的軟件項目,不提供任何責任轉移和質量保證。也就是說採用開源軟件造成的法律問題和開源項目無關,另外需要使用者承擔因質量問題造成的所有後果。另外,除了引入的程序包之外,字體、圖片、特效音、手冊等媒體資源也算廣義上的“軟件”需要考慮開源協議和使用場景。
第三方依賴管理
對項目中出現的任何第三方依賴有效的管理有非常重要的意義,通過掃描工具,識別出項目中是否有源碼、jar包、二進制文件是否來源於某個開源項目。
任何的第三方軟件需要申請入庫管理(內部其他團隊申請通過可以直接使用),質量團隊對申請的軟件進行評估:
- 是否有開源義務需要履行
- 引入的第三方依賴是否有 CVEs等漏洞
- 第三方開源軟件是否仍然在維護
質量團隊根據上面的一些條件,決定出申請的軟件能否在項目中使用,允許被採用的軟件會定義出優選級別,優先推薦團隊使用較為優選的軟件,並對項目整體的優選率有一定要求。如果項目中出現了無法識別的二進制文件、非約定目錄下的代碼片段,需要報備。通過良好的依賴管理和規範化,能減少不良第三方依賴的引入,讓軟件項目透明、可信。
一些商業公司提供這些完整的服務,例如 fossid、blackduck、code-climate 等。
運維安全
大的軟件公司,往往有一堆流程和要求。雖然一線開發對堡壘機、防火牆、各種安全規範顯得不耐煩,但這些安全措施也在保護開發者。
防火牆用於環境隔離
往往開發者理解的防火牆用於防止網絡入侵、審計、入侵檢測等功能,除此之外,防火牆還可以用於各個環境的隔離。一般來說,企業對於生產環境的數據控制比較嚴格,不會將生產環境的權限交給團隊所有開發者,但網絡連接有可能疏漏。
曾經出現過一次線上事故,由於配置文件錯誤,將原本應該連接到測試的數據庫連接到了生產環境,造成大量髒數據寫入。如果通過防火牆規則對各個環境進行隔離,這類問題將不會出現。
另外也可以設計 DMZ 區,將面向用戶側的網關部署到 DMZ 區,僅僅開放必要的端口給網關,實現內外網的物理隔離。同時,對整個系統的防火牆策略應該清晰地記錄,否則在做大的基礎設施更新時,梳理出所有的防火牆策略,是一件比較困難的事情。
憑據管理
項目中會用到大量的憑據,例如數據庫、第三方系統對接的 key,使用明文不是一件好事。理想的情況下,對項目中所有的密碼信息進行掩蓋(mask),避免 CI、日誌中敏感信息的洩露。
有很多種方法可以掩蓋項目中的密碼信息:
- 使用環境變量對密碼信息進行覆蓋。
- 使用Spring boot 的項目可以配置 jasypt,使用 jasypt 將密碼加密,將生成的加密串配置 ENC(加密串) 到工程的配置文件中。加密過程可以加鹽作為解密的憑據,“鹽” 可以不存放到工程中,在工程部署的時候注入即可。
- 如果使用 Jenkins 等 CI/CD 工具,可以使用構建平臺提供的憑證管理工具。
- 如果使用 Spring cloud,可以使用 spring cloud vault 組件部署一個憑證管理服務
另外,建議不要用任何個人憑據用作系統對接,應該使用一個公共的應用憑據。
堡壘機
一般來說我們管理服務器,所有的運維操作需要通過堡壘機進行操作。開放 22 等高危端口,允許開發者直接登錄到服務器是一種不安全的做法。
堡壘機,通俗的來說是跳板機 + 監控。最初使用的跳板機配置了兩張網卡,用於連接開發環境和生產環境,並沒有監控功能。在此基礎上,堡壘機增加了統一運維管理的功能,往往需要兩步驗證(SMS 或 Email),並對所有的操作進行記錄和監控。
在需要團隊參與運維工作的場景中,非常有必要部署一套堡壘機服務,並使用 LDAP 對接到團隊成員的 ID 上,便於集中運維管理。
定期對系統軟件掃描
Linux 系統往往有云廠商推送安全補丁和風險提示,但是安裝到服務器上的軟件,例如 JDK、nodejs,需要自己檢查安全問題。因此需要在系統中安裝並定期運行 CVEs 檢查並及時更新。有一款 cvechecker 可以幫助運維人員,編寫一個腳本定期運行 cvechecker 檢查系統中已知的軟件是否存在 CVEs 漏洞,並提醒開發者及時更新。
寫在後面
剛開始工作時候,喜歡動態的、靈活的編程語言,討厭的死板的、套路化的編程語言,然而需要很長一段時間,才能意識到 “約束是程序員的朋友”。對一些安全知識瞭解的來源大多來自修復 SonarQube 的經歷,使用 findbugs 也讓我對 Java 基礎認識的更加深刻。
類似的,在使用一些框架、平臺的時候往往存在大量的限制,有時候開發者難以意識到 “限制” 正是框架、平臺的作者 “保護” 應用開發者的一種方式。有一些開發者以 Hack 框架、平臺為樂,但是這樣會帶來潛在的隱患,在用戶量上來之後負面效應表現的尤為明顯。
項目的規範化對於 Tech Lead來說可以減少程序的運行事故和 codereview 時間,對於團隊來說也許可以少加班吧。
文/ThoughtWorks少個分號