前端 100 萬行代碼是怎樣的體驗?

Alibaba F2E 2021-10-14 01:46:20
前端



近年來,阿裏數據中臺產品發展迅速。核心產品之 Quick BI 連續 2 年成為國內唯一入選 Gartner 魔力象限的國產 BI。Quick BI 單一代碼倉庫源碼突破了 100萬行。整個開發過程涉及到的人員和模塊都很多,因為下面講的一些原則,產品能一直保持在快速的開發狀態。


先分享幾個關鍵數據:

  • 代碼:TypeScript 82萬行,樣式 Sass+Less+CSS 18萬行。(cloc 統計,去除自動生成代碼)

  • 協同:Code Review 12,111 次,Commit 53,026 次。



很多人會問,這麼多代碼,為什麼不切分代碼庫?還不趕快引入微前端、Serverless 框架?你們就不擔心無法維護,啟動龜速嗎?


實際情况是,從第一天開始,就預估到會有這麼大的代碼量。啟動時間也從最初的幾秒鐘到後面越來越慢5~10分鐘,再優化到近期的5秒鐘。整個過程下來,團隊更感受到 Monorepo(單一代碼倉庫)的優勢。


這個實踐想說明:

  • 大的 Codebase 可能是好事情,大道至簡。用極其“簡單”的架構更容易支持複雜靈活的業務

  • 要做到簡單的架構,內部需要更明確的規範,更密切的協同,更高效的執行

  • 能通過工程化解决的問題,就不要通過開發規範,能通過規範來解决的不要靠自由發揮



開工



2019年4月30號,晴朗的下午,剛好是喜迎五一的前一天,發揮集體智慧,投票選出滿意的倉庫名。同時借 Quick BI 和 FBI 底座融合的契機,項目開啟。後來底座代碼轉正,把上層業務代碼也吸納進來。


commit 769bf68c1740631b39dca6931a19a5e1692be48dDate: Tue Apr 30 17:48:52 2019 +0800
A New Era of BI Begins



Why Monorepo?




在開工之前,對單一倉庫(Monorepo)和多倉庫(Polyrepo)團隊內做了很多的討論。


曾經我也很喜歡 Polyrepo,為每個組件建立獨立 repo 獨立 npm,比如2019年前,單是錶單類的編輯器組件就有 43 個:



本以為這樣可以做到 完美的解耦、極致的複用??


但實際上:

  1. 每次 Babel、React 等依賴整體昇級能讓人脫層皮,所以自研了脚手架。造輪子都是被逼出來的,事情做了一點點,但寫脚本能力直線上昇

  2. 每次 調試組件,npm link 一下。後來組件跨級,可以做 3 層 npm link,使用過的都知道這是多麼糟糕的體驗

  1. 版本難對齊,每次主倉庫發布前,組件間版本對齊更是考驗眼力,稍有不慎觸發線上故障

  2. 方便別人複用的優勢呢?最終支持自己業務都捉襟見肘,哪還敢讓別人複用


最終我們把所有這些組件都合並到一個倉庫,其實像 Google/Facebook/Microsoft 這些公司內部都很推崇 Monorepo。


但我們不是原教旨主義的 Monorepo,沒必要把不相關的產品代碼硬放到一起。在實線團隊內部,單個產品可以使用 Monorepo,會極大降低協同成本。但開始的時候,團隊內還是有很多疑問。



關於 Monorepo 的幾個核心疑問?


1. 單一倉庫,體積會很大吧?


100 萬行代碼的體積有多大?


先來猜一下:1GB?10GB?還是更多?


首先,按照公式計算一下:

代碼的體積 = 源碼的體積 + .git 的體積 + 資源文件(音視頻、圖片、其他文件)


  1. 我們一起來算一下源碼的體積:

一般建議每行小於 120 字符,我們取每行 100 個字符來算,100 萬行就是:


100 * 1000,000 = 100,000,000 B轉換之後就是 100 MB!


那我們的倉庫實際多大呢?

只有 85 MB!也就是平均每行 85 個字符。


  1. 再來算一下 .git的體積:

.git裏記錄了所有代碼的提交曆史、branch 和 tag 信息。會很大體積吧?

