軟件工程的迷途和沉思

上世紀60年代爆發的軟件危機催生了軟件工程,人們寄希望於藉助工程化的手段管理、設計、構建和維護軟件,自此,聰明絕頂的工程師便在追求更美好軟件的漫漫長路上艱苦求索。

開發語言經歷了彙編、C、C++、Java、Erlang、Python;編程範式涵蓋了面向過程(POP)、面向對象(OOP)、泛型(GP)、函數式(FP);軟件架構從單機到分佈式到雲原生,包括巨石,庫組件模塊服務,分層,微服務,MVC/ServiceMesh/Serverless等;而軟件工程思想和方法論則包括以生命周期管理為核心注重工序的瀑布模型(Waterfall Model),以需求進化為核心注重迭代漸進的敏捷開發(Agile Development),以邊界劃分和控制為核心注重領域建模的領域驅動設計(DDD:Domain Driven Design)。

世界是繽紛複雜的,要把真實世界映射到虛擬軟件註定不會是一件容易的事,軟件開發是權衡抉擇的藝術,譬如快速交付和安全生產常常背道而馳,開發效率和運行效率總是難以一致。所以在軟件發展的歷史長河中,人們發明一種方法解決一個問題,而幾乎總是會引入另一個問題,軟件工程師不得不面對混沌不堪的世界。

面向過程(C)認為一切皆過程,現實世界都可以封裝為一個個過程,通過過程串聯和編排模擬世界。但隨着軟件被大規模用於解決複雜的商業問題,這種範式被證明缺乏足夠的抽象,雖然函數可視為最小粒度的模塊化技術,但依然無法掩蓋其模塊化能力的不足,過程和被操作數據分離也導致軟件偏離高內聚低耦合的方向。

為了解決上述問題,面向對象範式(C+++/Java)被設計為通過對象建模世界,對象把屬性和方法封裝在一起,通過公開接口與外界交互,為軟件設計提供一種邏輯層面的模塊化手段;而且,對象與現實世界的事物很容易映射。對象通過組合表徵更複雜的概念,通過接口泛化表達更抽象的概念。

泛型的動機則更加簡單,需要一種語言機制,為解決跨越數據類型而提供標準容器的障礙,C++通過模板這種語言機制提供了參數化類型的能力,編譯期的類型檢查和類實例化既保障類型安全又提升執行效率,但也增加編譯時間和損害可讀性,特別是模板元編程等新玩法的引入則讓事情更加複雜。

UML誕生於瀑布模型大行其道的時代,是獨立於具體程序設計語言的面向對象建模工具,UML把面向對象開發分解為分析(OOA)、設計(OOD)和編碼(OOP)三個階段,該流程注重分析設計而輕視編碼實現。

由於設計和實現被劃分成2個相互鉗制的階段,所以開發過程會存在兩個模型,即一個顯化於UML圖紙中的設計模型,一個隱匿於軟件源碼中的實現模型,兩個階段兩個模型必然導致設計和編碼的割裂,設計和實施交予不同人實施看似有利於分工協作,實則增加了溝通成本,降低了交付效率。

學院派一度追求依照架構師的UML設計圖就能自動生成代碼,這看起來很美,而實際上這種目標只在受限的情況下才能被滿足;而在日益複雜的商業軟件開發中,工程派感受到時間更多被消耗在開發和維護上,最終的交付件只能是源碼而非圖紙,所以開發人員只能轉向設計原則(SOLID)和設計模式(GOF)尋求慰藉,設計人員則頭頂「架構師」的美名天馬行空。

瀑布模型的過程難以逆轉,且只有到項目後期才能看到結果,針對瀑布的缺陷,敏捷開發試圖從改善軟件開發端到端的溝通方式入手,極限編程(XP)是一種實施敏捷開發的輕量級軟件工程方法學,嘗試用一種螺旋式的方式演進,極限編程正視軟件活動的複雜性,承認需求在起步階段無法固化下來,主張開發人員應該優先將精力投入到代碼中,透過引入基本價值、原則、方法等概念來靈活應對需求變更。

敏捷開發在互聯網的蓬勃發展中大放異彩,究其根本是因為互聯網應用的需求是動態變化的,難以嚴格遵照沉重的傳統瀑布模型流程。

重原型實現的敏捷方法甚至被曲解為完全不需要設計和文檔的開發方式,敏捷的優勢同時又成為它的弊端,忽視文檔的重要性,在人員流動大的情況下無疑會加大維護的困難,且它在面對領域知識複雜的組織和應用時,敏捷開發也飽受質疑。

隨着Web Service的應用井噴,以Java為代表的新興語言攻城拔地,Controller->Service->Dao或者SOA設計搖身變化成行業的標準解,Service層扮演着上帝類,所有的邏輯都往裡塞,充斥getter/setter DAO的貧血模式瀰漫著惡臭味,基於數據驅動的開發模式陷入了泥潭,領域驅動設計進入了人們的視野。

2003年,Eric Evans提倡的領域驅動設計(DDD)視「領域內核」為企業最重要資產,主張通過通用語言(Ubiquitous Language)消除表達的不準確性,領域驅動設計繼承並發展了敏捷開發。DDD把領域和設計歸為軟件設計的核心,讓業務人員和開發人員得到同樣的重視,建議合力捕捉充血的領域模型。DDD戰略設計從宏觀上確定限界上下文,而戰術設計在實現層面給出一些最佳實踐。

傳統模式秉持以數據(庫)為中心的理念,而領域驅動設計則轉向以領域模型為中心,這是根本性的設計轉變。DDD通過四重邊界劃分問題空間和解空間,確定核心、通用、支撐三類子領域,在界限上下文內部,通過分層(基礎層-領域層-應用層-展現層)實現內外隔離,應用層形成了一種保護層,有效地隔離了業務複雜度與技術複雜度。將領域層作為整個系統穩定而內聚的核心,是領域驅動設計的關鍵特徵。

