【架構(gòu)設(shè)計(jì)】保持簡(jiǎn)單輕量設(shè)計(jì)的三個(gè)原則——DRY,KISS, YAGNI
?前言
一個(gè)軟件輕量簡(jiǎn)單的軟件架構(gòu)是非常重要的,它可以讓我們花最小的代價(jià)就能滿足業(yè)務(wù)上的需求。那如何保證輕量簡(jiǎn)單呢?那今天就和大家分享下這其中的秘密,也就是3個(gè)重要的指導(dǎo)原則,KISS原則,YAGNI原則和DRY原則,你們都知道并且理解嗎?
KISS原則
KISS原則, 英文全稱Keep it simple and stupid。核心思想就是盡量保持簡(jiǎn)單。
KISS原則指導(dǎo)我們?cè)谲浖O(shè)計(jì)的時(shí)候要盡量保持簡(jiǎn)單,使用一些成熟的、適合業(yè)務(wù)的技術(shù)方案。另外從一個(gè)使用者的角度來思考,你設(shè)計(jì)時(shí)要思考如何讓自己的架構(gòu)設(shè)計(jì)變得簡(jiǎn)單,足夠易用,比如你開發(fā)的框架是不是對(duì)于接入成本低甚至0成本? 你設(shè)計(jì)的框架是否不侵入業(yè)務(wù)代碼?
不僅軟件架構(gòu)設(shè)計(jì)層面,在代碼層面也處處要體現(xiàn)KISS原則。代碼的可讀性和可維護(hù)性是衡量代碼質(zhì)量非常重要的兩個(gè)標(biāo)準(zhǔn)。而 KISS 原則就是保持代碼可讀和可維護(hù)的重要手段。代碼足夠簡(jiǎn)單,也就意味著很容易讀懂,bug 比較難隱藏。即便出現(xiàn) bug,修復(fù)起來也比較簡(jiǎn)單。
我們來看下面校驗(yàn)IP是否合法的3種實(shí)現(xiàn)方式哪個(gè)最“KISS”,大家覺得是哪個(gè)呢?
方式一
方式二
方式三
- 方式一代碼量最少,正則表達(dá)式本身是比較復(fù)雜的,寫出完全沒有 bug 的正則表達(dá)本身就比較有挑戰(zhàn);另一方面,并不是每個(gè)程序員都精通正則表達(dá)式。對(duì)于不怎么懂正則表達(dá)式的同事來說,看懂并且維護(hù)這段正則表達(dá)式是比較困難的。這種實(shí)現(xiàn)方式會(huì)導(dǎo)致代碼的可讀性和可維護(hù)性變差,所以,從 KISS 原則的設(shè)計(jì)初衷上來講,這種實(shí)現(xiàn)方式并不符合 KISS 原則。
- 方式二使用了 StringUtils 類、Integer 類提供的一些現(xiàn)成的工具函數(shù),來處理 IP地址字符串,邏輯清晰,可讀性好。
- 方式三不使用任何工具函數(shù),而是通過逐一處理 IP 地址中的字符,來判斷是否合法,容易出bug,不好理解。
小結(jié): 綜合來看,第二種方式更符合KISS原則。并不是代碼越少越好,還要考慮代碼是否邏輯清晰、是否容易理解、是否夠穩(wěn)定。
那如何寫出滿足 KISS 原則的代碼?
- 不要使用同事可能不懂的技術(shù)來實(shí)現(xiàn)代碼。比如前面例子中的正則表達(dá)式,還有一些編程語言中過于高級(jí)的語法等。
- 不要重復(fù)造輪子,要善于使用已經(jīng)有的工具類庫。經(jīng)驗(yàn)證明,自己去實(shí)現(xiàn)這些類庫,出bug 的概率會(huì)更高,維護(hù)的成本也比較高。
- 不要過度優(yōu)化。不要過度使用一些奇技淫巧(比如,位運(yùn)算代替算術(shù)運(yùn)算、復(fù)雜的條件語句代替if-else、使用一些過于底層的函數(shù)等)來優(yōu)化代碼,犧牲代碼的可讀性。
YAGNI原則
YAGNI 原則的英文全稱是:You Ain’t Gonna Need It。中文大意是說你根本不需要這么做。核心思想就是不要過度設(shè)計(jì)。
那什么是過度設(shè)計(jì)呢?比如說你的應(yīng)用,現(xiàn)在還是處在一個(gè)單體應(yīng)用的階段,那么這時(shí)候我就考慮啊,分布式事務(wù)該怎么做,那或者說你的數(shù)據(jù)庫還是一個(gè)單庫的時(shí)候,我就在思考。如何去做數(shù)據(jù)異構(gòu),甚至是你的業(yè)務(wù)邏輯在并不復(fù)雜的階段的時(shí)候,你就再去思考如何去上一個(gè)工作流引擎,或者是規(guī)則引擎。那再比如現(xiàn)在圖形數(shù)據(jù)庫非常的火,那你就在想方設(shè)法的把圖形數(shù)據(jù)庫引入到你自己的項(xiàng)目當(dāng)中。那這些是什么呢?。空f好聽點(diǎn)叫面向簡(jiǎn)歷編程,說難聽點(diǎn)就是什么?脫褲子放屁,多此一舉。我們提倡面向未來的架構(gòu)是什么意思???是去規(guī)劃你未來技術(shù)發(fā)展的路線,當(dāng)這一天需要到來的時(shí)候,你有能力去實(shí)現(xiàn)它。而不是說你現(xiàn)在就把未來不需要的東西去搬到自己的項(xiàng)目當(dāng)中。
所以千萬不要為了技術(shù)而技術(shù),一定要讓技術(shù)服務(wù)于業(yè)務(wù),謹(jǐn)遵YAGNI原則,過度設(shè)計(jì)是災(zāi)難。
DRY原則
DRY原則,英文全稱Don’t Repeat Yourself,直譯過來就是不要重復(fù)你自己。那這里的重復(fù)是什么意思?就是指代碼一模一樣的才算重復(fù)嗎?實(shí)際不是的,我這里從實(shí)現(xiàn)邏輯重復(fù)、功能語義重復(fù)和代碼執(zhí)行重復(fù)三個(gè)點(diǎn)來理解重復(fù)的意思。
實(shí)現(xiàn)邏輯重復(fù)
我們看下下面的例子:
- 校驗(yàn)用戶名
- 校驗(yàn)密碼
雖然上面兩個(gè)方法的代碼邏輯是重復(fù)的,但是他們語義不重復(fù),一個(gè)是用來校驗(yàn)用戶名,一個(gè)是用來校驗(yàn)密碼的,所以是符合DRY原則。如果我們強(qiáng)行把他們封裝成isValidUsernameOrPassword()方法,反而不符合單一職責(zé)原則,萬一哪天密碼的校驗(yàn)邏輯變了怎么辦呢?所以并不是說代碼一樣就一定違反了DRY原則。反而有時(shí)候代碼不一樣,恰好違反了DRY原則。
功能語義重復(fù)
直接上例子,項(xiàng)目中不同的同事由于不知道就寫了兩個(gè)校驗(yàn)IP的方法。
- 代碼一
- 代碼二
盡管兩段代碼的實(shí)現(xiàn)邏輯不重復(fù),但語義重復(fù),也就是功能重復(fù),我們認(rèn)為它違反了 DRY 原則。我們應(yīng)該在項(xiàng)目中,統(tǒng)一一種實(shí)現(xiàn)思路,所有用到判斷 IP 地址是否合法的地方,都統(tǒng)一調(diào)用同一個(gè)函數(shù)。不然哪天校驗(yàn)規(guī)則變了,很容易只改了其中一個(gè),另外一個(gè)漏改,就會(huì)出現(xiàn)莫名其妙的bug。
代碼執(zhí)行重復(fù)
我們看下面一個(gè)登錄的代碼例子。
你有沒有發(fā)現(xiàn)email的校驗(yàn)邏輯被執(zhí)行了2次,這種重復(fù)執(zhí)行的情況我們也可以認(rèn)為違反了DRY原則。
說了這么多,那有什么好的辦法提高代碼復(fù)用性,保證DRY原則呢?
- 使用現(xiàn)成的輪子,不輕易造輪子
其實(shí)最關(guān)鍵的就是寫代碼帶腦子,用到一個(gè)方法先看看有沒有現(xiàn)成的,不要看看不看,就動(dòng)手在那里造輪子。
- 減少代碼耦合
對(duì)于高度耦合的代碼,當(dāng)我們希望復(fù)用其中的一個(gè)功能,想把這個(gè)功能的代碼抽取出來成為一個(gè)獨(dú)立的模塊、類或者函數(shù)的時(shí)候,往往會(huì)發(fā)現(xiàn)牽一發(fā)而動(dòng)全身。移動(dòng)一點(diǎn)代碼,就要牽連到很多其他相關(guān)的代碼。所以,高度耦合的代碼會(huì)影響到代碼的復(fù)用性,我們要盡量減少代碼耦合。
- 滿足單一職責(zé)原則
我們前面講過,如果職責(zé)不夠單一,模塊、類設(shè)計(jì)得大而全,那依賴它的代碼或者它依賴的代碼就會(huì)比較多,進(jìn)而增加了代碼的耦合。根據(jù)上一點(diǎn),也就會(huì)影響到代碼的復(fù)用性。相反,越細(xì)粒度的代碼,代碼的通用性會(huì)越好,越容易被復(fù)用。
- 模塊化
這里的“模塊”,不單單指一組類構(gòu)成的模塊,還可以理解為單個(gè)類、函數(shù)。我們要善于將功能獨(dú)立的代碼,封裝成模塊。獨(dú)立的模塊就像一塊一塊的積木,更加容易復(fù)用,可以直接拿來搭建更加復(fù)雜的系統(tǒng)。
- 業(yè)務(wù)與非業(yè)務(wù)邏輯分離
越是跟業(yè)務(wù)無關(guān)的代碼越是容易復(fù)用,越是針對(duì)特定業(yè)務(wù)的代碼越難復(fù)用。所以,為了復(fù)用跟業(yè)務(wù)無關(guān)的代碼,我們將業(yè)務(wù)和非業(yè)務(wù)邏輯代碼分離,抽取成一些通用的框架、類庫、組件等。
- 通用代碼下沉
從分層的角度來看,越底層的代碼越通用、會(huì)被越多的模塊調(diào)用,越應(yīng)該設(shè)計(jì)得足夠可復(fù)用。一般情況下,在代碼分層之后,為了避免交叉調(diào)用導(dǎo)致調(diào)用關(guān)系混亂,我們只允許上層代碼調(diào)用下層代碼及同層代碼之間的調(diào)用,杜絕下層代碼調(diào)用上層代碼。所以,通用的代碼我們盡量下沉到更下層。
- 繼承、多態(tài)、抽象、封裝
在講面向?qū)ο筇匦缘臅r(shí)候,我們講到,利用繼承,可以將公共的代碼抽取到父類,子類復(fù)用父類的屬性和方法。利用多態(tài),我們可以動(dòng)態(tài)地替換一段代碼的部分邏輯,讓這段代碼可復(fù)用。除此之外,抽象和封裝,從更加廣義的層面、而非狹義的面向?qū)ο筇匦缘膶用鎭砝斫獾脑?,越抽象、越不依賴具體的實(shí)現(xiàn),越容易復(fù)用。代碼封裝成模塊,隱藏可變的細(xì)節(jié)、暴露不變的接口,就越容易復(fù)用。
- 應(yīng)用模板等設(shè)計(jì)模式
一些設(shè)計(jì)模式,也能提高代碼的復(fù)用性。比如,模板模式利用了多態(tài)來實(shí)現(xiàn),可以靈活地替換其中的部分代碼,整個(gè)流程模板代碼可復(fù)用。
總結(jié)
本文和大家介紹了軟件工程領(lǐng)域中保持簡(jiǎn)單架構(gòu)設(shè)計(jì)的三個(gè)原則,其實(shí)所有的這些原則就是為了達(dá)到一個(gè)很簡(jiǎn)單的目的,try everything to keep it simple,軟件設(shè)計(jì)上的簡(jiǎn)單輕量是非常重要的。