應(yīng)對復(fù)雜軟件的思考

由于自己身處SAAS行業(yè)慕蔚,在經(jīng)歷了幾輪復(fù)雜需求的蹂躪之后彼绷,我一直試圖尋找一種解法巍佑,可以盡量cover住復(fù)雜多變的需求。在過去的一年中寄悯,通過反復(fù)閱讀和實(shí)踐萤衰,似乎讓我對此有了一些清晰的思路,所以我想寫一點(diǎn)東西總結(jié)一下自己的這一年里的思考猜旬。

在我們的項(xiàng)目初期脆栋,項(xiàng)目的規(guī)模可能比較小洒擦,代碼量很少椿争,我們的代碼或許還能整理的比較干凈,就像這幾組交換機(jī)的網(wǎng)線一樣熟嫩,比較有條理秦踪。

整齊的網(wǎng)線

但是隨著功能復(fù)雜之后,項(xiàng)目也隨之變得龐大,整個(gè)代碼就可能會和這個(gè)機(jī)房一樣椅邓,非常的混亂舍扰。在經(jīng)歷幾次這種狀況之后,于是我便在想希坚,究竟是什么問題導(dǎo)致了這種混亂边苹。

凌亂的機(jī)房

首先來看一段代碼(Kotlin Code):


fun executeRequest(request: Request) : String {
    // 校驗(yàn)身份
    val isValidate = validateRequest(request)
    if( isValidate ) {
        return "Request is not valid"
    }
    // 處理業(yè)務(wù)
   dealBiz(request)
    try {
        // 存儲數(shù)據(jù)
        saveToDB(request)
    } catch (exception:Exception) {
        return "Occur error when save results to DB"
    }
    // 發(fā)送消息
    val isSendSuccess = sendMessage(request)
    if (isSendSuccess == false) {
         return "message send unsuccessfully."
    }
    return "success"
}

這是我們比較常見的一些代碼結(jié)構(gòu),其實(shí)看起來問題也不算很大裁僧,但是隨著業(yè)務(wù)復(fù)雜个束,業(yè)務(wù)邏輯的控制和控制邏輯耦合的很厲害,閱讀這種"面條代碼"的成本越來越高聊疲。每一位新進(jìn)入項(xiàng)目的伙伴猶如進(jìn)入了一個(gè)“代碼迷宮”茬底。來來回回去尋找自己需要的那一段代碼,實(shí)際上這個(gè)時(shí)候已經(jīng)形成了:

只有上帝和我能看得懂的“上帝代碼”了获洲。

這顯然是我們不愿見到的代碼阱表,在左耳聽風(fēng)專欄里有一篇《編程的本質(zhì)》里講到:

有效地分離 Logic、Control 和 Data 是寫出好程序的關(guān)鍵所在贡珊。

那什么又是Logic,Control,Data 呢最爬?

  • Logic : 就是一般的業(yè)務(wù)代碼,類似上面代碼中的dealBiz(),sendMessage()等等
  • Control : 對業(yè)務(wù)邏輯的流程控制门岔,比如遍歷數(shù)據(jù)爱致、查找數(shù)據(jù)、多線程寒随、并發(fā)糠悯、異步等等
  • Data :函數(shù)和程序之間傳遞的這部分信息

如果有效地將這幾種代碼分離,代碼可讀性將會大大提升妻往。通過這種拆分互艾,我們也降低除了自己之外的維護(hù)者閱讀代碼翻譯業(yè)務(wù)內(nèi)容的成本。通過分離讯泣,我們可以將代碼寫成這樣:

fun executeRequest(request: Request) : String {
    return Result
            .of(request)
            .flatMap { validateRequest(request) }
            .flatMap { dealBiz(request) }
            .flatMap { saveToDB(request) }
            .flatMap { sendMessage(request) }
            .fold(
                success = { return "success" },
                failure = { return it.message }
            )
}

當(dāng)然這里說了如何寫細(xì)節(jié)的代碼纫普,那么代碼架構(gòu)又如何去做才可能保證可以應(yīng)對這么多的變化?

一般的項(xiàng)目中我們把一個(gè)軟件系統(tǒng)進(jìn)行分層判帮,這是我們目前做工程項(xiàng)目的一個(gè)共識局嘁,我們最初學(xué)習(xí)的分層架構(gòu)就是經(jīng)典的三層架構(gòu)了溉箕。它自頂向下分成三層:

  • 用戶界面層(User Interface Layer)
  • 業(yè)務(wù)邏輯層(Business Logic Layer)
  • 數(shù)據(jù)訪問層(Data Access Layer)