實際上 Git 底層做了很多的優化:1. 所有 branch 和 tag 都是引用;2. 對變更是增量存儲;3. 變更對象存儲的時候會使用 zlib 壓縮。(對於重複出現的樣板代碼只會存儲一次,對於規範化的代碼壓縮比例極高)。


按照我們的經驗,.git記錄 10,000 次 commit 提交只需要額外的 1~3 個代碼體積即可。


  1. 資源文件大小

Git 做了很多針對源碼的優化,但視頻和音頻這類資源文件除外。我們最近使用 BFG 把另一個產品的倉庫從 22GB 優化到 200MB,降低 99%!而且優化後代碼的提交曆史和分支都得到了保留(因為 BFG 會編輯 Git 提交記錄,部分 commit id 會變化)。


以前 22 GB 是因為倉庫裏存放視頻、發布的 build 文件和 sourcemap 文件,這些都不應該放到源碼倉庫。


小結一下,百萬行代碼體積一般在 200MB ~ 400MB 之間。那來估算下 1000 萬行代碼占用體積是多少?


乘以十也就是 2GB ~ 4GB 之間。這對比 node_modules隨隨便便幾個 G 來說,並不算什麼,很容易管理。補充個案例,Linux 內核有 2800 萬行,使用 Monorepo,數千人協同。據說當時 Linus 就是為了管理 Linux 的源碼而開發出 Git。

2. 啟動很慢吧?5分鐘還是10分鐘?


聽到有些團隊講,代碼十幾萬行,啟動 10+分鐘,典型的“巨石”項目,已經很難維護了。趕緊拆包、或者改微前端。可能團隊才 3 個人卻拆了 5 個項目,協同起來非常麻煩。


我們做法有3個:

  1. 按照頁面來拆分多 Entry,每次只需啟動一個 Entry

  2. 梳理子包間的依賴關系,追求極致的 Lazy loading,Tree-Shaking

  1. Webpack 切換到 Vite


尤其是 Webpack 切換到 Vite 以後,最終項目冷啟動時間由 2-5分鐘 優化到 5秒 內。熱編譯時間由原來 5秒 優化到 1秒 內,Apple M1 電腦基本都是 500ms 以內。


3. 代碼複用怎麼辦?Monorepo 複用的時候是否要引入全部?


傳統的軟件工程思想追求 DRY,但並不是越 DRY 越好。


每寫一行代碼,都產生了相應代價:維護的成本。為了减少代碼,我們有了可複用的模塊。但是代碼複用有一個問題:當你以後想要修改的時候它就會成為一個障礙。


對於像 Quick BI 這樣長期迭代的產品,絕大部分需求都是對原有功能的擴展,所以寫出易維護的代碼最重要。因此,團隊不鼓勵使用 magic 的特技寫法;不單純追求代碼複用率,而是追求更易於修改;鼓勵在未來模塊下線的時候易於删除的編碼方式。


對於確實存在複用的場景,我們做了拆包。Monorepo 內部我們拆了多個 package(後面有截圖),比如其他產品需要 BI 搭建,可以複用 @alife/bi-designer,並借助於 Tree-Shaking 做到依賴引入的最小化。



目前的開發體驗



  1. 冷啟動 5秒,熱編譯 1秒內。以前是 5~10分鐘。

  2. 改一行代碼能解决的問題,真正改一行且發布一次。而不是改 10+ 個項目,按依賴發布 N 次。

  1. 新人 10分鐘 搭建好環境,上手開發

    1. 相比於以前每個組件一個 Repo,包賦權都要搞很久

  1. 避免了版本不對齊的問題

    1. 對於 2C 產品,不需要多版本多主幹分支,但多個 npm 依賴對齊版本也不容易

    2. 對於 2B 產品,由於多環境、多版本,會更加複雜,複雜度極高。Monorepo 通過分支來統一內部依賴的版本

  1. 工程化昇級只需要一次。目前是基於 Lerna 開發的 Pri Monorepo 方案。


這樣的體驗要保持並不容易,開發中還有很多問題要解决。



真正需要解决的問題



並不是把代碼放到一起就完了,背後複雜的問題是 協同、技術方案、穩定性(如何避免一個人提交代碼導致整個產品崩潰?)

