前言
隨著需求和規(guī)模的增大虑瀑,軟件系統(tǒng)變成了一個復(fù)雜系統(tǒng)迟几,除了實(shí)現(xiàn)業(yè)務(wù)需求和非功能需求的必要復(fù)雜性外坪仇,我們希望系統(tǒng)的復(fù)雜性越低越好酿箭。當(dāng)然我們的目的不是要消除復(fù)雜性复亏,而是要能夠駕馭它、利用它缭嫡。在這篇文章中缔御,筆者嘗試以軟件領(lǐng)域的兩個基礎(chǔ)原則和兩個重要概念出發(fā),來對如何做軟件設(shè)計(jì)做一些思考妇蛀。
首先來看一下什么是復(fù)雜系統(tǒng)耕突。
復(fù)雜系統(tǒng)
復(fù)雜系統(tǒng)(Complex systems)笤成,是指由許多可能相互作用的組成成分所組成的系統(tǒng)。在很多情況下眷茁,將這樣的系統(tǒng)表示為網(wǎng)絡(luò)是有用的疹启,其節(jié)點(diǎn)代表組成成分,鏈接則代表它們的交互作用蔼卡。
復(fù)雜系統(tǒng)里的關(guān)系是非線性的喊崖。實(shí)際上,這意味著一個小的擾動可能會產(chǎn)生很大的影響(參見“蝴蝶效應(yīng)”)雇逞、一個比例效應(yīng)荤懂、或甚至根本沒有效果。(出自維基百科)
破窗效應(yīng)
心理學(xué)上有著名的破窗效應(yīng)塘砸,也就是當(dāng)某間房子的玻璃有一個小洞時节仿,過不了很久,這間房子的所有窗戶的玻璃都會被打碎掉蔬,這個效應(yīng)告訴人們在現(xiàn)實(shí)生活中不要忽視細(xì)微的缺陷廊宪,因?yàn)檫@個細(xì)微的缺陷很容易導(dǎo)致其它細(xì)微缺陷的出現(xiàn),最后這些細(xì)微的缺陷作用于復(fù)雜系統(tǒng)時可能會演變成重大的事故女轿。
軟件系統(tǒng)中也經(jīng)常是這樣箭启,當(dāng)我們發(fā)現(xiàn)軟件系統(tǒng)中某個差的設(shè)計(jì)會腐蝕架構(gòu),但沒人去修改時蛉迹,往往很快又會有另一個腐蝕架構(gòu)的設(shè)計(jì)會出現(xiàn)傅寡,并且這兩次差的設(shè)計(jì)對于架構(gòu)的腐蝕力比單獨(dú)一項(xiàng)要大的多,因?yàn)椴畹脑O(shè)計(jì)之間會相互作用北救。對設(shè)計(jì)的質(zhì)量會造成大的傷害的一個思維是:每個人都覺得自己這樣的做法沒有問題荐操,因?yàn)槠渌艘彩沁@么做的。
軟件系統(tǒng)的兩個基礎(chǔ)原則
1珍策、可服務(wù)性是軟件系統(tǒng)的第一需要托启。
軟件系統(tǒng)需要優(yōu)先保證的就是對于外部調(diào)用者的可服務(wù)能力。
2攘宙、隨著軟件系統(tǒng)需求的不斷加入以及用戶量的增加屯耸,系統(tǒng)的熵以及所需資源都在不斷增大。
系統(tǒng)的熵的增大代表軟件系統(tǒng)的復(fù)雜性增加模聋,系統(tǒng)所需資源的增大代表軟件系統(tǒng)的規(guī)模在增大肩民,當(dāng)復(fù)雜性和規(guī)模一起增大時,根據(jù)排列組合原理链方,它們會產(chǎn)生四種不同的組合:
(1)復(fù)雜性增加的很快持痰,規(guī)模增加的很快;
(2)復(fù)雜性增加的很快祟蚀,規(guī)模增加的很慢工窍;
(3)復(fù)雜性增加的很慢割卖,規(guī)模增加的很快;
(4)復(fù)雜性增加的很慢患雏,規(guī)模增加的很慢鹏溯。
軟件系統(tǒng)的設(shè)計(jì)目標(biāo)應(yīng)該是復(fù)雜性和規(guī)模都增加的很慢,并且軟件系統(tǒng)的復(fù)雜度要能夠在我們的駕馭范圍之內(nèi)淹仑。但是目前軟件設(shè)計(jì)中普遍的困境就是:隨著復(fù)雜性越來越高丙挽,軟件系統(tǒng)變成了一個復(fù)雜系統(tǒng),人們往往只能理解復(fù)雜系統(tǒng)的一部分匀借,所以越來越難以從全局的角度去思考越來越復(fù)雜的軟件系統(tǒng)的設(shè)計(jì)颜阐,導(dǎo)致軟件的熵的增大會越來越快。當(dāng)熵值超過了一定的閾值后吓肋,系統(tǒng)會突然變得非常難以維護(hù)和擴(kuò)展凳怨,這樣就會導(dǎo)致軟件系統(tǒng)的可服務(wù)性越來越差,并且差的趨勢會越來越明顯是鬼。
面對軟件系統(tǒng)的熵日益增大的問題肤舞,通常的解決辦法就是把軟件系統(tǒng)非常明確地切割成幾部分,使得每一部分的熵都在人理解的閾值以下均蜜,每一部分都安排專門負(fù)責(zé)設(shè)計(jì)的人李剖。但是這種解決辦法不能解決這個問題,因?yàn)榧軜?gòu)是一個統(tǒng)一的綜合體系兆龙,這種考慮就決定了必須有一個掌握全局的人杖爽,這種解決辦法存在以下幾個嚴(yán)重的問題:
(1)每一部分負(fù)責(zé)設(shè)計(jì)的人都很難看到全局,不可能站在全局的高度去思考整個系統(tǒng)的設(shè)計(jì)紫皇;
(2)局部最優(yōu)并不能夠說明是全局最優(yōu),甚至有可能局部最優(yōu)的方案會使得全局受到很大的傷害腋寨。
(3)沒有一個人能夠掌控全局聪铺,這樣會使得整個軟件系統(tǒng)的熵增越來越難以控制,整個系統(tǒng)遲早會掉入到無法維護(hù)的境地萄窜。
為了解決上面的這個困境铃剔,必須使得軟件系統(tǒng)的熵控制在合理的范圍內(nèi),并且還要保證所需的資源盡量的少查刻,這樣必須有更加合理的設(shè)計(jì)方式和設(shè)計(jì)理念键兜。這就需要引出軟件領(lǐng)域中兩個重要的概念:可維護(hù)性和性能。系統(tǒng)具有好的可維護(hù)性可以讓系統(tǒng)的熵保持在可理解的范圍內(nèi)穗泵,系統(tǒng)具有好的性能可以讓系統(tǒng)的規(guī)模保持在合理的范圍內(nèi)普气。
軟件系統(tǒng)的兩個重要概念
可維護(hù)性
經(jīng)常會聽到開發(fā)人員抱怨說某個系統(tǒng)維護(hù)性太差了,可能主要表現(xiàn)在幾個方面:
(1)當(dāng)增加新的需求時佃延,不能確定該需求的實(shí)現(xiàn)要在哪幾個模塊中進(jìn)行现诀,也不確定每個模塊完成該需求的哪些部分夷磕。
(2)需要在同一模塊的多個“意想不到”的地方修改代碼:修改某個bug時,發(fā)現(xiàn)要修改的代碼分散在不同的地方仔沿,而且這些要修改的地方很多都是人“意想不到”的坐桩。
(3)很容易造成修改引入:修改某個bug時,這個bug修改好了封锉,很大概率會引入其它bug绵跷,而且這種修改沒辦法用擴(kuò)展的方式進(jìn)行(使用擴(kuò)展方式成本太高等原因)。
(4)代碼無法自注釋或者缺少必要的文字注釋成福,完全不明白代碼要處理的業(yè)務(wù)邏輯是什么抖坪。
(5)對于線上運(yùn)行系統(tǒng)的問題定位非常困難,依據(jù)線上的日志沒辦法定位出具體問題闷叉,需要人工在測試環(huán)境中進(jìn)行重現(xiàn)擦俐,并且不斷地打補(bǔ)丁進(jìn)行定位。
等等握侧。
可維護(hù)性是軟件系統(tǒng)好壞的關(guān)鍵衡量指標(biāo)之一蚯瞧,一個維護(hù)性差的系統(tǒng)不管是需求的設(shè)計(jì)和開發(fā)、運(yùn)行維護(hù)等都會非常困難品擎,并且效率低下埋合,可維護(hù)性主要包括以下幾個方面:
1、設(shè)計(jì)可維護(hù)萄传。
筆者認(rèn)為設(shè)計(jì)的主要工作有兩個:(1)定義設(shè)計(jì)相關(guān)的概念甚颂;(2)定義概念和概念之間的關(guān)系。
當(dāng)然事物的概念以及概念和概念之間的關(guān)系并不是只有一種定義秀菱,設(shè)計(jì)的目的不一樣振诬,看事物的角度不一樣,對事物的看法也會不一樣衍菱。因此在設(shè)計(jì)的時候可以以合適的方式和語言來說明事物的概念以及概念和概念之間的關(guān)系赶么。
設(shè)計(jì)可維護(hù)要求設(shè)計(jì)簡潔,設(shè)計(jì)相關(guān)的概念定義明確脊串,并且概念和概念之間的關(guān)系清晰辫呻。
設(shè)計(jì)簡潔不是說設(shè)計(jì)簡單。凡事應(yīng)該盡量往簡單處想琼锋,但是不能過于簡單放闺。因?yàn)楝F(xiàn)實(shí)世界本來就是一個非常復(fù)雜的系統(tǒng),而軟件系統(tǒng)可以認(rèn)為是現(xiàn)實(shí)世界系統(tǒng)在軟件領(lǐng)域的投影缕坎,如果現(xiàn)實(shí)系統(tǒng)非常復(fù)雜怖侦,其投影也會相應(yīng)地變得復(fù)雜。但是既然是投影,那肯定就會有相對于現(xiàn)實(shí)世界“抽象”的部分础钠,如果“抽象”的好恰力,那投影會比現(xiàn)實(shí)系統(tǒng)簡單很多,所以怎么去抽象很重要旗吁,可能決定了軟件系統(tǒng)初始復(fù)雜度的高低踩萎。(另外,現(xiàn)實(shí)世界系統(tǒng)的不同形狀投影到軟件領(lǐng)域時可能是同一個事物很钓,這樣就可以做到復(fù)用香府,我們在做軟件設(shè)計(jì)時需要進(jìn)行這種非常重要的思考)。
業(yè)務(wù)設(shè)計(jì)和實(shí)現(xiàn)設(shè)計(jì)的可維護(hù)
設(shè)計(jì)可維護(hù)分為兩個方面:業(yè)務(wù)設(shè)計(jì)的可維護(hù)和實(shí)現(xiàn)設(shè)計(jì)的可維護(hù)码倦。業(yè)務(wù)設(shè)計(jì)的可維護(hù)是站在客戶的角度思考企孩,而實(shí)現(xiàn)設(shè)計(jì)的可維護(hù)是站在軟件系統(tǒng)實(shí)現(xiàn)的角度來思考。
業(yè)務(wù)設(shè)計(jì)的目標(biāo)是為客戶帶來最大化的價(jià)值袁稽,所以業(yè)務(wù)設(shè)計(jì)修改的前提是修改這個業(yè)務(wù)可以為客戶帶來更大的價(jià)值勿璃,并且這種價(jià)值是要站在整個系統(tǒng)的角度去衡量而不是單項(xiàng)業(yè)務(wù)的角度。業(yè)務(wù)設(shè)計(jì)可維護(hù)是要去思考當(dāng)客戶所處的商業(yè)環(huán)境或者內(nèi)部的運(yùn)作流程發(fā)生變化時推汽,軟件系統(tǒng)的業(yè)務(wù)設(shè)計(jì)怎么去更好地適應(yīng)這種變化补疑。
實(shí)現(xiàn)設(shè)計(jì)的可維護(hù)則是在業(yè)務(wù)設(shè)計(jì)變更的前提下,怎么去保障實(shí)現(xiàn)的速度最快歹撒、交付的軟件版本的質(zhì)量最高莲组。
有哪些差的可維護(hù)設(shè)計(jì)
說什么是好的可維護(hù)設(shè)計(jì)非常困難,因?yàn)槭艿胶芏嘁蛩氐南拗坪驮u判锹杈,但是什么樣的設(shè)計(jì)維護(hù)非常困難,這個倒可以列舉幾個典型的迈着,筆者給出根本沒辦法維護(hù)的設(shè)計(jì)的幾個典型特征:
(1)各個服務(wù)的職責(zé)不清晰竭望。
(2)服務(wù)之間嚴(yán)重耦合,服務(wù)間的關(guān)系彼此交錯寥假,互相依賴市框,非常混亂糕韧。
(3)服務(wù)的設(shè)計(jì)跟特定的實(shí)現(xiàn)耦合太緊,沒有進(jìn)行合理的抽象喻圃。
(4)設(shè)計(jì)中引入太多沒有必要的概念萤彩,并且概念之間的關(guān)系不清晰,導(dǎo)致對于軟件設(shè)計(jì)的理解非常復(fù)雜斧拍。
那是否避免上述這些典型差的特征雀扶,軟件的設(shè)計(jì)可維護(hù)性就會很好嗎?答案是否定的。人首要的任務(wù)不是去努力看清楚遠(yuǎn)處模糊的東西愚墓,而是去做好身邊清楚的事情予权。設(shè)計(jì)的關(guān)鍵是要結(jié)合具體的業(yè)務(wù)逐漸理清楚什么樣的設(shè)計(jì)是不可維護(hù)的,并且在以后的設(shè)計(jì)中要避免它們浪册,我們需要有一個清單來記錄哪些是差的設(shè)計(jì)方法扫腺,在后面做設(shè)計(jì)時再根據(jù)這個清單來進(jìn)行逐一檢查。
2村象、代碼可維護(hù)笆环。
在實(shí)際的項(xiàng)目中,很難維護(hù)的代碼通常有以下幾個典型特征:
(1)命名隨意:代碼命名不規(guī)范厚者、不合理躁劣。
(2)違反DRY原則:重復(fù)代碼多。
(3)上帝類和上帝方法:類和方法職責(zé)不清晰库菲、類和方法過長等账忘。
(4)上下層循環(huán)依賴:上層和下層的循環(huán)依賴等。
等等熙宇。
從筆者的實(shí)際經(jīng)驗(yàn)來看鳖擒,代碼層面的可維護(hù)性雖然跟開發(fā)人員的設(shè)計(jì)能力有一定的關(guān)系,但是主要還是代碼編寫時的意識問題奇颠。開發(fā)人員需要去注重培養(yǎng)怎么去寫好代碼的意識败去,試想命名合理、注釋規(guī)范烈拒、幾乎沒有重復(fù)代碼圆裕、不存在上帝類和上帝方法、沒有循環(huán)依賴這幾個基本代碼的要求荆几,其實(shí)要做到也不難吓妆。
當(dāng)然代碼的可維護(hù)性里也包括擴(kuò)展的靈活性,要做到對擴(kuò)展開放吨铸、對修改關(guān)閉行拢,需要有一定的抽象設(shè)計(jì)能力,這個需要有一定的經(jīng)驗(yàn)積累才能做到诞吱。
3舟奠、運(yùn)行可維護(hù)。
運(yùn)行可維護(hù)主要包括運(yùn)行時的問題定位房维、運(yùn)行時服務(wù)的動態(tài)擴(kuò)展等沼瘫。筆者認(rèn)為目前阻礙軟件系統(tǒng)的運(yùn)行可維護(hù)的兩個最大障礙是:
(1)經(jīng)常忽視對于可維護(hù)性的設(shè)計(jì)。
( 2)復(fù)雜的技術(shù)架構(gòu)咙俩、流程以及組織結(jié)構(gòu)導(dǎo)致軟件系統(tǒng)的維護(hù)非常復(fù)雜耿戚。
很多時候我們在設(shè)計(jì)系統(tǒng)時,往往會忽略系統(tǒng)的運(yùn)行可維護(hù)性設(shè)計(jì),包括各服務(wù)的日志膜蛔、服務(wù)的調(diào)用鏈分析坛猪、關(guān)鍵服務(wù)的實(shí)時監(jiān)控、服務(wù)降級皂股、熔斷等處理墅茉。我們需要設(shè)計(jì)一個實(shí)時監(jiān)控的運(yùn)維地圖,通過這個運(yùn)維地圖我們可以獲知各個服務(wù)節(jié)點(diǎn)的運(yùn)行狀態(tài)屑墨、哪些服務(wù)需要擴(kuò)容躁锁、哪些服務(wù)出現(xiàn)了問題等。
復(fù)雜的系統(tǒng)必然導(dǎo)致復(fù)雜的運(yùn)維卵史。運(yùn)行可維護(hù)性的優(yōu)化战转,也可以從優(yōu)化不合理的技術(shù)架構(gòu)、流程以及組織結(jié)構(gòu)入手以躯,試想一個有著復(fù)雜的技術(shù)架構(gòu)槐秧、流程以及組織結(jié)構(gòu)的系統(tǒng),它的運(yùn)維往往也會非常復(fù)雜忧设,維護(hù)起來就會非常吃力刁标。所以在設(shè)計(jì)的時候首先應(yīng)該去試著做減法,對以前設(shè)計(jì)不合理址晕、復(fù)雜的地方進(jìn)行清理膀懈。只有簡單的東西才好管理。
性能
說到性能谨垃,我們往往關(guān)注的是系統(tǒng)運(yùn)行的速度启搂,而筆者認(rèn)為系統(tǒng)的性能不僅僅是這個,還需要關(guān)注設(shè)計(jì)刘陶、編碼等整個軟件交付過程的速度胳赌。
1、設(shè)計(jì)速度快
設(shè)計(jì)怎么同時兼顧速度和質(zhì)量匙隔?筆者認(rèn)為的一個可能有效的做法如下:
(1)弄清楚要解決的問題是什么疑苫?
(2)設(shè)計(jì)現(xiàn)狀可視化
(3)設(shè)計(jì)動作標(biāo)準(zhǔn)化
弄清楚要解決的問題是什么非常重要,但是這也是很多設(shè)計(jì)人員沒有仔細(xì)思考的地方纷责。盡快采取行動沒有問題捍掺,但是我們要清楚行動只是解決方案,一個沒有針對問題的解決方案即便實(shí)現(xiàn)的再好再膳,速度再快也是沒有用的乡小。愛因斯坦曾經(jīng)說過:如果只有一個小時的時間來解決問題,我會花55分鐘來思考問題是什么饵史,五分鐘來思考解決方案。把問題想清楚后,設(shè)計(jì)出來的東西往往就會更加簡潔胳喷、易理解并且可重用性很好湃番。
設(shè)計(jì)現(xiàn)狀可視化要求我們把系統(tǒng)的現(xiàn)有設(shè)計(jì)用圖形化的方式展現(xiàn)出來,可以保證不同的人對于同一個設(shè)計(jì)的理解是基本一致的吭露,理解的程度也是差不多的吠撮,這樣就可以做到不管是誰來設(shè)計(jì),設(shè)計(jì)的速度有保證讲竿。
設(shè)計(jì)動作標(biāo)準(zhǔn)化要求把設(shè)計(jì)的輸出要素整理成一個詳細(xì)的清單泥兰,清單里列舉了系統(tǒng)設(shè)計(jì)的必須步驟和詳細(xì)描述,這樣不管誰來設(shè)計(jì)题禀,設(shè)計(jì)的結(jié)果質(zhì)量有保證鞋诗。這里需要注意的是,設(shè)計(jì)清單不要大而空迈嘹,只是一些原則性的東西削彬,而是一定要實(shí)際的可以落地的東西。
2秀仲、編碼速度快
編碼是一項(xiàng)把設(shè)計(jì)意圖實(shí)現(xiàn)的活動融痛。只有在設(shè)計(jì)意圖明確清楚的情況下,編碼的速度和質(zhì)量才會有保障神僵。
要保證編碼的速度快雁刷,筆者認(rèn)為的一些有效做法是:
(1)理解清楚設(shè)計(jì)意圖。
(2)避免技術(shù)上的欠債保礼。
(3)重構(gòu)沛励、重構(gòu)、再重構(gòu)氓英。
理解清楚設(shè)計(jì)意圖要求我們的設(shè)計(jì)的輸出是規(guī)范侯勉、易理解的。現(xiàn)在一般有兩種方式:一種是設(shè)計(jì)和編碼實(shí)現(xiàn)是同一個人铝阐,類似于全棧的形式址貌,就是由某一個人負(fù)責(zé)從需求的分析、設(shè)計(jì)徘键、編碼及后續(xù)的上線維護(hù)练对。另一種形式就是設(shè)計(jì)和編碼實(shí)現(xiàn)是不同的人。不管是哪種形式吹害,對于設(shè)計(jì)輸出的規(guī)范和易理解再強(qiáng)調(diào)也不為過的螟凭,因?yàn)檫€需要考慮到后面換人或者維護(hù)時候的編碼效率問題。
避免技術(shù)上的欠債要求我們不能為了短期的利益而放棄長遠(yuǎn)的利益它呀。有時候?yàn)榱艘恍┚o急的需求采取了一些臨時的解決方案螺男,但這些解決方案對于整個系統(tǒng)的長遠(yuǎn)利益是有危害的棒厘。
重構(gòu)要求我們看到代碼中不合理的地方就要實(shí)行修改,避免破窗效應(yīng)的發(fā)生下隧。重構(gòu)是一個trade-off的過程奢人,根據(jù)業(yè)務(wù)需求來平衡可維護(hù)性、性能等方面淆院。好的代碼很大部分是重構(gòu)出來的何乎,只有在基于好代碼的基礎(chǔ)上,開發(fā)的速度才能有保障土辩。
3支救、運(yùn)行速度快
運(yùn)行速度就是我們通常理解的性能了,它的快慢也很大程度影響到用戶的直觀體驗(yàn)好壞拷淘。
如果運(yùn)行速度比較慢各墨,筆者認(rèn)為一些有效的做法是:
(1)重新審視業(yè)務(wù)方案,看能否從業(yè)務(wù)處理流程的優(yōu)化上來優(yōu)化性能辕棚。
(2)審視代碼實(shí)現(xiàn)方式欲主,尤其是對于一些特別耗費(fèi)時間的操作,如IO操作(磁盤和網(wǎng)絡(luò)IO)逝嚎、數(shù)據(jù)庫操作扁瓢、多層循環(huán)計(jì)算等。
重新審視業(yè)務(wù)方案要求我們把事情放在根本上來解決补君,根據(jù)筆者的經(jīng)驗(yàn)引几,性能問題絕大部分是業(yè)務(wù)方案的問題,如果優(yōu)化來業(yè)務(wù)方案挽铁,性能問題可能就也隨著解決了伟桅。這就要求我們更加深入地去了解業(yè)務(wù),去發(fā)現(xiàn)業(yè)務(wù)處理過程中有哪些可以改進(jìn)優(yōu)化的流程叽掘。
審視代碼實(shí)現(xiàn)要求我們在編碼過程中保持對于耗時操作的代碼編寫的謹(jǐn)慎楣铁,需要多考慮在多并發(fā)、多線程更扁、資源共用等運(yùn)行狀態(tài)下的高性能實(shí)現(xiàn)方案盖腕。筆者曾經(jīng)在沒有修改業(yè)務(wù)方案的前提下,通過優(yōu)化代碼浓镜,把運(yùn)行性能提高了將近一百倍溃列。