如何產出規范、安全、高質量的代碼?
對于一個軟件開發團隊,可以通過哪些代碼質量指標和掃描方法讓團隊產出規范、安全、高質量的代碼?讓開發團隊運行的安全、透明、可靠?本文總結了其中一些實踐和工具,包含常見代碼質量掃描工具、代碼質量指標、第三方依賴管理、安全運維等幾個方面,主要適用于 Java/JavaScript 技術棧的 web 項目,希望對于想要規范化自己的項目的 Tech Lead 有所幫助。
代碼掃描和常見質量指標
“禍患常積于忽微”,往往一些奇怪的 bug 都是一些不規范的小問題造成的。德國飛機渦輪機的發明者帕布斯·海恩提出的一個在航空界關于飛行安全的法則,法則指出: 每一起嚴重事故的背后,必然有 29 次輕微事故和 300 起未遂先兆以及 1000 起事故隱患。應用于軟件開發中,如果項目中代碼混亂不堪,必然會在某個時候最終爆發大量的問題。
這里整理了一些常見的掃描工具和代碼質量指標,可以在搭建項目基礎設施時引入,用于自動化的檢查代碼中潛在的問題,達到控制代碼產出質量的目的。
掃描工具
(1) checkstyle:checkstyle 是常用于 java 項目的掃描工具,檢查源代碼是否與代碼規范相符,檢查項目主要包括:Javadoc 注釋、imports、過長的類和方法、空格、重復文件、圈復雜度等,默認使用 sun 的代碼規則,也可以配置自定義的代碼規則,例如阿里就發布了相應的檢查規則。
(2) findbugs:通過 Bug Patterns 的概念,尋找代碼中可能出現的 bug,檢查項目主要包括:不良編程習慣導致的問題、性能問題、安全問題、線程問題等。例如,應使用 equals 判斷相等,而不是 “ =” 操作符、流需要關閉、線程資源需要釋放等問題。findbugs 的模式庫對編程經驗也有較好的提升作用。還可以導入和編寫自己的 Bug Patterns 完善檢查機制。
(3) simian:simian 是一個用于檢查重復和相似代碼的工具,它的重復檢查類似于論文查重,會提示一定的相似度。可以單獨運行,也可以作為 checkstyle 插件來使用,相對來來說比較小眾。
(4) pmd:pmd 是一款跨語言的通用靜態掃描工具,具備一部分 checkstyle、findbugs 的功能,不再贅述。
(5) ESlint/TSlint:前端界的 checkstyle , TSlint 設計用來做 TypeScript 類型檢查,ESlint 作為代碼風格檢查工具。不過現在 ESlint 也提供了TypeScript 類型檢查功能,基本上 ESlint 能整合這兩個功能。由于性能問題, TypeScript 也采用了 ESLint 作為 TSlint替代的檢查工具。
(6) SonarQube:SonarQube 是一款用于代碼質量管理的開源工具,它主要用于管理源代碼的質量。SonarQube 和上面的工具不太一樣,SonarQube 設計目的是提供一個平臺,通過插件的方式提供對各個語言進行支持,也可以和 checkstyle、pmd、simian 等工具進行集成。SonarQube 一般需要單獨部署成一個服務,提供數據庫,可以記錄掃描結果等信息。
(7) npm audit:npm audit 是 npm 6 之后的版本 自帶的一個前端安全掃描工具,可以掃描 npm 依賴中的潛在的漏洞威脅。這些引入的漏洞可能威脅用戶開發的機,另外也可能被帶入 bundle 文件發布到線上,帶來安全問題。目前 npm audit 會在 npm install 完成后自動執行,需要留意安全威脅報告。
(8) Fortify SCA:Fortify SCA(Source Code Analyzer) 是一款非常優秀的代碼安全掃描工具,用于分析代碼中潛在的安全問題。通過調用語言的編譯器或者解釋器把代碼(Java、C、C++等源代碼)轉換成一種中間媒體文件 NST(Normal Syntax Trcc),然后通過模式匹配相關的方式抓取存在于漏洞庫中的漏洞。例如,上傳的文件沒有做檢查等 XSS 攻擊。
(9) OWASP Dependency-Track:開放式 Web 應用程序安全項目(OWASP)是一個非營利組織,提供了很多安全標準、數據庫、社區和培訓。其中一個工具就是 OWASP Dependency-Track,可以對第三方依賴包中的知名漏洞進行檢查,掃描結果受到漏洞數據庫的更新影響。
(10) 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,安全威脅應該被清零。
- 代碼缺陷,如果配置了缺陷掃描工具,例如 Findbus,需要清零。
第三方依賴規范化
軟件開發過程中,不可避免的需要引入第三方或者開源軟件包作為庫或者框架引入。“第三方” 其實不是一個軟件工程術語,現今在軟件行業里面的理解是:第一方為自研的軟件,第二方為內部發布的軟件,第三方為從社區或者外部商業途徑引入的軟件包。對于個人開發者而言,面向“搜索引擎”編程往往將來源不明的代碼片段和程序包引入到項目中。對于企業來說,考慮到的不僅僅是功能是否能實現,還要考慮引入時帶來的成本和問題,例如是否需要授權、開源協議是否合理、是否會帶來安全威脅。企業對于第三方依賴的引入分為幾種情況:
- 作為開發工具引入,例如 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 等。
運維安全
大的軟件公司,往往有一堆流程和要求。雖然一線開發對堡壘機、防火墻、各種安全規范顯得不耐煩,但這些安全措施也在保護開發者。
1. 防火墻用于環境隔離
往往開發者理解的防火墻用于防止網絡入侵、審計、入侵檢測等功能,除此之外,防火墻還可以用于各個環境的隔離。一般來說,企業對于生產環境的數據控制比較嚴格,不會將生產環境的權限交給團隊所有開發者,但網絡連接有可能疏漏。曾經出現過一次線上事故,由于配置文件錯誤,將原本應該連接到測試的數據庫連接到了生產環境,造成大量臟數據寫入。如果通過防火墻規則對各個環境進行隔離,這類問題將不會出現。另外也可以設計 DMZ 區,將面向用戶側的網關部署到 DMZ 區,僅僅開放必要的端口給網關,實現內外網的物理隔離。同時,對整個系統的防火墻策略應該清晰地記錄,否則在做大的基礎設施更新時,梳理出所有的防火墻策略,是一件比較困難的事情。
2. 憑據管理
項目中會用到大量的憑據,例如數據庫、第三方系統對接的 key,使用明文不是一件好事。理想的情況下,對項目中所有的密碼信息進行掩蓋(mask),避免 CI、日志中敏感信息的泄露。有很多種方法可以掩蓋項目中的密碼信息:
- 使用環境變量對密碼信息進行覆蓋。
- 使用Spring boot 的項目可以配置 jasypt,使用 jasypt 將密碼加密,將生成的加密串配置 ENC(加密串) 到工程的配置文件中。加密過程可以加鹽作為解密的憑據,“鹽” 可以不存放到工程中,在工程部署的時候注入即可。
- 如果使用 Jenkins 等 CI/CD 工具,可以使用構建平臺提供的憑證管理工具。
- 如果使用 Spring cloud,可以使用 spring cloud vault 組件部署一個憑證管理服務
另外,建議不要用任何個人憑據用作系統對接,應該使用一個公共的應用憑據。
3. 堡壘機
一般來說我們管理服務器,所有的運維操作需要通過堡壘機進行操作。開放 22 等高危端口,允許開發者直接登錄到服務器是一種不安全的做法。堡壘機,通俗的來說是跳板機 + 監控。最初使用的跳板機配置了兩張網卡,用于連接開發環境和生產環境,并沒有監控功能。在此基礎上,堡壘機增加了統一運維管理的功能,往往需要兩步驗證(SMS 或 Email),并對所有的操作進行記錄和監控。在需要團隊參與運維工作的場景中,非常有必要部署一套堡壘機服務,并使用 LDAP 對接到團隊成員的 ID 上,便于集中運維管理。
4. 定期對系統軟件掃描
Linux 系統往往有云廠商推送安全補丁和風險提示,但是安裝到服務器上的軟件,例如 JDK、nodejs,需要自己檢查安全問題。因此需要在系統中安裝并定期運行 CVEs 檢查并及時更新。有一款 cvechecker 可以幫助運維人員,編寫一個腳本定期運行 cvechecker 檢查系統中已知的軟件是否存在 CVEs 漏洞,并提醒開發者及時更新。
寫在后面
剛開始工作時候,喜歡動態的、靈活的編程語言,討厭的死板的、套路化的編程語言,然而需要很長一段時間,才能意識到 “約束是程序員的朋友”。對一些安全知識了解的來源大多來自修復 SonarQube 的經歷,使用 findbugs 也讓我對 Java 基礎認識的更加深刻。
類似的,在使用一些框架、平臺的時候往往存在大量的限制,有時候開發者難以意識到 “限制” 正是框架、平臺的作者 “保護” 應用開發者的一種方式。有一些開發者以 Hack 框架、平臺為樂,但是這樣會帶來潛在的隱患,在用戶量上來之后負面效應表現的尤為明顯。
項目的規范化對于 Tech Lead來說可以減少程序的運行事故和 codereview 時間,對于團隊來說也許可以少加班吧。
【本文是51CTO專欄作者“ThoughtWorks”的原創稿件,微信公眾號:思特沃克,轉載請聯系原作者】