1. 包依賴管理


內部拆分多個子包,每個子包是子文件,可以單獨發布 npm,見下圖:



內部包管理的核心原則是:

  • 從左向右單向依賴,只能右邊引用左邊。避免循環依賴

  • 規範還不够,開發插件來自動檢測,如果左邊依賴右邊直接報錯


對於開源 npm 的引入,應該更慎重。大部分 npm 的維護時長不超過x年,即使像 Moment.js 這樣曾經標配的工具庫也會終止維護。可能有 20% 的 npm 是沒人維護。但未來如果你的線上用戶遇到問題,你就需要靠自己啃源碼,陷入被動。所以我們的原則是,引入開源 npm 要三人線下評審通過才行。


2. Code Review 文化


互相 Code Review 能幫助新人快速成長,同時也是打造團隊技術文化的方式。

過去幾年一直在團隊內推行 100% CR,但這還不够。機械的執行很容易把 CR 流於形式,還要分場景來做。


Monorepo 有個風險是一旦有問題就可能是整體的問題。


目前我們的 Code Review 主要分為3個場景:

  1. 線上 MR Code Review【1對1】

  2. 主題式 Code Review【3-5個人】

  1. 大版本發布前集體 Code Review【All】


12,111 次 Code Review 的經驗很多,主要是:

  1. 及時 Review,鼓勵小顆粒度的 MR,不必等整個功能開發完成

  2. 代碼是寫給人看的,鼓勵白話文一樣的代碼,而不是文言文

  1. 建立最佳實踐(目錄樹結構、命名規範、數據流規範)。開發一個功能可以有 10 種方法,但團隊需要選 1 種並推廣

  2. 不鼓勵炫技,為了未來可維護性。能用簡單技術實現,不要用“高深”冷門的技術

  1. 强調開發潔癖,追求優雅代碼的文化。(命名是否易於理解、注釋是否完整、是否有性能隱患等)


3. 工程化建設


這個過程首先要感謝淘系前端 DEF 工程化團隊的支持,在這麼多代碼的情况下,不斷挑戰極限昇級 DEF 支持我們。


除了制定文檔的規範之外,能够自動化工具檢查的規範才是好規範。


檢查器:ESLint、TS 類型校驗、Prettier

語法檢查器是推動規範落地的重要方法,ESLint 可以做增量,優化後 git commit 的 pre-hooks 依舊很快。但 TS type check 因為不支持增量就比較慢了,需要搭配 CI/CD 來使用。


Webpack vs Vite

發布使用 Webpack,開發使用 Vite。

開發環境使用 Vite 快速調試,生產環境依舊使用 Webpack 打包。

風險是開發和生產編譯產物不一致,這一塊需要上線前回歸測試避免。


4. 性能優化


對於數據產品而言,性能的挑戰除了來自於 Monorepo 後資源包的變大,還有大數據量對渲染計算帶來的挑戰。


性能優化可以分為3個環節:

  • 資源加載:精細化 Tree Shaking,難在精細。Webpack 本身的 Tree-Shaking 做的並不好,不支持 Class method 做 Tree Shaking,所以有時候需要修改代碼。Lazy Loading 模塊做到按需加載,尤其是圖錶、SQL 編輯器這類大組件。合理的接口預加載,不要讓網絡閑下來。

  • 視圖渲染:讓組件渲染次數降到最低,錶格類組件虛擬滾動優化,閑時預加載預渲染。

  • 取數請求:資源本地化緩沖方案,移動端使用 PWA 將 JS 等資源文件和數據緩存到本地。


另外還有性能檢測工具,定比特性能卡點。計劃做代碼性能門閂,代碼提交前如果發現包體積增大發出提醒。

5. 數據化驅動架構優化


身在數據中臺,我對數據的業務價值深信不疑。但對於開發本身而言,很少深度使用過數據。


所以 S1 重點探索了開發體驗的數字化。通過采集大家的開發環境和啟動耗時數據來做分析【不統計其他數據避免內卷】。發現很多有意思的事情,比如有個同學熱編譯 3~5 分鐘,他以為別人也是這樣慢,嚴重影響了開發效率,當從報錶發現數據异常後十分鐘幫他解决。


