前言
大家有沒有發(fā)現(xiàn),高職位的崗位,八股文的技術(shù)題面試就越少,反而更多的是考察面試者工作上的心得及經(jīng)驗總結(jié)。一位前同事分享了他面試健客的經(jīng)歷,當(dāng)時他面試的崗位是架構(gòu)師,面試官問他:作為架構(gòu)師,架構(gòu)設(shè)計的方法論或者設(shè)計的原則是什么?當(dāng)時這位同事回答的不是很好,因為在平常的工作中,很少會總結(jié)這方面的知識,面試結(jié)果也可想而知。應(yīng)前同事的要求,有必要總結(jié)一下那些業(yè)界知名,給我留下深刻印象的軟件架構(gòu)設(shè)計原則及一些經(jīng)驗總結(jié),和大家一起分享。
一. 架構(gòu)到底是什么
我們可能經(jīng)常會聽到有人說架構(gòu)一詞,但是到底什么是架構(gòu),卻很少有人說的清楚。
其實架構(gòu)無處不在,它不只在計算機(jī)中存在,它在我們生活當(dāng)中也很常見,比如我們著手解決一些問題,首先要計劃一套方案,一套流程,這些方案和流程其實就是一種架構(gòu)。
在軟件系統(tǒng)中,架構(gòu)是非常重要的一部分,它統(tǒng)領(lǐng)各個模塊的開發(fā)和合作,項目從研發(fā)到上線運(yùn)營,我們要在不同的方案中選擇合適的架構(gòu),比如,我們服務(wù)器使用什么操作系統(tǒng)?使用什么語言開發(fā)?數(shù)據(jù)如何獲取和存儲?客戶端使用什么框架開發(fā)?
軟件架構(gòu)指軟件系統(tǒng)的“基礎(chǔ)結(jié)構(gòu)”,創(chuàng)造這些基礎(chǔ)結(jié)構(gòu)的準(zhǔn)則,以及對這些結(jié)構(gòu)的描述。參考維基百科的定義,我將架構(gòu)重新定義為:軟件架構(gòu)指軟件系統(tǒng)的頂層結(jié)構(gòu)。
通俗一點的說法就是,可以把軟件架構(gòu)理解成一棟房子的框架,與現(xiàn)實生活中的房子有異曲同工之處,這個框架有很多個大大小小的房間,每個房間可以放各種類型的東西。
二. 架構(gòu)設(shè)計的目的
通過熟悉和理解需求,識別系統(tǒng)復(fù)雜性所在的地方,然后針對這些復(fù)雜點進(jìn)行架構(gòu)設(shè)計。架構(gòu)設(shè)計并不是要面面俱到,不需要每個架構(gòu)都具備高性能、高可用、高擴(kuò)展等特點,而是要識別出復(fù)雜點然后有針對性地解決問題。
即架構(gòu)設(shè)計的主要目的是為了解決軟件系統(tǒng)復(fù)雜度帶來的問題。
三. 架構(gòu)設(shè)計的六大基本原則
1. 單一職責(zé)原則(Single Responsibility Principle - SRP)
譯文:永遠(yuǎn)不應(yīng)該有多于一個原因來改變某個類。
理解:對于一個類而言,應(yīng)該僅有一個引起它變化的原因。說白了就是,不同的類具備不同的職責(zé),各施其責(zé)。這就好比一個團(tuán)隊,大家分工協(xié)作,互不影響,各做各的事情。
應(yīng)用:當(dāng)我們做系統(tǒng)設(shè)計時,如果發(fā)現(xiàn)有一個類擁有了兩種的職責(zé),那就問自己一個問題:可以將這個類分成兩個類嗎?如果真的有必要,那就分吧。千萬不要讓一個類干的事情太多!
2. 開放封閉原則(Open Closed Principle - OCP)
譯文:軟件實體,如:類、模塊與函數(shù),對于擴(kuò)展應(yīng)該是開放的,但對于修改應(yīng)該是封閉的。
理解:簡言之,對擴(kuò)展開放,對修改封閉。換句話說,可以加擴(kuò)展類,但不要去修改類。
應(yīng)用:當(dāng)需求有改動,要修改代碼了,此時您要做的是,盡量用繼承或組合的方式來擴(kuò)展類的功能,而不是直接修改類的代碼。當(dāng)然,如果能夠確保對整體架構(gòu)不會產(chǎn)生任何影響,那么也沒必要搞得那么復(fù)雜了,直接改這個類吧。
3. 里氏替換原則(Liskov Substitution Principle - LSP)
譯文:使用基類的指針或引用的函數(shù),必須是在不知情的情況下,能夠使用派生類的對象。
理解:父類能夠替換子類,但子類不一定能替換父類。也就是說,在代碼中可以將父類全部替換為子類,程序不會報錯,也不會在運(yùn)行時出現(xiàn)任何異常,但反過來卻不一定成立。
應(yīng)用:在繼承類時,務(wù)必重寫(Override)父類中所有的方法,尤其需要注意父類的 protected 方法(它們往往是讓您重寫的),子類盡量不要暴露自己的 public 方法供外界調(diào)用。
4. 迪米特法則(Least Knowledge Principle - LKP)
譯文:只與你最直接的朋友交流。
理解:盡量減少對象之間的交互,從而減小類之間的耦合。簡言之,一定要做到:低耦合,高內(nèi)聚。
應(yīng)用:在做系統(tǒng)設(shè)計時,不要讓一個類依賴于太多的其他類,需盡量減小依賴關(guān)系,否則,您死都不知道自己怎么死的。
5. 接口隔離原則(Interface Segregation Principle - ISP)
譯文:一個類與另一個類之間的依賴性,應(yīng)該依賴于盡可能小的接口。
理解:不要對外暴露沒有實際意義的接口。也就是說,接口是給別人調(diào)用的,那就不要去為難別人了,盡可能保證接口的實用性吧。她好,我也好。
應(yīng)用:當(dāng)需要對外暴露接口時,需要再三斟酌,如果真的沒有必要對外提供的,就刪了吧。一旦您提供了,就意味著,您將來要多做一件事情,何苦要給自己找事做呢。
6. 依賴倒置原則(Dependence Inversion Principle - DIP)
譯文:高層模塊不應(yīng)該依賴于低層模塊,它們應(yīng)該依賴于抽象。抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
理解:應(yīng)該面向接口編程,不應(yīng)該面向?qū)崿F(xiàn)類編程。面向?qū)崿F(xiàn)類編程,相當(dāng)于就是論事,那是正向依賴(正常人思維);面向接口編程,相當(dāng)于通過事物表象來看本質(zhì),那是反向依賴,即依賴倒置(程序員思維)。
應(yīng)用:并不是說,所有的類都要有一個對應(yīng)的接口,而是說,如果有接口,那就盡量使用接口來編程吧。
將以上六大原則的英文首字母拼在一起就是 SOLID(穩(wěn)定的),所以也稱之為 SOLID 原則。只有滿足了這六大原則,才能設(shè)計出穩(wěn)定的軟件架構(gòu)!但它們畢竟只是原則,有些時候我們還是要學(xué)會靈活應(yīng)變,千萬不要生搬硬套,否則只會把簡單問題復(fù)雜化。
四. 架構(gòu)設(shè)計心得
前幾年一直擔(dān)任架構(gòu)師的崗位,對軟件架構(gòu)設(shè)計有一定的心得,小編認(rèn)為架構(gòu)設(shè)計時遵循以下三個原則,有助于你做出最好的選擇:合適原則、簡單原則、演化原則。
1、合適原則
合適原則宣言:“合適優(yōu)于業(yè)界領(lǐng)先”。
再好的夢想,也需要腳踏實地實現(xiàn)!這里的“腳踏實地”主要體現(xiàn)在下面幾個方面。
1. 將軍難打無兵之仗
沒那么多人,卻想干那么多活,是失敗的第一個主要原因。
2. 羅馬不是一天建成的
沒有那么多積累,卻想一步登天,是失敗的第二個主要原因。
3. 冰山下面才是關(guān)鍵
沒有那么卓越的業(yè)務(wù)場景,卻幻想靈光一閃成為天才,是失敗的第三個主要原因。
所以,真正優(yōu)秀的架構(gòu)都是在企業(yè)當(dāng)前人力、條件、業(yè)務(wù)等各種約束下設(shè)計出來的,能夠合理地將資源整合在一起并發(fā)揮出最大功效,并且能夠快速落地。這也是很多 BAT 出來的架構(gòu)師到了小公司或者創(chuàng)業(yè)團(tuán)隊反而做不出成績的原因,因為沒有了大公司的平臺、資源、積累,只是生搬硬套大公司的做法,失敗的概率非常高。
2、簡單原則
簡單原則宣言:“簡單優(yōu)于復(fù)雜”。
剛才我聊的這些原因,會在潛意識層面促使初出茅廬的架構(gòu)師,不自覺地追求架構(gòu)的復(fù)雜性。然而,“復(fù)雜”在制造領(lǐng)域代表先進(jìn),在建筑領(lǐng)域代表領(lǐng)先,但在軟件領(lǐng)域,卻恰恰相反,代表的是“問題”。
軟件領(lǐng)域的復(fù)雜性體現(xiàn)在兩個方面:
1. 結(jié)構(gòu)的復(fù)雜性
結(jié)構(gòu)復(fù)雜的系統(tǒng)幾乎毫無例外具備兩個特點:
組成復(fù)雜系統(tǒng)的組件數(shù)量更多;
同時這些組件之間的關(guān)系也更加復(fù)雜。
結(jié)構(gòu)上的復(fù)雜性存在的第二個問題是,某個組件改動,會影響關(guān)聯(lián)的所有組件,這些被影響的組件同樣會繼續(xù)遞歸影響更多的組件。這個問題會影響整個系統(tǒng)的開發(fā)效率,因為一旦變更涉及外部系統(tǒng),需要協(xié)調(diào)各方統(tǒng)一進(jìn)行方案評估、資源協(xié)調(diào)、上線配合。
結(jié)構(gòu)上的復(fù)雜性存在的第三個問題是,定位一個復(fù)雜系統(tǒng)中的問題總是比簡單系統(tǒng)更加困難。首先是組件多,每個組件都有嫌疑,因此要逐一排查;其次組件間的關(guān)系復(fù)雜,有可能表現(xiàn)故障的組件并不是真正問題的根源。
2. 邏輯的復(fù)雜性
邏輯復(fù)雜的組件,一個典型特征就是單個組件承擔(dān)了太多的功能。以電商業(yè)務(wù)為例,常見的功能有:商品管理、商品搜索、商品展示、訂單管理、用戶管理、支付、發(fā)貨、客服……把這些功能全部在一個組件中實現(xiàn),就是典型的邏輯復(fù)雜性。
邏輯復(fù)雜幾乎會導(dǎo)致軟件工程的每個環(huán)節(jié)都有問題,假設(shè)現(xiàn)在淘寶將這些功能全部在單一的組件中實現(xiàn),可以想象一下這個恐怖的場景:
系統(tǒng)會很龐大,可能是上百萬、上千萬的代碼規(guī)模,“clone”一次代碼要 30 分鐘。
幾十、上百人維護(hù)這一套代碼,某個“菜鳥”不小心改了一行代碼,導(dǎo)致整站崩潰。
需求像雪片般飛來,為了應(yīng)對,開幾十個代碼分支,然后各種分支合并、各種分支覆蓋。
產(chǎn)品、研發(fā)、測試、項目管理不停地開會討論版本計劃,協(xié)調(diào)資源,解決沖突。
版本太多,每天都要上線幾十個版本,系統(tǒng)每隔 1 個小時重啟一次。
線上運(yùn)行出現(xiàn)故障,幾十個人撲上去定位和處理,一間小黑屋都裝不下所有人,整個辦公區(qū)鬧翻天。
綜合前面的分析,我們可以看到,無論是結(jié)構(gòu)的復(fù)雜性,還是邏輯的復(fù)雜性,都會存在各種問題,所以架構(gòu)設(shè)計時如果簡單的方案和復(fù)雜的方案都可以滿足需求,最好選擇簡單的方案。
但是,事實上,當(dāng)軟件系統(tǒng)變得太復(fù)雜后,就會有人換一個思路進(jìn)行重構(gòu)、升級,將它重新變得簡單,這也是軟件開發(fā)的大趨勢。 簡單原則是一個樸素且偉大的原則,Google的MapReduce系統(tǒng)就采用了分而治之的思想,而背后就是將復(fù)雜問題轉(zhuǎn)化為簡單問題的典型案例。
3、演化原則
演化原則宣言:“演化優(yōu)于一步到位”。
如果沒有把握“軟件架構(gòu)需要根據(jù)業(yè)務(wù)發(fā)展不斷變化”這個本質(zhì),在做架構(gòu)設(shè)計的時候就很容易陷入一個誤區(qū):試圖一步到位設(shè)計一個軟件架構(gòu),期望不管業(yè)務(wù)如何變化,架構(gòu)都穩(wěn)如磐石。為了實現(xiàn)這樣的目標(biāo),要么照搬業(yè)界大公司公開發(fā)表的方案;要么投入龐大的資源和時間來做各種各樣的預(yù)測、分析、設(shè)計。無論哪種做法,后果都很明顯:投入巨大,落地遙遙無期。更讓人沮喪的是,就算跌跌撞撞拼死拼活終于落地,卻發(fā)現(xiàn)很多預(yù)測和分析都是不靠譜的。
軟件架構(gòu)設(shè)計其實更加類似于大自然“設(shè)計”一個生物,通過演化讓生物適應(yīng)環(huán)境,逐步變得更加強(qiáng)大。
大到人類社會、自然生物,小到一個細(xì)胞,似乎都遵循這一普世原則,軟件架構(gòu)也不例外。業(yè)務(wù)在發(fā)展、技術(shù)在創(chuàng)新、外部環(huán)境在變化,這一切都是在告誡架構(gòu)師不要貪大求全,或者盲目照搬大公司的做法。應(yīng)該認(rèn)真分析當(dāng)前業(yè)務(wù)的特點,明確業(yè)務(wù)面臨的主要問題,設(shè)計合理的架構(gòu),快速落地以滿足業(yè)務(wù)需要,然后在運(yùn)行過程中不斷完善架構(gòu),不斷隨著業(yè)務(wù)演化架構(gòu)。懷胎需要十月,早一月或晚一月都很危險。
架構(gòu)即決策。架構(gòu)需要面向業(yè)務(wù)需求,并在各種資源(人、財、物、時、事)約束條件下去做權(quán)衡、取舍。而決策就會存在不確定性。采用一些高屋建瓴的設(shè)計原則有助于去消除不確定,去逼近解決問題的最優(yōu)解。