威信| 尋烏| 武功| 烈山| 鄒平| 嘉善| 陽春| 寶應| 鐵嶺縣| 臺前| 友好| 漢陽| 烏馬河| 松溪| 青縣| 班瑪| 漢南| 民樂| 臺北市| 樂業| 塔什庫爾干| 蘭西| 米脂| 津南| 額濟納旗| 昌江| 肅寧| 民豐| 富縣| 臺中市| 和平| 威寧| 湛江| 黃埔| 射陽| 膠南| 灤縣| 靈璧| 雙遼| 通榆| 息縣| 屏邊| 石棉| 寧陜| 高陵| 孝昌| 隴川| 應縣| 奇臺| 德陽| 薊縣| 吳橋| 安吉| 陸良| 單縣| 元陽| 海豐| 隆安| 井陘礦| 洮南| 普洱| 南充| 建昌| 新津| 綿陽| 恩平| 宜川| 察布查爾| 新源| 鹿邑| 平遠| 濰坊| 四川| 疏附| 武夷山| 巴彥淖爾| 黃巖| 宣漢| 山亭| 南召| 墊江| 石樓| 耿馬| 大姚| 寧鄉| 夾江| 平鄉| 息縣| 集安| 屏邊| 銅川| 新密| 渭源| 思南| 平頂山| 王益| 龍里| 黑河| 冊亨| 松滋| 獲嘉| 岳普湖| 宿州| 成縣| 陸川| 昭蘇| 盤錦| 榆中| 黔江| 阜南| 南和| 平泉| 息縣| 云陽| 永吉| 鎮寧| 興業| 虎林| 依安| 托克遜| 夏河| 集賢| 邕寧| 高唐| 全州| 青白江| 鷹潭| 崇陽| 豐縣| 介休| 定安| 班瑪| 薛城| 同江| 平遙| 高碑店| 莊河| 云龍| 和龍| 莘縣| 潁上| 德惠| 黎川| 陽山| 陽江| 小金| 桃江| 塔河| 屏東| 內丘| 紅河| 會寧| 澤州| 彌渡| 東至| 磐安| 碭山| 羅江| 新竹縣| 元壩| 廣饒| 浮梁| 大關| 浪卡子| 聶拉木| 桑植| 沙河| 遼陽市| 零陵| 準格爾旗| 平谷| 海興| 焉耆| 姜堰| 雙陽| 高邑| 樂亭| 平果| 南山| 文登| 新野| 株洲縣| 光澤| 遵義縣| 旬陽| 灤南| 惠安| 昌江| 西疇| 吳起| 平陸| 撫寧| 延吉| 長樂| 寧陵| 大豐| 安溪| 漳州| 扎魯特旗| 吉安縣| 千陽| 永仁| 武鄉| 滑縣| 資溪| 綏寧| 南宮| 迭部| 西和| 山陰| 昌樂| 祁陽| 延長| 慈利| 南澳| 綦江| 白水| 蘭坪| 太倉| 延川| 漳浦| 扎囊| 定襄| 崇州| 河池| 兗州| 永福| 仁壽| 赤峰| 姚安| 遼陽市| 城固| 隆化| 許昌| 德保| 吳堡| 太原| 云集鎮| 灌云| 防城區| 鄂州| 會澤| 濱州| 蘇尼特右旗| 南宮| 郾城| 陳倉| 淮陰| 陜西| 鐵力| 承德縣| 靈山| 香河| 云溪| 烏拉特后旗| 和順| 本溪市| 博羅| 湯旺河| 婁煩| 懷寧| 土默特右旗| 瀏陽| 普洱| 澳門威尼斯人注冊
<dd id="pi3yb"><menuitem id="pi3yb"></menuitem></dd>
            1. <label id="pi3yb"></label>

                <meter id="pi3yb"><menuitem id="pi3yb"></menuitem></meter>
                |
                |
                51CTO旗下網站
                |
                |
                移動端
                創建專欄

                深入分析軟件復雜度

                選擇領域驅動設計,就是要與軟件系統的復雜作一番殊死拼搏,以降低軟件復雜度為己任。那么,什么才是復雜呢?

                作者:張逸|2019-01-09 10:11

                軟件復雜度的成因

                Eric Evans的經典著作《領域驅動設計》的副標題為“軟件核心復雜性應對之道”,這說明了Eric對領域驅動設計的定位就是應對軟件開發的復雜度。Eric甚至認為:“領域驅動設計只有應用在大型項目上才能產生最大的收益”。他通過Smart UI反模式逆向地說明了在軟件設計與開發過程中如果出現了如下問題,就應該考慮運用領域驅動設計:

                • 沒有對行為的重用,也沒有對業務問題的抽象。每當操作用到業務規則時,都必須重復這些規則。
                • 快速的原型建立和迭代很快會達到其極限,因為抽象的缺乏限制了重構的選擇。
                • 復雜的功能很快會讓你無所適從,所以程序的擴展只能是增加簡單的應用模塊,沒有很好的辦法來實現更豐富的功能。

                因此,選擇領域驅動設計,就是要與軟件系統的復雜作一番殊死拼搏,以降低軟件復雜度為己任。那么,什么才是復雜呢?

                軟件復雜度

                什么是復雜?

                即使是研究復雜系統的專家,如《復雜》一書的作者Melanie Mitchell,都認為復雜沒有一個明確得到公認的定義。不過,Melanie Mitchell在接受Ubiquity雜志專訪時,還是“勉為其難”地給出了一個通俗的復雜系統定義:由大量相互作用的部分組成的系統,與整個系統比起來,這些組成部分相對簡單,沒有中央控制,組成部分之間也沒有全局性的通訊,并且組成部分的相互作用導致了復雜行為。

                這個定義庶幾可以表達軟件復雜度的特征。定義中的組成部分對于軟件系統,就是我所謂的“設計單元”,基于粒度的不同可以是函數、對象、模塊、組件和服務。這些設計單元相對簡單,然而彼此之間的相互作用卻導致了軟件系統的復雜行為。

                Jurgen Appelo從理解力與預測能力兩個維度分析了復雜系統理論,這兩個維度又各自分為不同的復雜層次,其中,理解力維度分為simple與comlicated兩個層次,預測能力維度則分為ordered,complex與chaotic三個層次,如下圖所示:

                參考復雜的含義,complicated與simple(簡單)相對,意指非常難以理解,而complex則介于ordered(有序的)與chaotic(混沌的)之間,認為在某種程度上可以預測,但會有很多出乎意料的事情發生。顯然,對于大多數軟件系統而言,系統的功能都是難以理解的;在對未來需求變化的把控上,雖然我們可以遵循一些設計原則來應對可能的變化,但未來的不可預測性使得軟件系統的演進仍然存在不可預測的風險。因此,軟件系統的所謂“復雜”其實覆蓋了complicated與complex兩個方面。要理解軟件復雜度的成因,就應該結合理解力與預測能力這兩個因素來幫助我們思考。

                理解力

                在軟件系統中,是什么阻礙了開發人員對它的理解?想象團隊招入一位新人,就像一位游客來到了一座陌生的城市,他是否會迷失在阡陌交錯的城市交通體系中,不辨方向?倘若這座城市實則是鄉野郊外的一座村落,不過只有房屋數間,一條街道連通城市的兩頭,還會生出迷失之感嗎?

                因而,影響理解力的第一要素是規模。

                1. 規模

                軟件的需求決定了系統的規模。當需求呈現線性增長的趨勢時,為了實現這些功能,軟件規模也會以近似的速度增長。由于需求不可能做到完全獨立,導致出現相互影響相互依賴的關系,修改一處就會牽一發而動全身。就好似城市的一條道路因為施工需要臨時關閉,此路不通,通行的車輛只得改道繞行,這又導致了其他原本已經飽和的道路因為涌入更多車輛而超出道路的負載變得更加擁堵,這種擁堵現象又會順勢向這些道路的其他分叉道路蔓延,形成一種輻射效應的擁堵現象。

                軟件開發的擁堵現象或許更嚴重:

                • 函數存在副作用,調用時可能對函數的結果作了隱含的假設;
                • 類的職責繁多,不敢輕易修改,因為不知這種變化會影響到哪些模塊;
                • 熱點代碼被頻繁變更,職責被包裹了一層又一層,沒有清晰的邊界;
                • 在系統某個角落,隱藏著伺機而動的Bug,當誘發條件具備時,就會讓整條調用鏈癱瘓;
                • 不同的業務場景包含了不同的例外場景,每種例外場景的處理方式都各不相同;
                • 同步處理與異步處理代碼糾纏在一起,不可預知程序執行的順序。

                當需求增多時,軟件系統的規模也會增大,且這種增長趨勢并非線性增長,會更加陡峭。倘若需求還產生了事先未曾預料到的變化,我們又沒有足夠的風險應對措施,在時間緊迫的情況下,難免會對設計做出妥協,頭疼醫頭,腳疼醫腳,在系統的各個地方打上補丁,從而欠下技術債(Technical Debt)。當技術債務越欠越多,累計到某個臨界點時,就會量變引起質變,整個軟件系統的復雜度達到巔峰,步入衰亡的老年期,成為“可怕”的遺留系統。正如飼養場的“奶牛規則”:奶牛逐漸衰老,最終無奶可擠;然而與此同時,飼養成本卻在上升。

                2. 結構

                你去過迷宮嗎?相似而回旋繁復的結構使得本來封閉狹小的空間被魔法般地擴展為一個無限的空間,變得無窮大,仿佛這空間被安置了一個循環,倘若沒有找到正確的退出條件,循環就會無休無止,永遠無法退出。許多規模較小卻格外復雜的軟件系統,就好似這樣的一座迷宮。

                此時,結構成了決定系統復雜度的關鍵因素。

                結構之所以變得復雜,多數情況下還是因為系統的質量屬性決定的。例如,我們需要滿足高性能、高并發的需求,就需要考慮在系統中引入緩存、并行處理、CDN、異步消息以及支持分區的可伸縮結構。倘若我們需要支持對海量數據的高效分析,就得考慮這些海量數據該如何分布存儲,并如何有效地利用各個節點的內存與CPU資源執行運算。

                從系統結構的視角看,單體架構一定比微服務架構更簡單,更便于掌控,正如單細胞生物比人體的生理結構要簡單數百倍;那么,為何還有這么多軟件組織開始清算自己的軟件資產,花費大量人力物力對現有的單體架構進行重構,走向微服務化?究其主因,不還是系統的質量屬性在作祟嗎?

                縱觀軟件設計的歷史,不是分久必合,合久必分,而是不斷拆分繼續拆分持續拆分的微型化過程。分解的軟件元素不可能單兵作戰。怎么協同,怎么通信,就成為了系統分解后面臨的主要問題。如果沒有控制好,這些問題固有的復雜度甚至會在某些場景下超過因為分解給我們帶來的收益。

                無論是優雅的設計,還是拙劣的設計,都可能因為某種設計權衡而導致系統結構變得復雜。唯一的區別在于前者是主動地控制結構的復雜度,而后者帶來的復雜度是偶發的,是錯誤的滋生,是一種技術債,它可能會隨著系統規模的增大而導致一種無序設計。

                在Pete Goodliffe講述的《兩個系統的故事:現代軟件神話》中詳細地羅列了無序設計系統的幾種警告信號:

                • 代碼沒有顯而易見的進入系統中的路徑;
                • 不存在一致性、不存在風格、也沒有統一的概念能夠將不同的部分組織在一起
                • 系統中的控制流讓人覺得不舒服,無法預測
                • 系統中有太多的“壞味道”,整個代碼庫散發著腐爛的氣味,是在大熱天里散發著刺激氣體的一個垃圾堆
                • 數據很少放在使用它的地方。經常引入額外的巴羅克式緩存層,目的是試圖讓數據停留在更方便的地方。

                我們看一個無序設計的軟件系統,就好像隔著一層半透明的玻璃觀察事物一般,系統中的軟件元素都變得模糊不清,充斥著各種技術債。細節層面,代碼污濁不堪,違背了“高內聚松耦合”的設計原則,導致許多代碼要么放錯了位置,要么出現重復的代碼塊;架構層面,缺乏清晰的邊界,各種通信與調用依賴糾纏在一起,同一問題域的解決方案各式各樣,讓人眼花繚亂,仿佛進入了沒有規則的無序社會。

                預測能力

                當我們掌握了事物發展的客觀規律時,我們就具有了一定的對未來的預測能力。例如我們洞察了萬有引力的本質,就可以對我們能夠觀察到的宇宙天體建立模型,相對準確地推測出各個天體在未來一段時間的運行軌跡。然而,宇宙空間變化莫測,或許因為一個星球的死亡產生黑洞的吸噬能力,就可能導致那一片星域產生劇烈的動蕩,這種動蕩會傳遞到更遠的星空,從而干擾了我們的預測。坦白說,我們現在連自己居住的地球天氣都不能做一個準確的預測,何敢妄談對星空的預測?之所以如此,正是因為未知的變化的產生。

                1. 變化

                未來總會出現不可預測的變化。這種不可預測性帶來的復雜度,使得我們產生畏懼,因為我們不知道何時會發生變化,變化的方向又會走向哪里,這就導致心理滋生一種仿若失重一般的感覺。變化讓事物失去控制,受到事物牽扯的我們會感到惶恐不安。

                在設計軟件系統時,變化讓我們患得患失,不知道如何把握系統設計的度。若拒絕對變化做出理智的預測,系統的設計會變得僵化,一旦變化發生,修改的成本會非常的大;若過于看重變化產生的影響,渴望涵蓋一切變化的可能,一旦預期的變化不曾發生,我們之前為變化付出的成本就再也補償不回來了。這就是所謂的“過度設計”。

                從需求的角度講,變化可能來自業務需求,也可能來自質量屬性。以對系統架構的影響而言,尤以后者為甚,因為它可能牽涉到整個基礎架構的變更。George Fairbanks在《恰如其分的軟件架構》一書中介紹了郵件托管服務公司RackSpace的日志架構變遷,業務功能沒有任何變化,卻因為郵件數量的持續增長,為滿足性能需求,架構經歷了三個完全不同解決方案的變遷:從最初的本地日志文件,到中央數據庫,再到基于HDFS的分布式存儲,整個系統幾乎發生了顛覆性的變化。這并非RackSpace的設計師欠缺設計能力,而是在公司草創之初,他們沒有能夠高瞻遠矚地預見到客戶數量的增長,導致日志數據增多,以至于超出了已有系統支持的能力范圍。俗話說:“事后諸葛亮”,當我們在對一個軟件系統的架構設計進行復盤時,總會發現許多設計決策是如此的愚昧。殊不知這并非愚昧,而是在設計當初,我們手中掌握的籌碼不足以讓自己贏下這場面對未來的戰爭罷了。

                2. 這就是變化之殤!

                如果將軟件系統中我們自己開發的部分都劃歸為需求的范疇,那么還有一種變化,則是因為我們依賴的第三方庫、框架或平臺、甚至語言版本的變化帶來的連鎖反應。例如,作為Java開發人員,一定更垂涎于Lambda表達式的簡潔與抽象,又或者Jigsaw提供的模塊定義能力,然而現實是我們看到多數的企業軟件系統依舊在Java 6或者Java 7中裹足不前。

                這還算是幸運的例子,因為我們盡可以滿足這種故步自封,因為情況并沒有到必須變化的境地。但當我們依賴的第三方有讓我們不得不改變的理由時,難道我們還能拒絕變化嗎?

                許多軟件在版本變遷過程中都盡量考慮到API變化對調用者帶來的影響,因而盡可能保持版本向后兼容。我親自參與過系統從Spring 2.0到4.0的升級,Spark從1.3.1到1.5再到1.6的升級,感謝這些框架或平臺設計人員對兼容性的體貼照顧,使得我們的升級成本能夠被降到最低;但是在升級之后,倘若沒有對系統做全方位的回歸測試,我們的內心始終是惴惴不安的。

                對第三方的依賴看似簡單,殊不知我們所依賴的庫、平臺或者框架又可能依賴了若干對于它們而言又份屬第三方的更多庫、平臺和框架。每回初次構建軟件系統時,我都為漫長等待的依賴下載過程而感覺煩躁不安。多種版本共存時可能帶來的所謂依賴地獄,只要親身經歷過,就沒有不感到不寒而栗的。倘若你運氣欠佳,可能還會有各種古怪問題接踵而來,讓你應接不暇,疲于奔命。

                如果變化是不可預測的,那么軟件系統也會變得不可預測。一方面我們要盡可能地控制變化,至少要將變化產生的影響限制在較小的空間范圍內;另一方面又要保證系統不會因為滿足可擴展性而變得更加復雜,最后背上過度設計的壞名聲。軟件設計者們就像走在高空鋼纜的技巧挑戰者,驚險地調整重心以維持行動的平衡。故而,變化之難,在于如何平衡。

                【本文為51CTO專欄作者“張逸”原創稿件,轉載請聯系原作者】

                戳這里,看該作者更多好文

                【編輯推薦】

                1. 外媒速遞:敏捷軟件開發當中最為常見的十類錯誤
                2. 外媒速遞:2018年值得持續關注的15位軟件開發影響力領袖
                3. 外媒速遞:要成為真正的軟件開發者,這七本英文論著不容錯過
                4. 外媒速遞:2018年最值得關注的五大軟件開發趨勢
                5. 以用戶為中心的軟件開發
                【責任編輯:趙寧寧 TEL:(010)68476606】

                點贊 0
                大家都在看
                猜你喜歡
                水玉咀村 馬杜橋鄉 仙中村 二股流村村路 橋江鎮
                宜通公司 范縣 芒哈圖 西安工業學院未央校區 長樂坪鎮
                澳門百家樂 明升娛樂場網址 寶利棋牌 新濠天地賭場網址 澳門大富豪賭博娛樂
                澳門拉斯維加斯線上賭博 燃燒的欲望電子游戲 賭城 威尼斯人線上平臺 澳門威尼斯人網站
                龍虎斗游戲娛樂 澳門二十一點游戲官網 澳門威尼斯人網址 澳門番攤游戲 電子游戲下載
                華人博彩 澳門威尼斯人官網 澳門威尼斯人平臺 網絡棋牌游戲 澳門萬利賭場網站
                河南快3三同号遗漏分析
                <dd id="pi3yb"><menuitem id="pi3yb"></menuitem></dd>
                        1. <label id="pi3yb"></label>

                            <meter id="pi3yb"><menuitem id="pi3yb"></menuitem></meter>
                            <dd id="pi3yb"><menuitem id="pi3yb"></menuitem></dd>
                                    1. <label id="pi3yb"></label>

                                        <meter id="pi3yb"><menuitem id="pi3yb"></menuitem></meter>