這是Google軟件工程系列[1]的最后一篇,這篇主要是分享軟件工程中常用的工具,這些工具支撐了軟件工程中的流程。但在開始之前,我們先思考一個問題:軟件的研發到底是工程還是設計?
軟件工程還是軟件設計
傳統工程的流程比如土木工程是設計師先設計好圖紙,然后工程隊按照設計圖紙去施工建造,所以這里的工程既包含設計又包含建造,但負責設計的人員明顯與建造的人員不是同一類人,甚至有著非常大的差異。
那軟件的生產流程是什么呢?以敏捷開發流程為例,組建一個軟件開發隊伍,先進行Inception確定好開發的需求及范圍,之后根據需求拆分故事卡,開發人員根據故事卡實現產品需求。在實現故事卡的過程中,開發人員每天會寫一部分代碼并在本地做自測,之后會對代碼做Code Diff[2],在這個過程中又可能重新修改設計與實現。不斷重復這個過程,直到最終這部分代碼進入集成環境被測試人員驗收,最終會上線到生產環境。那么這個過程中既包含了設計又包含了實現(或者說建造),或者說這實際上是個不斷設計的過程。
以下兩篇文章推薦閱讀,可能會讓你對這個問題有更好的理解:
?Are We Really Engineers?[3]?What Is Software Design?[4]
Google軟件工程中的工具
以下是《Software Engineering at Google》一書第四部分工具篇的思維導圖,由于此部分占全書近40%,所以本文不會詳細地介紹其中的概念,想詳細了解的讀者建議閱讀原書。本文會結合此書這部分內容分享作者的個人理解及相關經驗。
版本控制(Version control)
在眾多軟件工程所用的工具中,最重要的我覺得就是版本控制系統了(Version Control System)。版本控制系統從字面意思就可以看出來是控制源代碼的版本的,VCS就像時間寶石一樣讓開發人員在源代碼歷史中穿梭,為什么這種能力很重要?
其實這和本文開頭那個問題相關,如果說軟件開發是一個設計的過程,那這個設計可能需要不斷修改,能最低成本地在不同版本間切換非常重要,更重要的是這種能力可以讓多人協作完成軟件的設計與開發。
Development is inherently a branch-and-merge process, both when coordinating between multiple developers or a single developer at different points in time. (Software Engineering at Google)
版本控制也讓軟件開發過程中產生了Code Diff或Code Review的過程進而促進團隊知識共享,而這又是軟件工程中文化的一部分。版本控制也影響了軟件的部署過程,比如結合Pipeline與Artifact Repository,可以構建出不同環境不同版本的軟件制品。
CVCS vs DVCS
早期的版本控制系統是集中式(CVCS)的,比如Subversion,現在更流行的是分布式的(DVCS),比如Git。這兩者的區別可以看這篇文章:
?GitSvnComparison[5]
CVCS與DVCS僅僅是適用的場景不同,并不意味著后者是前者更好的替代。比如很多大的公司或組織,如Google、Microsoft與FreeBSD都在用CVCS。一般來說大的公司更偏向于用CVCS,與CVCS密切相關的就是單一代碼倉(Monorepo)了。
分布式版本控制系統如Git,其實是沒有中央存儲庫的。我們在GitHub克隆某個倉庫到本地,其中的origin其實是刻意約定設置成中央倉庫的,但我們可以在本地倉庫中添加多個遠端中央倉庫,也可以rebase多個遠端倉庫的代碼到本地倉庫。
單一代碼倉(Monorepo)
Monorepo簡單理解就是把整個組織的所有項目的代碼都放入一個倉庫中。初看不可思議,但Monorepo并不僅僅是把代碼放一塊就行了,它需要一整套的流程與工具鏈支撐,比如不同團隊協作模式、代碼庫之間的依賴管理、目錄的權限配置、構建與發布等。
與以Git為主的Polyrepo(一個項目一個代碼存儲庫)存儲庫模型相比,Monorepo有如下的好處:
?代碼共享:所有人都可以看到其他人的代碼,能降低重復代碼;?統一依賴:不會出現多個項目依賴相同三方包的不同版本導致的沖突問題;?跨項目修改簡單:大規模跨項目的重構更簡單了,能一次修改多個項目的代碼;?共享構建發布流程:能共享同一套構建發布流程,簡化基礎設施的復雜性;
Developers within an organization must not have a choice where to commit, or which version of an existing component to depend upon. (Software Engineering at Google)
進一步了解,強烈推薦閱讀這篇文檔:
?Monorepo Explained[6]
分支管理(Branch management)
版本控制系統不僅可以讓開發人員具備時間穿梭的能力,還具備開辟多重宇宙的能力,這就是分支(Branch)的功能。分支不僅僅是代碼的不同版本,它還深刻的影響了開發部署的流程。
早期流行復雜的Git Flow[7]分支模型,但這種模型帶來了很復雜的維護成本,包括分支的管理、沖突的解決等問題。最終逐漸演變出更簡單的主干分支開發(Truck Based Development[8])模型。
主干開發分支在實踐中可能存在的問題是,主干分支與流水線(Pipeline)的集成,一般會有不同環境,如CI、INT、UAT、PROD等。當開發人員要在集成環境測試時,如果有緊急的Hotfix代碼要推送到生產環境,這時候主干分支中還包含著集成環境的開發代碼,就算有特性開關(feature toggle)的支持,也不敢直接把這些代碼推入到生產環境。此時能做到就是回滾(git revert)這部分代碼回去。這個問題本質還是因為測試環境有限,無法做到一個代碼變更部署到一個臨時創建的測試環境中,這時候主干開發分支可能需要做一定的調整,比如用Release分支來發布,主干分支做開發代碼的Single Source of Truth。
不同分支模型的介紹,推薦這篇文章:
?Git(Hub) Flow, Trunk Based Development, and Code reviews[9]
代碼搜索、構建與靜態分析(Code search && Build system && Static analysis)
代碼搜索可以用最簡單的grep -r命令或者IDE的搜索功能來實現,但要在多個代碼倉庫間高效地對某些代碼進行跨倉庫搜索,那這些工具可能很難滿足需求。
Google自研了一套代碼搜索的工具,這個代碼搜索工具甚至可以和其他系統如日志查看系統集成。
Sourcegraph[10]是一個開源免費的代碼搜索云服務,可以與GitHub集成,提供良好的代碼閱讀體驗。
Google同樣實現了自己的基于制品的構建工具Bazel[11],Bazel也是支持Monorepo很好的構建工具,同樣的還有Nx[12]與Gradle[13]。
代碼靜態分析就像自動化的Code Review一樣,能幫助發現代碼中的質量與安全問題,減少不必要的Review時間,提升代碼質量。流行的代碼靜態分析工具中,SonarQube[14]是推薦的。
依賴管理(Dependency management)
依賴管理可能是軟件工程中最復雜的問題之一(短期編程代碼無需考慮此問題)。現代軟件是建立在大量的依賴庫或框架之上的,這些外部代碼很多并不受開發人員的控制,當軟件變得越來越龐大時,大量的依賴可能會形成復雜的依賴樹(如在Gradle項目中,gradle dependencies命令可以打印出應用的依賴樹)。
依賴問題最多的可能是鉆石依賴問題,簡單說就是同一個包的不同版本共存的問題,這在某些編程語言如Java中影響并不大,因為多個版本可以共存,除非在某些特殊的場景下,不同的包可能會造成一些很詭異的Bug。
在Black Duck[15]中又把依賴的問題分為三大類:
?許可證(License Risk):商業應用對依賴包的License有限制,比如無法使用GPL類的License。?安全(Security Risk):依賴包經常會被爆出重大的安全CVE[16]問題,有時候因兼容性的問題很難去通過版本升級來修復。?運營(Operational Risk):一些小眾的編程語言如Clojure的很多包,經常無人維護或者缺乏更新,導致存在潛在的運營風險。
另外一個主要的問題就是兼容性的問題,比如API出現破壞性的更新,或者ABI無法兼容。
編程語言ABI(Application binary interface)兼容性:與API(Application programming interface)類似,是描述二進制文件的兼容性。比如Java有ABI兼容性的保證,意味著基于新版本JDK的代碼可以安全地調用老版本JDK的Jar包。
在解決API變化導致的依賴問題上,業界一個流行的方案是語義化版本:SemVer[17]。通過將版本拆分為三部分,如x.y.z,x是破壞性更新版本號,y是特性版本號,z是Bug修復的版本號。我們可以在依賴配置文件如package.json中通過^或~符號來指定依賴的最大版本號范圍。
依賴管理的問題也可能和代碼設計有關。比如應用對某個外部服務有依賴,如何降低外部API變化對應用代碼的影響?這個問題可以從設計模式的角度去解決,比如創建一個適配層(如Gateway[18]模式),通過定義一個抽象的接口層去實現,而非依賴具體的外部API去實現。
持續集成與持續交付(Continuous Integration && Continuous Delivery)
CI是一種團隊開發軟件的實踐,在代碼變更集成到主代碼分支前盡早的捕捉變更帶來的問題,流程主要有自動化的測試[19]與構建,CI工具可以幫助開發人員快速獲得代碼變更是否正確的反饋。
常用的CI工具有:GitHub Actions[20],GoCD[21]與Jekins[22]。這些工具也稱為流水線(Pipeline),不僅支持UI的操作,還支持Pipeline as Code[23]。
實際的CI工具一般受制于服務器資源的限制,很難做到一個代碼變更(Code Commit)自動部署一個測試驗證環境(這也被稱為無限環境CI[24])。目前只有少數的云服務可以支持前端項目的無限環境CI,比如Cloudflare Pages[25],Vercel[26]與Netlify[27]等。
CD發生在代碼集成后,包括從代碼集成后到發布變更的軟件給用戶的過程,良好的CD實踐既可以快速進行價值交付,又可以快速獲得用戶反饋。持續交付的原則和敏捷的方法論[28]有一些重合的部分:
?敏捷:小而頻繁地發布過程,快速獲取反饋。?自動化:通過自動化的手段降低發布的時間成本。?隔離:采用模塊化的架構設計使需求變更和故障排除更簡單。?可靠:通過技術監控提高系統的可靠性。?數據驅動:使用埋點或A/B測試獲取用戶反饋的數據,通過數據做決策。?分步發布:產品特性先灰度發布,確保無誤后再全量推送給用戶。
寫在最后
軟件工程或者說軟件設計是個復雜的活動,其中既涉及文化相關的東西,又有很復雜的流程及一系列的工具集。
-
存儲
+關注
關注
13文章
4314瀏覽量
85846 -
軟件
+關注
關注
69文章
4944瀏覽量
87492 -
代碼
+關注
關注
30文章
4788瀏覽量
68612
原文標題:Google軟件工程之工具篇
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論