另外一個例子,為了保持線上打包產物的一致性,推動團隊做 Node.js 版本統一,以前都是靠釘,釘多少次都無法知道效果如何。有了報錶以後就一目了然。



目前整個數據化的流程跑通,初步嘗到甜頭。未來還有很多好玩的分析可以做。



更深層的經驗


效率最高的方式就是一次最好

每行代碼都會留下成本。長遠考慮,效率最高的方法就是一次做好。

蘇世民說“做大事和做小事的難度是一樣的。兩者都會消耗你的時間和精力”。既然如此,不妨把代碼一次寫好。代碼中如果遺留 “TODO” 可能就永遠 TO DO。客觀來講,一次做好比較難,首先是每個人認為的“好”標准不同,背後是個人的技術能力、體驗的追求、業務的理解。


組織文化技術 相輔相成

技術架構和組織結構有很大關系,選擇適合組織的技術架構更重要。

如果一個組織是分散的,使用 Monorepo 會有很大的協同成本。但組織如果是內聚的,Monorepo 能極大提效。

工程化和架構底座是團隊的事情,靠個人很難去推動。

短期可以靠戰役靠照搬,長期要形成文化才能持續迭代。

組織溝通成本高應該通過組織來解,通過技術來解的力量是渺小的。技術可以做的是充分發揮工具的優勢,讓變化快速發生。


簡單不先於複雜,而是在複雜之後

對於一個簡單的架構,總有人會想辦法把它做複雜。踩了坑,下决心重構,成功則回歸簡單,失敗就會被新的簡單模式顛覆。踩坑本身也是有價值的,不然新人總是按捺不住還會再踩一次。做複雜很容易,但保持簡單需要遠見和克制。沒有經曆過過程的磨練,別人的解藥對你可能是毒藥。

架構不可能一成不變的,我們的圖錶最開始直接使用 D3、ECharts 很簡單,後來定制很多逐漸複雜到難以維護,於是基於 G2 自研 bi-charts 後架構又一次變簡單,前後的開發體驗可能是差不多的,但背後的技術完全變了。



總結與展望



百萬行代碼沒什麼可怕,是一個正常的節點,仍然可以像幾萬行代碼那樣敏捷。

現在 Quick BI 已經向千萬行邁進,向世界一流 BI 的目標邁進。以上內容更多是工程化相關,把工程化做好目的是想讓開發者更專注於業務,沒講的業務挑戰其實更多,因為數據分析天生就要與海量數據打交道,性能優化有長期的實踐;洞察豐富异樣的數據,有很多可視化及複雜錶格方面的沉澱,可視化不僅是技術,也是業務本身;手機平板電視等多端展示,跨端適配的挑戰。未來還希望能够把數據分析打造成一個引擎,能够快速集成到辦公和商業流程中。


目前的開發模式並不完美,在迭代的過程中,不可避免會產生技術債,架構的優化本質就是在保持可維護性和减少技術債。最近團隊在醞釀一次 Redux-Toolkit 的引入,會對取數和數據流有大的昇級,有進展再分享。







關注「Alibaba F2E」微信公眾號把握阿裏巴巴前端新動向