數(shù)據(jù)訪問層這塊晦墙,其實(shí)很多系統(tǒng)已經(jīng)變成了面向數(shù)據(jù)編程,最終做成了“數(shù)據(jù)庫管理系統(tǒng)”肴茄。按照傳統(tǒng)的三層模型晌畅,用戶界面的開發(fā)依賴Service層,而Service層又依賴著DAO寡痰,DAO對應(yīng)著數(shù)據(jù)庫抗楔。大家相互依賴棋凳,業(yè)務(wù)邏輯一旦修改,就意味著要從DAO層開始修改连躏,數(shù)據(jù)庫也跟著被修改剩岳,而往往隨著我們開發(fā)的深入,業(yè)務(wù)的模型會被不斷調(diào)整入热,這樣數(shù)據(jù)庫可能就要頻繁的變動拍棕。代碼也開始變得復(fù)雜... ...

而在領(lǐng)域驅(qū)動設(shè)計(jì)中提出了另外一種四層架構(gòu),在此之前勺良,我想先分享《實(shí)現(xiàn)領(lǐng)域驅(qū)動設(shè)計(jì)》一書講的六邊形模型绰播。

六邊形架構(gòu)

我們在設(shè)計(jì)系統(tǒng)的時(shí)候,往往過于關(guān)注數(shù)據(jù)庫尚困,Http接口等基礎(chǔ)設(shè)施的設(shè)計(jì)蠢箩,而忽略了我們需要關(guān)注的業(yè)務(wù)。在復(fù)雜系統(tǒng)中事甜,最容易變化的也是業(yè)務(wù)形態(tài)谬泌,產(chǎn)品經(jīng)常會要求改來改去,因?yàn)闃I(yè)務(wù)本身就在不斷地演進(jìn)逻谦,如果我們一開始就基于數(shù)據(jù)庫作所有的設(shè)計(jì)呵萨,那么勢必一旦遇上業(yè)務(wù)的修改,庫表肯定也需要對應(yīng)先進(jìn)行變化跨跨。假如我們?nèi)谌肓呅渭軜?gòu)潮峦,將數(shù)據(jù)庫和暴露的Controller都視為是基礎(chǔ)設(shè)施,先去關(guān)注業(yè)務(wù)的模型和代碼勇婴,Class的修改比要數(shù)據(jù)庫改起來要簡單的多忱嘹。另外一方面,也大大提高了程序的可測試性:在沒有準(zhǔn)備一堆基礎(chǔ)設(shè)施(數(shù)據(jù)庫耕渴,接口拘悦,異步通知等等)情況下,可以先測試邏輯的完整性橱脸。

另外础米,有時(shí)候隨著業(yè)務(wù)增長有的基礎(chǔ)設(shè)施是會需要進(jìn)行替換的,采用六邊形架構(gòu)之后添诉,這種更換的成本就會降低屁桑。另外如果出現(xiàn)需要使用Web Service的客戶,我們也不必糾結(jié)于之前的HTTP接口栏赴,直接開出一套新的協(xié)議代碼供客戶使用蘑斧,而不會糾結(jié)領(lǐng)域部分代碼有邏輯上的缺失。

采用六邊形架構(gòu)之后,我們的領(lǐng)域模型也會更加獨(dú)立竖瘾,更精簡沟突,在適應(yīng)新的需求時(shí)修改也會更容易。在《架構(gòu)整潔之道》之中提到的“整潔架構(gòu)”也與“六邊形架構(gòu)”大同小異捕传。

Bob大叔的Clean Architecture

其實(shí)這兩種架構(gòu)也是依賴倒置原則很好的實(shí)現(xiàn):

  1. 高層模塊不應(yīng)該依賴低層模塊惠拭,兩者都應(yīng)該依賴其抽象
  2. 抽象不應(yīng)該依賴細(xì)節(jié)
  3. 細(xì)節(jié)應(yīng)該依賴抽象

此時(shí)再回顧原來定義的四層架構(gòu):

  • 用戶展示層
  • 應(yīng)用服務(wù)層
  • 領(lǐng)域?qū)?/li>
  • 基礎(chǔ)設(shè)施層

他們的依賴關(guān)系如圖:

四層架構(gòu)

這樣我們可以將業(yè)務(wù)核心代碼放入領(lǐng)域?qū)?/code>之中,要應(yīng)對的各個(gè)場景代碼放入應(yīng)用服務(wù)層中庸论。將協(xié)議轉(zhuǎn)換求橄、中間件和數(shù)據(jù)庫的適配都放入基礎(chǔ)設(shè)施層里。在應(yīng)用層與Controller之間的那些VO作為用戶展示層葡公,以做出整潔架構(gòu)罐农。

當(dāng)我們開始學(xué)習(xí)Java的時(shí)候,都知道Java是一門面向?qū)ο蟮恼Z言催什,我們本可以將現(xiàn)實(shí)世界翻譯到代碼的世界之中涵亏,但實(shí)際上我們往往在項(xiàng)目中只會將對象定義成貧血模型扬卷,最終寫成面向過程的代碼吨些。如何做才能讓這個(gè)復(fù)雜的世界反應(yīng)到代碼里呢?

