本週一篇指摘 Vue 抄襲 Angular 的文章一石激起千層浪。為此,筆者作為中立吃瓜的 React 使用者,分析了 13 個主流前端框架版本上萬個變數的命名風格,應用自然語言處理中的文字相似度演算法進行了分析,以對這一論點的有效性做出客觀的評價。
思路
在分析書籍抄襲、論文查重等場景下,使用演算法比較文字相似度的方法是一種有效的技術手段。那麼,我們如何通過這一手段,來分析原始碼層面的抄襲呢?
在對比形如 我喜歡寫程式碼,不喜歡撕逼
和 我不喜歡撕逼,喜歡寫程式碼
的兩個句子相似度時,大致的思路是首先分詞,而後計算出詞頻,再將詞頻向量化,最後比較兩個高維向量的夾角,夾角越小則越相似。
在【Vue 是否抄襲了 Angular】這一場景下,我們分析的物件從句子變成了程式碼。這時主要的區別是這兩點:
- 程式碼是高度結構化的文字,分詞已經通過詞法分析器完成了。
- 某種程式語言的程式碼中,充斥著大量該語言的關鍵字。如
var
和function
的關鍵字,這些關鍵字的重複與相似度無關。
而對於是否抄襲與相似度的關係,我們給出這幾個假設:
- 解決同樣的開放性問題時,獨立編寫、不存在抄襲的程式碼,以變數名為代表的編碼風格通常有巨大的差別,此時相似度是低的。
- 存在較大規模抄襲的程式碼,類似於同一個框架,未經大規模重構的不同版本程式碼。此時編碼風格是類似的,此時相似度是高的。
- 前後調換模組宣告順序,不影響相似度。
給定這幾個前提後,我們可以確定出這樣的分析策略:
- 輸入各大框架未經壓縮的原始碼,解析出其語法樹。
- 捨棄語法樹中無關部分,提取出其變數宣告以代表其編碼風格。
- 使用文字相似度演算法計算變數名間的相似度,分析結論。
變數名提取
在通過 Webpack 引用框架依賴時,通常匯入的都是打包成單一檔案且未經混淆的框架原始碼。這是一個非常好的特性。筆者編寫了一個簡單的 Webpack Loader 以在這個過程中實現變數名的提取:
// loader/index.js
// 為 loader 傳入的 content 即為 JS 原始碼
module.exports = function (content) {
return demo(content)
}複製程式碼
在 demo
函式中獲得框架原始碼後,解析語法樹也不是一個困難的問題了。通過 acorn 這一 Parser 我們就能做到:
function demo (content) {
const ast = acorn.parse(content, { sourceType: 'module' })
walk.simple(ast, {
// 在 walk 遍歷時,抽取全部變數宣告語句中的變數名
VariableDeclaration (node) {
const name = node.declarations[0].id.name + '\n'
fs.appendFileSync(resolve('./result.txt'), name)
}
})
return content
}複製程式碼
這時候我們就能在 result.txt
內獲得一個前端框架中的全部變數名了,形如:
p
i
resolved
c
segs
i
...複製程式碼
這都是什麼亂七八糟的…這時候我們獲得的文字並沒有經過初步的處理,我們真正感興趣的是各個框架變數名的詞頻。詞頻的計算是一道不錯的面試題,不過在這裡我們直接通過 Wordclouds 的服務來實現這一步。這一步中還包括基本的清洗,以去除 i
/ a
/ b
這些無意義的變數名。我們的結果是形如這樣的格式:
29 value
19 arg
18 result
16 key
14 index
...複製程式碼
以上就是 React / Vue / Angular 三大框架中某一個的 Top 5 變數名,猜猜是哪一個?好吧這幾個變數名都十分爛大街…暫時看不出什麼端倪。讓我們繼續做相似度比較吧,答案在後面揭曉。
相似度演算法
我們在上文中,實際上已經獲得了這樣的物件:
const a = {
'foo': 5,
'bar': 4,
'baz': 3
}
const b = {
'foo': 4,
'bar': 6,
'baz': 0
}複製程式碼
我們可以認為,每個變數名是一個獨立的維度,每個框架中存在的所有型別變數名組成一個高維空間的向量。從而,我們的問題就簡化為了如何比較 a 與 b 這兩個向量的相似程度。在此引用阮一峰老師的介紹:
我們可以把它們想象成空間中的兩條線段,都是從原點([0, 0, ...])出發,指向不同的方向。兩條線段之間形成一個夾角,如果夾角為0度,意味著方向相同、線段重合;如果夾角為90度,意味著形成直角,方向完全不相似;如果夾角為180度,意味著方向正好相反。因此,我們可以通過夾角的大小,來判斷向量的相似程度。夾角越小,就代表越相似。
假定 a 向量是 [x1, y1]
,b向量是 [x2, y2]
,那麼可以將餘弦定理改寫成下面的形式,計算出的 cosθ
代表相似度:
推廣到高維向量的一般情形:
根據演算法編寫出簡化的示例程式碼:
function getTheta () {
let x = 0
Object.keys(dictAll).forEach(key => {
if (dictA[key] && dictB[key]) x += dictA[key] * dictB[key]
})
let yA = getY(dictA)
let yB = getY(dictB)
const result = x / (yA * yB)
console.log(result)
}複製程式碼
最後執行我們的分析演算法處理上一步的變數名即可:
➜ node analyse vue@2.4.1 vue@2.4.2
0.9436438155995188複製程式碼
實驗結果與總結
一系列鋪墊以後,終於到了檢驗真理的時候了。我們首先基於【相似版本相似度高】的假設,驗證 Vue 是否符合這一假設:
➜ node analyse vue@2.4.1 vue@2.4.2
0.9436438155995188複製程式碼
可以看到,目前最新的 vue 2.4.2 與 2.4.1 之間,確實存在著很高的相似度。接下來比較 vue 最新版與 2.0.0 同一個 Major 版本之間的相似度:
➜ node analyse vue@2.0.0 vue@2.4.2
0.8838059164881868複製程式碼
相似度有所降低,說明最新版比起去年的 V2,已經有了不小的改動了。再來比較 V2 與 V1 系列的相似度:
➜ node analyse vue@2.0.0 vue@1.0.28
0.5883193867742227複製程式碼
相似度明顯降低,顯然重構之言非虛。最後比較 Vue 的最新版與第一個版本:
➜ node analyse vue@2.4.2 vue@0.6.0
0.4590386014371645複製程式碼
這是 Vue 家族中最低的相似度,也達到了 0.45 的水平。接下來是正戲,比較 Vue 和 Angular 的最新版:
➜ node analyse vue@2.4.2 angular@4.3.3
0.19322280449484375複製程式碼
區區 0.19 的相似度!好吧,Angular 最新版也是重構過的,我們不妨直接比較最早【照著 Angular 抄的】的 Vue 和 Angular 1.x 系列:
➜ node analyse vue@0.6.0 angular@1.2.32
0.294527560626686複製程式碼
這個相似度也大幅低於 Vue 全系列縱向對比的相似度!為了更有效地對比,我們讓隔壁 React 躺個槍(未加版本號代表最新版):
➜ node analyse vue@2.4.2 react
0.27592736925848194複製程式碼
0.27 與 0.29 的對比,說明即便是最早階段(與 Angular 相似度最高)的 Vue,相似度也僅僅相當於現在的 Vue 和 React 而已!為了保證公平,我們讓 jQuery 也來湊個熱鬧:
➜ node analyse jquery angular@1.2.32
0.2508302720623658複製程式碼
這也是不到 0.3 的相似度,據此我們甚至可以得出一個大膽的結論:Vue 和 Angular 的相似度,和 Angular 與 jQuery 之間的相似度接近!沒有人會認為 jQuery 與 Angular 之間存在抄襲吧?
當然,Vue 和 Angular 的相似度是客觀存在的。我們在前端領域,可以找到另一對這樣的例子:jQuery VS Zepto,它們之間的相似度如何呢?
➜ node analyse jquery zepto
0.25994377334635854複製程式碼
這個相似度和 Angular VS jQuery 幾乎相同,這說明即便設計理念相近,具體實現不同的原創框架之間,相似度也是很低的。Vue VS Angular 也完全符合這一結論。
hmmm 目前我們的論據已經比較充分了。最後,我們比較一種情形:設計理念完全不同的原創框架之間,相似度如何?我們拉出 jQuery 和 React:
➜ node analyse jquery react
0.1007248324385447複製程式碼
全場最低相似度…所以我們可以理解 jQuery 時代的前端轉向 React 時有多麼不習慣了吧?
到此為止,我們的結論有:
- Vue 系列迭代間相似度較高。
- 即便是最早的 Vue,與經典 Angular 的相似度也很低。
- 最新 Vue 與最新 Angular 之間,相似度更低,說明二者的發展道路早已更加獨立。
- 即便設計理念相近,具體實現不同的原創框架之間,相似度也很低。
- React 與 jQuery 的相似度特別特別低(離題了)。
據此,筆者有理由認為【Vue 抄襲了 Angular】的論點是站不住腳的。
本文的實驗資料託管在 Github 上,歡迎感興趣的同學驗證並改進這些結論。最後,框架畢竟只是工具,相互撕逼併不利於社群的發展。引用我司 Boss 的觀點:【一流的人做事,二流的人去評論,三流的人去評論別人的評論】,希望大家能把口水戰的時間放在更務實的事情上,推動技術水平、社群氛圍和平均工資的上升……