版权声明
本文为[Alibaba F2E]所创,转载请带上原文链接,感谢
https://qdmana.com/2021/10/20211014014520252V.html

  1. 初学者怎么学Web前端?
  2. react
  3. PaddlePaddle:在 Serverless 架构上十几行代码实现 OCR 能力
  4. JavaScript数组 几个常用方法
  5. Angular 依赖注入 - 全面解析
  6. html_day02
  7. 那些年我们前端面试中经常被问到的题!
  8. The starting price of Ducati multistada V2 in North America is less than 100000 yuan
  9. Hls.js 使用文檔
  10. Hls.js travailler avec des documents
  11. Problèmes liés à la précision JS
  12. Copie une partie des propriétés d'un objet à un autre objet
  13. Multiplexage de modules en vuex
  14. Développement multilingue Android, questions d'entrevue pour le développement de logiciels Android
  15. Chen lushai and her best friend Wang Meng play video, fearless of the pressure of public opinion, and in a good mood to dance in a bare back
  16. # Sass速通(四):继承、混合与函数
  17. Vidéo de développement de combat Android, questions d'entrevue rxjava
  18. Bugatti Chiron maintenance cost exposure! One piece for one car, burn money endlessly
  19. android应用开发基础答案,深入理解Nginx
  20. 做了三年前端,你才知道10分钟就能实现一个PC版魔方游戏
  21. Html + CSS + JS implémentation ️ Responsive Lucky Turnover ️ [with full source Sharing]
  22. Ren Jialun, who married young, was in a mess. Now she feels that it is a blessing in disguise
  23. 达梦数据库使用disql生成html格式的巡检报告
  24. React render phase parsing II - beginwork process
  25. Tableau linéaire de la structure des données (dessin à la main)
  26. In 2022, what are the highlights and popular elements in skirts to make skirts more elegant and gentle?
  27. JQuery installation
  28. Exemple de développement Android, dernière compilation de questions d'entrevue Android
  29. Differences and relations between JDK, JRE and JVM, nginx architecture diagram
  30. 【Azure 云服务】Azure Cloud Service 为 Web Role(IIS Host)增加自定义字段 (把HTTP Request Header中的User-Agent字段增加到IIS输出日志中)
  31. 【Azure 云服务】Azure Cloud Service 为 Web Role(IIS Host)增加自定义字段 (把HTTP Request Header中的User-Agent字段增加到IIS输出日志中)
  32. Questions d'entrevue pour les ingénieurs en développement Android, Android Foundation 72 questions
  33. It's kind of Cadillac CT6 to have a Mercedes Benz S-class captain and a 10At entry-level configuration, falling to less than 300000
  34. H6 meets the strong enemy again! The car body has a Cayenne visual sense, breaking 8.8 seconds, and the top configuration is less than 130000
  35. How nginx supports HTTPS and Linux kernel video tutorial
  36. Le martyr se réjouit de sa vieillesse Audi R8 V10 performance Rwd
  37. import 方式隨意互轉,感受 babel 插件的威力
  38. Le mode d'importation peut se déplacer librement pour sentir la puissance du plug - in Babel
  39. Pas de héros en termes de ventes!Du point de vue de la force du produit, la nouvelle version ax7 Mach est plus forte que H6
  40. The vue3 + TS project introduces vant as needed
  41. 深入浅出虚拟 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别
  42. 深入淺出虛擬 DOM 和 Diff 算法,及 Vue2 與 Vue3 中的區別
  43. Explorer les algorithmes DOM et diff virtuels et les différences entre vue2 et vue3
  44. 两万字Vue基础知识总结,小白零基础入门,跟着路线走,不迷路(建议收藏)
  45. Résumé des connaissances de base de 20 000 mots vue, Introduction à la petite base blanche zéro, suivre la route et ne pas se perdre (Collection recommandée)
  46. 兩萬字Vue基礎知識總結,小白零基礎入門,跟著路線走,不迷路(建議收藏)
  47. "Talk show conference 4" Zhou qimo a remporté le championnat. Tout le monde l'admire. Il est mature et stable et a une vue d'ensemble
  48. Test logiciel entrevue non technique questions classiques - mise à jour continue!
  49. Digital forward disassembly reverse disassembly
  50. Analyse du cache distribué redis et essence de l'entrevue en usine v6.2.6
  51. [Hadoop 3. X series] use of HDFS rest HTTP API (II) httpfs
  52. Zhang Daxian sang in the morning to bless the motherland, xYG team: singing is much better than us
  53. My three years' experience -- avoiding endless internal friction
  54. Introduction à l'algorithme "dénombrement binaire" modéré 01 - - question d'entrevue leetcode 10.09. Recherche de matrice de tri
  55. Introduction à l'algorithme simple 06 - - leetcode 34. Trouver la première et la dernière position d'un élément dans un tableau de tri
  56. CSS animation
  57. Explain the new tags in HTML5 and the pseudo classes and pseudo elements in CSS3
  58. They are all talking about "serverless first", but do you really understand serverless?
  59. [apprentissage de l'algorithme] 1486. Fonctionnement exclusif du tableau (Java / C / C + + / python / go / Rust)
  60. Front and back end data interaction (VI) -- advantages, disadvantages and comparison of Ajax, fetch and Axios