讓我們再從需求說起连锯,對于一個(gè)復(fù)雜的軟件旋圆,任何一個(gè)項(xiàng)目的參與者(包括初創(chuàng)的成員)宠默,都很難靠自己就看清整個(gè)項(xiàng)目的全貌,我們猶如圖中的盲人灵巧,大家可能最后對項(xiàng)目的理解都是不一致的搀矫。此時(shí)每一位參與者都猶如“盲人摸象”中的“盲人”,對需求(大象)只有片面的理解刻肄,于是乎瓤球,有的人覺得大象是水管的形狀,有的人覺得大象是扇子一樣的形狀敏弃,有的人說大象長得跟柱子一樣... ...

盲人摸象

通過討論卦羡,我們會對自我的認(rèn)知進(jìn)行一些修正,最終大致得出一個(gè)需求的全貌麦到。

討論的價(jià)值

比如我們要去識別一些系統(tǒng)邊界绿饵,在DDD的戰(zhàn)略設(shè)計(jì)中非常強(qiáng)調(diào)劃分界限上下文。比如我作為一個(gè)個(gè)體瓶颠,在不同的場景中拟赊,我的身份、角色都不大相同步清。

對自我身份的識別

猶如在上圖中要门,在“地鐵”廓啊、“家庭”和“公司”中,我的身份是不一樣的炒瘟,但是我依舊是我,找出業(yè)務(wù)的場景第步,也就意味著我找到了系統(tǒng)的邊界。通過分析場景識別邊界來找出系統(tǒng)的核心領(lǐng)域和支撐領(lǐng)域廓推,以此來最終確定系統(tǒng)的數(shù)量,降低系統(tǒng)的耦合翩隧。

我們還可以用四色建模方法來識別出我們系統(tǒng)中發(fā)生的整個(gè)流程,發(fā)現(xiàn)究竟是誰通過什么方式觸發(fā)了什么事情专缠,最終又影響了哪些對象淑仆。

四色建模(圖片來自infoq.cn)

最終通過我們找出的事件蔗怠,整理出一個(gè)能夠讓我們進(jìn)行溝通的模型寞射。在我們的模型被構(gòu)建得相對完善之時(shí)怠惶,其實(shí)代碼也差不多已經(jīng)被構(gòu)建出來了策治,因?yàn)檫@個(gè)時(shí)候再去回想面向?qū)ο?/strong>的設(shè)計(jì),我們發(fā)現(xiàn)模型即代碼,代碼即模型珊燎。

四色建模的要點(diǎn)

一直以來我都希望通過一些好的“工程實(shí)踐”來提高團(tuán)隊(duì)的效率以及我們的代碼質(zhì)量悔政,我想這也是思考這些架構(gòu)的意義吧谋国。我想用《架構(gòu)整潔之道》中的一句話來做本文的總結(jié):

軟件架構(gòu)的最終目標(biāo)是芦瘾,用最小的人力成本來滿足構(gòu)建和維護(hù)系統(tǒng)的需求。

正如《人月神話》里說的一樣缅糟,軟件工程里沒有“銀彈”祷愉,即使做了整潔架構(gòu)也無法避免需求的變化和延期谣辞,只是希望當(dāng)我們身處需求的困境中時(shí),仍能給自己以更多的選擇句占。

參考資料&活動 :
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纱烘,一起剝皮案震驚了整個(gè)濱河市祈餐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哺壶,老刑警劉巖山宾,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳍徽,死亡現(xiàn)場離奇詭異阶祭,居然都是意外死亡直秆,警方通過查閱死者的電腦和手機(jī)鞭盟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門懊缺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鹃两,“玉大人舀凛,你說我怎么就攤上這事猛遍。” “怎么了梯醒?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵茸习,是天一觀的道長壁肋。 經(jīng)常有香客問我浸遗,道長跛锌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮锈至,結(jié)果婚禮上译秦,老公的妹妹穿的比我還像新娘。我一直安慰自己稍途,他們只是感情好砚婆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布装盯。 她就那樣靜靜地躺著埂奈,像睡著了一般账磺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氏捞,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天液茎,我揣著相機(jī)與錄音豁护,去河邊找鬼欲间。 笑死猎贴,一個(gè)胖子當(dāng)著我的面吹牛她渴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沉唠,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼满葛,長吁一口氣:“原來是場噩夢啊……” “哼嘀韧!你這毒婦竟也來了锄贷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岳锁,沒想到半個(gè)月后蹦魔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勿决,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡低缩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年咆繁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玩般。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片礼饱。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匀伏,死狀恐怖蝴韭,靈堂內(nèi)的尸體忽然破棺而出榄鉴,到底是詐尸還是另有隱情核行,我是刑警寧澤芝雪,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布惩系,位于F島的核電站堡牡,受9級特大地震影響杨刨,放射性物質(zhì)發(fā)生泄漏妖胀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盾计。 院中可真熱鬧赁遗,春花似錦岩四、人聲如沸炫乓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽莽红。三九已至安吁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間网棍,已是汗流浹背滥玷。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工惑畴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留如贷,地道東北人到踏。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓夭禽,卻偏偏與公主長得像讹躯,于是被迫代替她去往敵國和親潮梯。 傳聞我的和親對象是個(gè)殘疾皇子惨恭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容