終端程式碼重複率檢測實踐

美團點評點餐發表於2017-06-28

背景

當一個專案在不斷開發迭代、功能累加的過程中,重複程式碼的出現幾乎是不可避免的。其出現的原因不外乎以下兩點:

  • 複製貼上:這可能是造成程式碼重複的最大因素,其原因也有很多種,可能是跨專案的程式碼拷貝,可能是類似功能的程式碼拷貝。無論如何,這都違背了集中管理的原則,給後續的程式碼維護增加了比較大的負擔。
  • 對專案不瞭解:新人加入、業務輪轉等原因會讓開發人員面對一個全新的專案。這種陌生的情況下,許多開發人員就會產生重複造輪子的問題,只顧著自己開發的部分,完全沒考慮到專案原先的程式碼狀況。

所以對專案定期進行程式碼率檢測是一個很有意義的事,可以幫助開發人員發現冗餘程式碼,進行程式碼抽象和重構。

本文將介紹程式碼重複率檢測的基本概念以及前端、客戶端專案中程式碼重複率檢測的實踐過程。

基本概念

在《Software Clone Detection and Refactoring》一文中,對重複程式碼的型別進行了定義:

  • 完全一致的程式碼或者只修改了空格和評論
  • 結構上和句法上一致的程式碼,例如只是修改了變數名
  • 插入和刪除了部分程式碼
  • 功能和邏輯上一致的程式碼,語義上的拷貝

很明顯越往後,重複程式碼檢測難度越大。在實際情況中,我們的檢測要求只要大致能滿足前兩者就已經足夠了。

在技術上,重複程式碼檢測主要有以下分類:

  • 基於程式碼行的
  • 基於識別符號(token)的
  • 基於度量(metrics)的
  • 基於抽象語法樹(Abstract Syntax Tree)的
  • 基於程式依賴圖(Program Dependence Graph)的

這些技術細節不是本文關注的重點,有興趣的讀者可以查閱相關論文進行詳細瞭解。

前端程式碼重複率檢測

工具

由於前端原始碼檔案格式多樣,重複率檢測除了原始碼檢測以外,還可以從檢測打包檔案和檔案退化角度考慮。

