如何快速識(shí)別項(xiàng)目水平?
軟件開(kāi)發(fā)是一個(gè)非常有意思的復(fù)制 + 粘貼活動(dòng)。開(kāi)發(fā)業(yè)務(wù)代碼的時(shí)候,大部分人都不會(huì)不加思索地添加代碼。畢竟,聰明的產(chǎn)品經(jīng)理/項(xiàng)目經(jīng)理們,天才式地想出了用代碼行數(shù)的方式來(lái)計(jì)算 KPI,又或者是通過(guò)提交次數(shù)來(lái)進(jìn)行考核 —— 雖然小步提交是個(gè)好東西,但是吧,大部分人不經(jīng)過(guò)練習(xí)還是掌握不會(huì)的。
最近,我還我的朋友們說(shuō)到,她們公司的打算強(qiáng)制一天只能提交一次代碼。這絕對(duì)是代碼行數(shù)計(jì)算 KPI 之后的,又一個(gè)偉大地創(chuàng)舉式的地發(fā)明。如果我有直接頒發(fā)諾貝爾獎(jiǎng)的權(quán)力,我一定給送給他一獎(jiǎng)杯。
好了,回到正題。
自上而下的代碼分析
最近,剛好因?yàn)轫?xiàng)目的關(guān)系,需要分析某一系統(tǒng)的代碼行數(shù)。通過(guò)一系列的復(fù)制 + 粘貼和 Excel 操作,我大致有了一套 DIY 的自動(dòng)化分析方案:自上而下的代碼分析。當(dāng)然了,這肯定不是我先發(fā)明的,在某處一定有論文和代碼、工具。只是我依據(jù)自己的想法和需求,完善了一下現(xiàn)有的方案。要知道,已經(jīng)有大量地代碼分析工具了。
其實(shí)總體的思路非常簡(jiǎn)單:項(xiàng)目行數(shù) -> 包行數(shù) -> 修改歷史 -> 引用分析。
具體來(lái)說(shuō),就是:
- 通過(guò)代碼行數(shù)(LOC)統(tǒng)計(jì)工具,統(tǒng)計(jì)總體的代碼情況。
- 結(jié)合代碼行數(shù)(LOC)統(tǒng)計(jì)工具,統(tǒng)計(jì)各個(gè)包的代碼情況
- 獲取 Git 提交歷史,統(tǒng)計(jì)出經(jīng)常修改的包或者是類(lèi)。
- 構(gòu)建語(yǔ)法樹(shù)、制品(如 jar)分析,統(tǒng)計(jì)出引用次數(shù)最多的包。
唯一麻煩的地方就是做一些自動(dòng)化。所以,這些功能就被我完善到 Coca 里了,笑~。
好了,讓我們來(lái)看個(gè)示例。這里以開(kāi)源項(xiàng)目 intelli-community (即 IDEA 的社區(qū)版)為例。
項(xiàng)目級(jí)代碼行數(shù)
市面上已經(jīng)有大量的行數(shù)統(tǒng)計(jì)工具,大家可以自行尋找。這里我用的是 Coca (GitHub:https://github.com/phodal/coca ),集成了三方用 Go 實(shí)現(xiàn)的 CLOC 統(tǒng)計(jì)功能。
首先呢,我們要實(shí)現(xiàn)的是分析整個(gè)項(xiàng)目的行數(shù)情況 coca cloc . :
- Language Files Lines Blanks Comments Code Complexity
- ───────────────────────────────────────────────────────────────────────────────
- Java 66554 5172301 688054 512630 3971617 603221
- Python 10017 424614 31629 34876 358109 22329
- Kotlin 6383 602814 89130 35660 478024 51292
- Plain Text 4105 635689 5799 0 629890 0
- Groovy 3397 154817 23296 12364 119157 4683
- XML 2549 494074 10056 3008 481010 0
- HTML 2329 63331 2988 3623 56720 0
- SVG 2124 21078 23 87 20968 0
- JSON 1155 346795 352 0 346443 0
- Shell 535 8295 1138 734 6423 811
- Markdown 425 9660 1434 0 8226 0
- Properties File 384 42069 2545 1348 38176 0
- YAML 384 3264 202 55 3007 0
- XML Schema 345 196649 17963 0 178686 0
- JavaScript 169 30569 1562 5151 23856 3895
- ...
- ───────────────────────────────────────────────────────────────────────────────
- Total 101908 8389984 898893 629497 6861594 703260
- ───────────────────────────────────────────────────────────────────────────────
- Estimated Cost to Develop $288,297,976
- Estimated Schedule Effort 132.017220 months
- Estimated People Required 258.681675
嗯,從規(guī)模上來(lái)看,這真的是一個(gè)超級(jí)大的項(xiàng)目,接近 700 萬(wàn)行的規(guī)模。所以,我第一次看到的時(shí)候,也不知道從哪里下手,于是我便想著是不是從包(目錄)結(jié)構(gòu)能解決這個(gè)問(wèn)題。
PS:Coca 當(dāng)前只支持單體分析,考慮有多模塊和微服務(wù)系統(tǒng)的存在,我會(huì)在未來(lái)必要的時(shí)候,添加對(duì)應(yīng)的實(shí)現(xiàn)。
按目錄分析
簡(jiǎn)單來(lái)說(shuō)就是,我們可以按目錄執(zhí)行 cloc,然后匯總結(jié)構(gòu)即可。
所以,進(jìn)一步地我們就可以執(zhí)行 coca cloc . --by-directory,得到一個(gè) CSV 數(shù)據(jù),根據(jù)自己的需要進(jìn)行編輯:
package | summary | Java | Python | Kotlin | Plain Text |
---|---|---|---|---|---|
platform | 1800542 | 1460686 | 106 | 244586 | 4669 |
java | 1479891 | 1059828 | 0 | 35224 | 267792 |
plugins | 1765695 | 983860 | 70301 | 151816 | 150158 |
android | 1865010 | 769437 | 52 | 325659 | 101848 |
python | 664760 | 240080 | 287641 | 24626 | 17855 |
xml | 866926 | 108794 | 0 | 207 | 174471 |
jps | 66671 | 63437 | 0 | 1498 | 729 |
還可以繪制成圖表。
除此,我還提供了一個(gè) --top-file --top-size 10 的參數(shù),以了解行數(shù)最多的幾個(gè)文件。
- | LENGTH | COMPLEXITY | LOCATION |
- |--------|------------|-----------------------------------|
- | 1642 | 236 | ConstraintLayoutHandler.java |
- | 1492 | 375 | ConstraintComponentUtilities.java |
- | 1189 | 166 | CommonActions.java |
- | 1184 | 325 | ConstraintWidget.java |
- | 1169 | 129 | SingleWidgetView.java |
- | 1115 | 213 | ScoutArrange.java |
- | 1097 | 281 | ScoutWidget.java |
- | 1081 | 224 | 3d/Rasterize.java |
- | 1016 | 159 | LayoutlibSceneManager.java |
- | 1014 | 220 | TimeLinePanel.java |
接著,只需要層層下推,我們就可以分析出哪個(gè)是系統(tǒng)最復(fù)雜的一部分。如下圖中的復(fù)雜點(diǎn),依次是:platforms、java、plugins、android。
變更頻次
緊接著,我們就可以通過(guò)獲取 Git 提交歷史來(lái)知道,對(duì)應(yīng)文件的修改變化。這里,我依舊使用的是 coca git -t。它源自于對(duì)于 git log --all --date=short --pretty="format:[%h] %aN %ad %s" --numstat --reverse --summary 命令的分析結(jié)果,有興趣的讀者可以參考 Coca 的源碼,自行編寫(xiě)不同版本地對(duì)應(yīng)實(shí)現(xiàn)。
可怕的是,我在 intellij-community 執(zhí)行了 coca git -t 之后,生成了一個(gè) 241M 的文件,回去 GitHub 看了一眼:累計(jì) 290,459 次提交。
在我第一次沒(méi)意識(shí)到應(yīng)該記錄下 log 之后,我又重新執(zhí)行了一遍。最終,拿到了結(jié)果:
- | ENTITYNAME | REVSCOUNT | AUTHORCOUNT |
- |-------------------------------------------------------------------------------------------------------------|-----------|-------------|
- | platform/util/resources/misc/registry.properties | 2473 | 224 |
- | platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java | 1211 | 149 |
- | platform/platform-api/resources/messages/IdeBundle.properties | 1209 | 181 |
- | platform/platform-resources/src/META-INF/LangExtensions.xml | 1206 | 192 |
- | plugins/InspectionGadgets/InspectionGadgetsAnalysis/resources/messages/InspectionGadgetsBundle.properties | 1113 | 159 |
- | platform/platform-resources-en/src/messages/ActionsBundle.properties | 1004 | 161 |
- | platform/platform-resources/src/META-INF/PlatformExtensions.xml | 937 | 162 |
- | platform/util//src/com/intellij/util/ui/UIUtil.java | 779 | 120 |
- | platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java | 763 | 133 |
- | platform/platform-resources/src/META-INF/LangExtensionPoints.xml | 762 | 150 |
- | platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexImpl.java | 684 | 126 |
- | java/java-analysis-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java | 675 | 117 |
- | platform/platform-resources/src/idea/PlatformActions.xml | 671 | 139 |
然后,看一眼 registry.properties 是一個(gè)有 1800+ 行的配置文件,EditorImpl.java 是一個(gè)有 5000+ 行的 Java 代碼,UIUtil.java 也有 3600+ 行……。嗯,效果是不是也相當(dāng)理想,再看看 UIUtil.java 這一個(gè)名字,一看就非常適合重構(gòu)。
高引用
最后,可能會(huì)進(jìn)入慢的一步,分析代碼,生成 AST??紤]到 IDEA Community 的這個(gè)代碼量。我就不重復(fù)演示了,以 GitHub 的示例為例 coca count:
- +------------+--------------------------------------------------------------------------+
- | REFS COUNT | METHOD |
- +------------+--------------------------------------------------------------------------+
- | 2 | com.phodal.pholedge.book.BookRepository.byId |
- | 2 | com.phodal.pholedge.book.model.Book.toRepresentation |
- | 2 | com.phodal.pholedge.book.BookRepository.save |
- | 2 | com.phodal.coca.analysis.JavaCallApp.parse |
- | 2 | com.phodal.pholedge.book.BookRepository.save |
- | 2 | com.phodal.coca.analysis.JavaCallApp.parse |
- | 1 | com.phodal.pholedge.book.model.Book.save |
最后,我們又回到了這個(gè)模型上。
高引用與高修改
考慮到 AST 的慢的程度,我已經(jīng)有一個(gè)更好的實(shí)現(xiàn)方式。
結(jié)論
分析代碼是一件很有意思的事情。一番操作下來(lái),能學(xué)習(xí)到非常有意思的東西。
本文轉(zhuǎn)載自微信公眾號(hào)「phodal」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系phodal公眾號(hào)。