回顧軟件工程發展過程中湧現的各種主義,每一個流行的思潮都有一套自圓其說的理論,都聲稱完美解決了某些問題,但又無一例外的陷入另一個框架陷阱,但又都無一能夠終結軟件工程的無序設計。我們無法跳出自娛自樂般無休止重構循環的怪圈,我們依然置身於充滿各種技術債的困境,所以,跳出各種框架模式的精神枷鎖,回過頭來,我們不妨重新審視設計的本質,我們不妨想一想應對軟件複雜性的根本原則。

可將解決大規模複雜軟件問題的方法,簡單歸為幾點:抽象,分解,隔離。

抽象就是歸類,歸類是為了復用。抽象的意義是通過表現找到事物背後的本質,抽象的目的是為了減輕認知負擔,避免重複思考和勞動,精簡問題空間,讓人關注更高層次的事物,建模是提煉心智模型的過程,本質就是一種抽象。

分解是把一個複雜問題分割為更小的易於解決的小問題,拆分問題的過程即是簡化問題的過程,問題分解之後,還需要協作,這其實就是分治的理念,庫、組件化、微服務無不閃爍着分治理念的智慧的光芒。拆分有兩種方式:技術維度和業務維度,微服務和DDD就是從業務維度做問題拆分。拆分可以遵循AKF原則和康威定律,高內聚低耦合是評價拆分好壞的標準,讓上帝的歸上帝,讓凱撒的歸凱撒。

隔離是為了解耦,建立松耦合的系統一直是工程師們孜孜以求的目標,分層是實現隔離的有效手段,每層專註於自己的功能實現,上層使用下層的能力,下層為上層提供服務,上下層之間通過約定的接口交互,不緊鄰的層之間完全透明。外部世界的規則是契約、通信以及系統級別的架構風格和模式,而內部世界的規則是分層、協作以及類級別的設計分格和模式。

在軟件變革的滾滾洪流中,軟件工程的先驅和賢哲們,提出了各種各樣的編程思想和方法論,但無一從根本上徹底解決問題,《人月神話》第16章提出,因為軟件工程是超級複雜的系統,所以斷言沒有銀彈,不僅沒有包治百病的靈藥,更指出在未來十年不可能有提升十倍效率的方法。

回顧歷史,每一種完美方案都從懟已有的方案和宣稱解決所有問題開始,然後傳播佈道,把大眾帶入自己精心設計的邏輯閉環,然後追隨者以一種宗教般的虔誠,將理論生搬硬套到項目中去,最後交付的代碼依然充斥各種模糊不清、污濁不堪,任何演進都可能便引起偶然不變性的瞬間坍塌,剩下一地雞毛,而那些新穎的理論,最終都會像裊煙一樣,飄散在歷史的浩瀚天空中。

古人云:人生而無知,卻並不愚蠢,是教育使人愚蠢。古人又云:學而不思則怠。所以,我們應該意識到思辨的重要性,對於知識,我們學習它研究它,但不盲從它。那對待軟件工程,我們應該秉持怎樣的原則呢?

首先,人是關鍵,軟件開發沒有終極解,因為軟件就是人思想的外化,而人本身充滿缺陷。我們必須認可人這種生物在抽象過程中的一些必然缺陷,以及人抽象能力的差異,這將意味着,相比於規則和流程,人其實才是軟件實施過程中的靈魂,思想和法則可以給人提供指引,但它們無法神奇的解決軟件工程中的所有問題,影響軟件開發質量的關鍵因素是人,而不是設計方法。注重形式而不是內容,注重文檔而非交付的代碼,都是本末倒置的。

其二,實事求是,具體問題具體分析,軟件涵蓋的範圍實在太廣的,這就意味着每一種具體實施細則都有它的局限性,不能用僵化的標準困住手腳,不可拘泥於規則而使之成為教條,不可用倚天劍剪指甲,不要用屠龍刀剃鬍子。比如最簡單的業務CRUD模型可能就夠了,而有些可能適用CQRS、六邊形架構,有些貧血領域對象也可以,有些可能事件風暴模式更好,有時候數據和操作應該分離,甚至讀寫也應該分離,而有時候數據和操作封裝在一起更好,脫離實際的牛刀殺雞隻會徒增笑耳。

第三,幾乎任何語言和技術都有好壞的兩面,都有適用性,比如C,它雖然欠缺抽象能力,但是它的核心語法集非常簡單,簡單意味着聚焦和可靠,意味着對程序員的要求更低,你只需要掌握幾十年不變的少數幾十個STD C API便能構建所有應用,但你必須認識到它在抽象能力和開發效率上的不足。而C++雖然有良好的抽象能力,但它的語法集太龐大,而且似乎很難約束大家都在最小的公共知識面行事,但如果能夠達成共識,又或者大家水平都比較高,它編寫的程序確實有更好的可維護性。

第四,紀律!對,紀律才是關鍵,一套方法體系不管有多麼的完美,如果團隊不能嚴格地執行方法體系規定的紀律,都是空談。無論是整潔編碼還是架構設計還是敏捷開發還是領域建模,只有持之以恆的一致性的遵守紀律,用紀律施加約束,才能持續改進質量。

最後,雖然沒有銀彈,但我們不應該過於悲觀,軟件工程一直在迂迴中前進,每進一步,就能夠解決一大片問題,同時引入一些可控的副作用,但宏觀上來看,軟件工程還是伴隨人類社會在不斷進步。

所以,何不嘗試接受不完美?

 

點擊關注,第一時間了解華為雲新鮮技術~