檢測前端程式碼重複率的工具有jsinspectjscpd,PMD-CPD(PMD's Copy/Paste Detector)中也支援js檔案的重複率檢測。

  • jsinspect工具支援js和jsx格式的檔案,基於抽象語法樹,可以檢測出結構類似的程式碼塊
  • jscpd工具支援檔案格式廣泛,如java、oc、js、jsx、vue、ts、less等。其重複率判定依據為一定長度識別符號的MD5值是否相同
  • PMD-CPD工具支援js檔案檢測,也可以自己開發擴充套件包來解析指定的語言:官方介紹

每個工具各有其優缺點,若只需要檢測js或jsx檔案,且對檢測結果要求較高,可以選擇jsinspect或者PMD-CPD工具,若考慮檢測工具的通用性,可以選擇jscpc工具。

經過分析

  • 檢測打包檔案方案,若有多個打包檔案,無法區分跨檔案的重複程式碼是原始碼重複還是由於打包生成的,因此不太適合。
  • 檔案退化方案,jsx檔案轉換成js檔案可以進行檢測,但vue或less等包含css的檔案格式無法檢測。退化成純文字的檢測工具有商業收費的simian。

為了適應多種前端程式碼檔案,本團隊目前選擇jscpd作為前端程式碼重複率檢測工具。對於重複率要求較嚴格的專案,可以使用jsinspect針對js(x)檔案進行進一步檢測。

使用方法

jscpd工具可以在本地使用,也可以整合在gulp中。

本地檢測

  1. npm安裝
npm install jscpd -g
複製程式碼
  1. 在專案目錄配置.cpd.yaml檔案,配置參考
#.cpd.yaml
languages:
  - javascript
  - typescript
  - jsx
  - vue
  - css
files:
  - 'src/**'
  - 'less/**'
exclude:
  - 'dist/**'
  - 'dest/**'
  - 'neurons/**'
  - 'node_modules/**'
  - 'test/**'
  - 'data/**'
  - 'css/**'
  - 'entries/**'
reporter: xml
xsl-href: '/Users/dianping/dp/f2e-cpd/reporters-xslt/simple.xsl'
limit: 100
min-tokens: 70
min-lines: 5
output: '/Users/dianping/dp/f2e-cpd/report-ecom-70.xml'
複製程式碼

其中languages值對應的檔案字尾如下:

TokenizerFactory.prototype.LANGUAGES = {
  javascript: ['js', 'es', 'es6'],
  typescript: ['ts', 'tsx'],
  jsx: ['jsx'],
  haxe: ['hx', 'hxml'],
  coffeescript: ['coffee'],
  ruby: ['rb'],
  php: ['php', 'phtml'],
  python: ['py'],
  css: ['less', 'css'],
  sass: ['scss'],
  java: ['java'],
  csharp: ['cs'],
  go: ['go'],
  clike: ['cpp', 'c', 'm', 'h'],
  htmlmixed: ['html', 'htm'],
  yaml: ['yaml', 'yml'],
  erlang: ['erl', 'erlang'],
  swift: ['swift'],
  xml: ['xml', 'xsl', 'xslt'],
  puppet: ['pp', 'puppet'],
  twig: ['twig'],
  vue: ['vue']
};
複製程式碼
  1. 命令列工具 所有配置引數也可以直接在終端命令列中以引數形式附加。

  2. 檢視結果 執行jscpd命令列,在終端可以看到簡要的重複程式碼位置,以及總的重複率計算結果。指定verbose引數,可以看到重複程式碼塊。

jscpd支援輸出xml和json兩種格式的報告檔案,為了便於檢視重複程式碼塊,建議輸出xml格式檔案,配置xsl模板後在瀏覽器中具有較高的可讀性。

  • xml格式 執行jscpd命令後,若有配置 output 引數,將會在指定地址生成檔案。可用瀏覽器檢視。 為了便於閱讀,可以配置 xsl-href 引數,指定xsl模板,應用對應模板的xml檔案在瀏覽器中具有較高的可讀性,效果如下:
    終端程式碼重複率檢測實踐
    xsl模板可以自己編寫,官方github也提供了一份簡單的xsl檔案。

gulp整合

  1. 安裝
npm install gulp-jscpd
複製程式碼
  1. 使用 在gulp.js中新增以下任務,配置參考
var jscpd = require('gulp-jscpd');
gulp.task('jscpd', function() {
    return gulp.src([path.join(__dirname, 'src/**'), path.join(__dirname, 'less/**')])
        .pipe(jscpd({
            'min-lines' : 5,
            'min-tokens': 70,
            reporter    : 'xml',
            languages   : ['javascript', 'jsx', 'css'],
            output      : '/Users/dianping/dp/f2e-cpd/report-ecom-70.xml',
            verbose     : false,
            debug       : false,
            silent      : false,
            failOnError : false,
            'xsl-href'  : '/Users/dianping/dp/f2e-cpd/reporters-xslt/simple.xsl'
        }));
});
複製程式碼
  1. 值得注意的是,failOnError配置項指定檢查完畢後是否丟擲錯誤,預設true,會終止打包流程。在CI中,若不希望重複率檢查停止正常打包,應指定為false。

客戶端程式碼重複率檢測

工具

對於客戶端程式碼而言,由於有iOS和Android兩個平臺,所以需要考慮工具的通用性,必須支援objective-C和java兩種語言。

基於以上原因,最後選擇的工具是PMD-CPD(PMD's Copy/Paste Detector)。此工具使用的是Karp-Rabin字串匹配演算法,支援gui,支援命令列,輸出格式支援text、xml、csv等,可以很好的配合指令碼語言進行二次開發,對重複率資料進行統計。

使用方法

  1. 從官網下載PMD工具,其中已經包含CPD,下載地址:官網地址
  2. 解壓後可以在bin資料夾下看到對應的工具
  3. 以linux為例,使用以下命令就可以開啟CPD工具的gui。
./run.sh cpdgui
複製程式碼
  1. 如果需要配合自定義指令碼使用,可以選擇命令列工具,可以指定相應的引數,例如tokens、目錄、語言、輸出格式等等。詳細的引數列表請參考官方文件
./run.sh cpd --minimum-tokens 100 --files /usr/local/java/src/java --language java --format xml
複製程式碼

思考

工具的使用本身是比較簡單的,更多的是針對不同專案進行相應的定製。可能需要考量的點有如下幾個:

  • tokens和有效率的平衡: tokens是工具的一個引數,可以理解為對重複程式碼長度定義的標準。所以當tokens越小,檢查到的重複程式碼數量更多、覆蓋面越大,但相應的有效率就會降低,產生較多的誤報情況。反之則有效率較高,但覆蓋率就會相應降低,會有所遺漏。對tokens的選擇依賴於專案的語言、框架等因素,在工具使用初期可以進行多次測試比較確定合適的tokens值。

  • 指令碼定製:使用其他指令碼語言例如python、ruby等進行相應的定製,把程式碼重複率檢查包裝成為一個自動化工具。重複率檢查工具例如PMD-CPD本身也不具備統計功能,所以指令碼還可以幫助把最後的檢查結果進行量化。

  • 重複率標準:對於比較獨立、規模不大的專案,剛開始檢查時,5%可能是一個比較合適的值。當然重複率標準的制定需要參考的因素有很多,例如tokens、專案、架構、時間等等。

參考文獻

Fontana F A, Zanoni M, Ranchetti A, et al. Software Clone Detection and Refactoring[J]. Isrn Software Engineering, 2013, 2013(2013).

相關文章