自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Go工程化(一) 架構(gòu)整潔之道閱讀筆記

開發(fā) 架構(gòu)
本系列為極客時間 Go 進(jìn)階訓(xùn)練營筆記,全文接近 2W 字,篇幅較長,采用書中重點摘錄+不成熟的個人小結(jié)組成,桌面端可以點擊右側(cè)目錄快速定位到你感興趣的章節(jié)。

 [[388570]]

本系列為極客時間 Go 進(jìn)階訓(xùn)練營筆記,同步直播更新,預(yù)計一周更新 1 ~ 2 篇文章,到 202103 月更新完成

其實這一篇文章不應(yīng)該算在這里面,(PS: 毛老師課程上沒講這本書)但是恰好最近把這本書讀完了,并且部門內(nèi)推薦大家讀這本書,毛老師在課上也推薦這本書,也和我們這次的主題有一些關(guān)系,一切都是最好的安排,那就放這系列吧。

閱讀建議: 全文接近 2W 字,篇幅較長,采用書中重點摘錄+不成熟的個人小結(jié)組成,桌面端可以點擊右側(cè)目錄快速定位到你感興趣的章節(jié)

讀書筆記

前言

  • 今天的軟件與過去的軟件本質(zhì)上仍然是一樣的。都是由 if 語句、賦值語句以及 while 循環(huán)組成的
  • 軟件架構(gòu)的規(guī)則其實就是排列組合代碼塊的規(guī)則

這說明什么呢,說明了可能我們以為過時的,古老的技術(shù)或者解決方案也是有用的

第一部分 概述

第 1 章 設(shè)計與架構(gòu)究竟是什么

  • 架構(gòu)圖里實際上包含了所有的「底層設(shè)計細(xì)節(jié)」,這些細(xì)節(jié)信息共同支撐了頂層的架構(gòu)設(shè)計,「底層設(shè)計信息和頂層架構(gòu)設(shè)計」共同組成了整個房屋的架構(gòu)文檔。
  • 「軟件架構(gòu)的終極目標(biāo)是,用最小的人力成本來滿足構(gòu)建和維護該系統(tǒng)的需求。」
  • 一個軟件架構(gòu)的優(yōu)劣,「可以用它滿足用戶需求所需要的成本來衡量?!?/li>
  • 亂麻系統(tǒng):這種系統(tǒng)一般都是沒有經(jīng)過設(shè)計,匆匆忙忙被構(gòu)建起來的
  1. 我們經(jīng)常使用一句話來欺騙自己**“我們可以未來再重構(gòu)代碼,產(chǎn)品上線最重要!”**
  2. 另外一個錯誤的觀點:「“在工程中容忍糟糕的代碼存在可以在短期內(nèi)加快該工程上線的速度,未來這些代碼會造成一些額外的工作量,但是并沒有什么大不了”」
  • 研發(fā)團隊最好的選擇是清晰地認(rèn)識并避開工程師們過度自信的特點,開始認(rèn)真地對待自己的代碼架構(gòu),對其質(zhì)量負(fù)責(zé)

軟件的架構(gòu)的終極目標(biāo),以及如何衡量一個架構(gòu)的優(yōu)劣,尤其是兩個錯誤的觀點非常感同身受,我也說過類似的話語,還有一句話是“當(dāng)前的需求非常緊急,這只是一個臨時的系統(tǒng)很快就會被替換掉,我們先完成它”。作為一個專業(yè)的技術(shù)人員我們需要有一些底線來保證我們的代碼架構(gòu)和質(zhì)量,不能輕易妥協(xié),這在 Bob 大叔整潔系列的另外一本書中也有提到。

第 2 章 兩個價值緯度

1.行為價值

只有可以產(chǎn)生收入的代碼才是有用的代碼,技術(shù)是需要為業(yè)務(wù)服務(wù)的,但是我們的工作并不是說就按照需求文檔寫代碼,修bug就行了

  • 軟件系統(tǒng)的行為是其最直觀的價值維度。程序員的工作就是讓機器按照某種指定方式運轉(zhuǎn),給系統(tǒng)的使用者創(chuàng)造或者提高利潤。
  • 按照需求文檔編寫代碼,并且修復(fù)任何 Bug。這真是大錯特錯。
  • 「系統(tǒng)行為,是緊急的,但是并不總是特別重要?!?/li>

2.架構(gòu)價值

架構(gòu)價值主要就是為了能夠應(yīng)對變化,其實舉個反面例子,我們之前有一個系統(tǒng) A 是直接在 A 中調(diào)用接口獲取數(shù)據(jù),隨著業(yè)務(wù)的發(fā)展我們拆分了一個應(yīng)用 B 需要從 B 中獲取對應(yīng)的數(shù)據(jù),這個時候我們發(fā)現(xiàn)代碼變更非常嚴(yán)重,從里到外都需要進(jìn)行重構(gòu)修改,這就是典型了依賴了“具體的形狀”導(dǎo)致的額外成本

  • 為了達(dá)到軟件的本來目的,軟件系統(tǒng)必須夠“軟”——也就是說,軟件應(yīng)該容易被修改。
  • 當(dāng)需求方改變需求的時候,隨之所需的軟件變更必須可以簡單而方便地實現(xiàn)。
  • 變更實施的難度應(yīng)該和變更的范疇(scope)成等比關(guān)系,而與變更的具體形狀(shape)無關(guān)。
  • 「系統(tǒng)架構(gòu),是重要的,但是并不總是特別緊急?!?/li>

3.重要緊急的排序

  • 重要且緊急
  • 重要不緊急
  • 不重要但緊急
  • 不重要且不緊急

4.業(yè)務(wù)/市場的同事往往是無法評估架構(gòu)的重要性的,所以,「平衡系統(tǒng)架構(gòu)的重要性與功能的緊急程度這件事,是軟件研發(fā)人員自己的職責(zé)?!?/p>

我們當(dāng)前處在公共技術(shù)的部門,這也是一個經(jīng)常困擾的一個例子,所有的業(yè)務(wù)方在提需求的時候都會表示需求非常緊急,但是這個功能的實現(xiàn)對我們來說重要嗎?這個需要打上一個大大的問號,其他部門的同學(xué)其實是無法對評估需求對于我們的重要性的,這個需要我們自己來權(quán)衡。

5.為好的軟件架構(gòu)而持續(xù)斗爭

這不僅僅是架構(gòu)師的職責(zé),這是每一位開發(fā)同學(xué)的職責(zé),忽略架構(gòu)的價值會導(dǎo)致我們帶來無休止的加班,領(lǐng)導(dǎo)的質(zhì)疑,產(chǎn)品的argue

  • 軟件架構(gòu)師這一職責(zé)本身就應(yīng)更關(guān)注系統(tǒng)的整體結(jié)構(gòu),而不是具體的功能和系統(tǒng)行為的實現(xiàn)。
  • 「軟件架構(gòu)師必須創(chuàng)建出一個可以讓功能實現(xiàn)起來更容易、修改起來更簡單、擴展起來更輕松的軟件架構(gòu)?!?/li>
  • 如果忽視軟件架構(gòu)的價值,系統(tǒng)將會變得越來越難以維護,終會有一天,系統(tǒng)將會變得再也無法修改。

第二部分 從基礎(chǔ)構(gòu)件開始:編程范式

編程范式指的是程序的編寫模式,與具體的編程語言關(guān)系相對較小。這些范式會告訴你應(yīng)該在什么時候采用什么樣的代碼結(jié)構(gòu) 當(dāng)前的三種編程范式,結(jié)構(gòu)化編程,面向?qū)ο?,函?shù)式編程

第 3 章 編程范式總覽

1.結(jié)構(gòu)化編程(面向過程)

  • 結(jié)構(gòu)化編程對程序控制權(quán)的直接轉(zhuǎn)移進(jìn)行了限制和規(guī)范。
  • 限制了 goto 語句的使用

2.面向?qū)ο?/p>

  • 面向?qū)ο缶幊虒Τ绦蚩刂茩?quán)的間接轉(zhuǎn)移進(jìn)行了限制和規(guī)范。
  • 限制了函數(shù)指針的使用

3.函數(shù)式編程

這個角度之前還沒有看到過,對我而言還是比較新奇,從限制的角度來看不同的編程范式有著不同限制,可以減少在編程當(dāng)中出錯的可能

  • 函數(shù)式編程對程序中的賦值進(jìn)行了限制和規(guī)范。
  • 限制了賦值語句的使用

第 4 章 結(jié)構(gòu)化編程

  • Bohm 和 Jocopini 剛剛證明了人們可以用順序結(jié)構(gòu)、分支結(jié)構(gòu)、循環(huán)結(jié)構(gòu)這三種結(jié)構(gòu)構(gòu)造出任何程序。
  • 證明了我們構(gòu)建可推導(dǎo)模塊所需要的控制結(jié)構(gòu)集與構(gòu)建所有程序所需的控制結(jié)構(gòu)集的最小集是等同的。
  • 結(jié)構(gòu)化編程范式可將模塊遞歸降解拆分為可推導(dǎo)的單元,這就意味著模塊也可以按功能進(jìn)行降解拆分。
  • 測試只能展示 Bug 的存在,并不能證明不存在 Bug。

結(jié)構(gòu)化編程可以讓我們將一個大的模塊按照功能進(jìn)行拆分,變成小的功能模塊,同時通過測試我們可以證明其錯誤性,無論是架構(gòu)上還是實際的開發(fā)過程中,大模塊拆小模塊的思路的數(shù)不勝數(shù),其實單體應(yīng)用拆分為微服務(wù)應(yīng)用也是這個范疇內(nèi)的。

  • 換句話說,一段程序可以由一個測試來證明其錯誤性,但是卻不能被證明是正確的。測試的作用是讓我們得出某段程序已經(jīng)足夠?qū)崿F(xiàn)當(dāng)前目標(biāo)這一結(jié)論。

第 5 章 面向?qū)ο缶幊?/h3>

1.什么是面向?qū)ο?

面向?qū)ο罄碚撌窃?1966 年提出的,當(dāng)時 Dahl 和 Nygaard 主要是將函數(shù)調(diào)用棧遷移到了堆區(qū)域中

  • 一種常見的回答是“數(shù)據(jù)與函數(shù)的組合”,這種不太貼切
  • 另一種常見的回答是“面向?qū)ο缶幊淌且环N對真實世界進(jìn)行建模的方式”,這有點避重就輕
  • 面向?qū)ο缶幊淌欠庋b(encapsulation)、繼承(inheritance)、多態(tài)(polymorphism)這三項的有機組合

2.封裝

  • 通過采用封裝特性,我們可以把一組相關(guān)聯(lián)的數(shù)據(jù)和函數(shù)圈起來,使圈外面的代碼只能看見部分函數(shù),數(shù)據(jù)則完全不可見
  • C 語言也支持完整的封裝特性,使用 C 語言的時候應(yīng)用頭文件 .h 的模塊是無法知道結(jié)構(gòu)體中的成員變量的,但是 C++ 的頭文件中包含了成員信息。
  • 不是面向?qū)ο笳Z言的 C 語言相對于面向?qū)ο笳Z言 C++ 反而擁有更好的封裝特性,所以「我們很難說強封裝是面向?qū)ο缶幊痰谋匾獥l件」

3.繼承

  • 繼承的主要作用是讓我們可以在某個作用域內(nèi)對外部定義的某一組變量與函數(shù)進(jìn)行覆蓋
  • C 其實也可以實現(xiàn)繼承,只是相對面向?qū)ο笳Z言而言會更加困難。

4.多態(tài)

  • 歸根結(jié)底,多態(tài)其實不過就是函數(shù)指針的一種應(yīng)用。但是函數(shù)指針非常危險,需要人為的遵守很多約定,容易出 bug。
  • 面向?qū)ο缶幊陶Z言雖然在多態(tài)上并沒有理論創(chuàng)新,但它們也確實讓多態(tài)變得更安全、更便于使用了。

5.依賴反轉(zhuǎn)

  • 依賴關(guān)系(或者叫繼承關(guān)系)的方向和控制流正好是相反的,我們稱之為依賴反轉(zhuǎn)
  • 依賴關(guān)系都可以通過引入接口的方式來進(jìn)行反轉(zhuǎn)。
  • 通過這種方法,軟件架構(gòu)師可以完全控制采用了面向?qū)ο筮@種編程方式的系統(tǒng)中所有的源代碼依賴關(guān)系,
  • 而不再受到系統(tǒng)控制流的限制。不管哪個模塊調(diào)用或者被調(diào)用,軟件架構(gòu)師都可以隨意更改源代碼依賴關(guān)系。
  • 當(dāng)某個組件的源代碼需要修改時,僅僅需要重新部署該組件,不需要更改其他組件,這就是獨立部署能力。

6**面向?qū)ο缶幊叹褪且远鄳B(tài)為手段來對源代碼中的依賴關(guān)系進(jìn)行控制的能力,**這種能力讓軟件架構(gòu)師可以構(gòu)建出某種插件式架構(gòu),讓高層策略性組件與底層實現(xiàn)性組件相分離,底層組件可以被編譯成插件,實現(xiàn)獨立于高層組件的開發(fā)和部署。

在剛學(xué)習(xí)編程的時候,學(xué)到面向?qū)ο笠欢〞f到,封裝、繼承、和多態(tài),但是通過這一章我們可以發(fā)現(xiàn),面向?qū)ο笳Z言的封裝不一定比面向過程的 C 語言做的更好,這里強調(diào)的更重要的是使用多態(tài)的手段對源碼的依賴關(guān)系進(jìn)行控制,主要是指通過接口來實現(xiàn)依賴反轉(zhuǎn),這樣就可以將組件進(jìn)行分離,可以進(jìn)行獨立開發(fā)和部署。我現(xiàn)在主要使用的語言是 Go,有一個常見的問題就是 Go 是不是一個面向?qū)ο笳Z言,回答也是 Yes or no,是也不是,Go 不支持繼承,也不支持函數(shù)重載,運算符重載等在面向?qū)ο笳Z言非常常見的特性,但是 Go 的接口非常強大,不需要顯示依賴接口的設(shè)計讓我們在依賴反轉(zhuǎn)的使用上更加游刃有余。

第 6 章 函數(shù)式編程

  • 函數(shù)式編程語言中的變量(Variable)是不可變(Vary)的。
  • 為什么軟件架構(gòu)師要操心變量的可變性呢?答案顯而易見:所有的競爭問題、死鎖問題、并發(fā)更新問題都是由可變變量導(dǎo)致的。
  • 一個架構(gòu)設(shè)計良好的應(yīng)用程序應(yīng)該將狀態(tài)修改的部分和不需要修改狀態(tài)的部分隔離成單獨的組件,然后用合適的機制來保護可變量。
  • 事件溯源體系下,我們只存儲事務(wù)記錄,不存儲具體狀態(tài)。當(dāng)需要具體狀態(tài)時,我們只要從頭開始計算所有的事務(wù)即可。
  • 這種數(shù)據(jù)存儲模式中不存在刪除和更新的情況,我們的應(yīng)用程序不是 CRUD,而是 CR。因為更新和刪除這兩種操作都不存在了,自然也就不存在并發(fā)問題。

在我們剛剛結(jié)束的上一個系列,[Go并發(fā)編程](https://lailin.xyz/categories/Go%E8%BF%9B%E9%98%B6%E8%AE%AD%E7%BB%83%E8%90%A5/Go%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/)中,我們講到的大量手段來避免數(shù)據(jù)競爭,這些都是由于在并發(fā)時寫入導(dǎo)致的,而函數(shù)式編程最重要的一個特性就是變量不可變,由于變量無法被修改所以自然而然就不存在數(shù)據(jù)競爭,也就不需要加鎖,這樣可以獲得很高的性能。

第三部分 設(shè)計原則

軟件構(gòu)建中層結(jié)構(gòu)的主要目標(biāo):

  • 使軟件可容忍被改動。
  • 使軟件更容易被理解。
  • 構(gòu)建可在多個軟件系統(tǒng)中復(fù)用的組件。

在之前的[《Go設(shè)計模式》](https://lailin.xyz/post/go-design-pattern.html)系列文章當(dāng)中也有提到 SOLID 原則,換個角度可以發(fā)現(xiàn)這些其實都是殊途同歸的一些東西,SOLID 原則的歷史已經(jīng)非常悠久了,但是直到現(xiàn)在它仍然非常具有指導(dǎo)意義。

第 7 章 SRP:單一職責(zé)原則

1.「任何一個軟件模塊都應(yīng)該有且僅有一個被修改的原因。」

2.任何一個軟件模塊都應(yīng)該只對一個用戶(User)或系統(tǒng)利益相關(guān)者(Stakeholder)負(fù)責(zé)。

3.「任何一個軟件模塊都應(yīng)該只對某一類行為者負(fù)責(zé)。」

4.「反例: 代碼合并沖突」

單一職責(zé)原則非常容易被誤認(rèn)為“每個模塊應(yīng)該只做一件事”,沒錯之前我也是這么理解的,雖然這個描述沒錯,但是這并不是 SRP 的全部。

  • 多人為了不同的目的修改了同一份源代碼,這很容易造成問題的產(chǎn)生。
  • 避免這種問題產(chǎn)生的方法就是將服務(wù)不同行為者的代碼進(jìn)行切分。

第 8 章 OCP:開閉原則

1.設(shè)計良好的計算機軟件應(yīng)該易于擴展,同時抗拒修改。

  • 換句話說,「一個設(shè)計良好的計算機系統(tǒng)應(yīng)該在不需要修改的前提下就可以輕易被擴展?!?/li>

2.一個好的軟件架構(gòu)設(shè)計師會努力將舊代碼的修改需求量降至最小,甚至為 0。

  • 可以先將滿足不同需求的代碼分組(即 SRP),然后再來調(diào)整這些分組之間的依賴關(guān)系(即 DIP)

3.如果 A 組件不想被 B 組件上發(fā)生的修改所影響,那么就應(yīng)該讓 B 組件依賴于 A 組件。

4.軟件架構(gòu)師可以根據(jù)相關(guān)函數(shù)被修改的原因、修改的方式及修改的時間來對其進(jìn)行分組隔離,并將這些互相隔離的函數(shù)分組整理成組件結(jié)構(gòu),使得高階組件不會因低階組件被修改而受到影響。

5.OCP 是我們進(jìn)行系統(tǒng)架構(gòu)設(shè)計的主導(dǎo)原則,其主要目標(biāo)是讓系統(tǒng)易于擴展,同時限制其每次被修改所影響的范圍。

開閉原則在架構(gòu)設(shè)計上非常常見,其中最常見的做法就是使用接口實現(xiàn)依賴反轉(zhuǎn),如果開閉原則實現(xiàn)的不好就有可能導(dǎo)致我們在進(jìn)行后續(xù)功能擴展的時候牽一發(fā)而動全身,成本非常的高。

第 9 章 LSP:里氏替換原則

1.如果對于每個類型是 S 的對象 o1 都存在一個類型為 T 的對象 o2,能使操作 T 類型的程序 P 在用 o2 替換 o1 時行為保持不變,我們就可以將 S 稱為 T 的子類型。

2.比較常見的一個違反 LSP 原則的例子,長方形與正方形

這個反面例子對我的震撼比較大,依稀記得最開始在學(xué)習(xí)編程語言繼承的例子的時候就常常用長方形正方形來舉例,但是這個其實是違反了里式替換原則的。在架構(gòu)設(shè)計上這個原則也十分的重要,因為我們只有做到了 LSP 我們才可以在例如數(shù)據(jù)庫類型切換,微服務(wù)拆分這種場景下做的游刃有余。

  • Square 類并不是 Rectangle 類的子類型,因為 Rectangle 類的高和寬可以分別修改,而 Square 類的高和寬則必須一同修改。

第 10 章 ISP:接口隔離原則

  • ISP 最初的成因:在一般情況下,任何層次的軟件設(shè)計如果依賴于不需要的東西,都會是有害的。
  • 任何層次的軟件設(shè)計如果依賴了它并不需要的東西,就會帶來意料之外的麻煩。

由于 Go 接口的隱式依賴的特性,讓 ISP 在 Go 中處處可見,我們常常采用的方式就是在調(diào)用者處依賴接口,而不管實現(xiàn),這樣就可以做到,模塊分離以及最小化依賴。

第 11 章 DIP:依賴反轉(zhuǎn)原則

1.如果想要設(shè)計一個靈活的系統(tǒng),在源代碼層次的依賴關(guān)系中就應(yīng)該多引用抽象類型,而非具體實現(xiàn)。

2.在應(yīng)用 DIP 時,我們也不必考慮穩(wěn)定的操作系統(tǒng)或者平臺設(shè)施,因為這些系統(tǒng)接口很少會有變動。

3.主要應(yīng)該關(guān)注的是軟件系統(tǒng)內(nèi)部那些會經(jīng)常變動的(volatile)具體實現(xiàn)模塊,這些模塊是不停開發(fā)的,也就會經(jīng)常出現(xiàn)變更。

4.編碼規(guī)范

通常來說,接口會比實現(xiàn)更加穩(wěn)定,舉個反例,如果接口變動實現(xiàn)是必須要跟著修改的,因為實現(xiàn)是依賴接口的,但是反過來確未必。DIP 原則指導(dǎo)我們無論是在架構(gòu)設(shè)計還是在編碼實現(xiàn)當(dāng)中都應(yīng)該盡量的依賴抽象而不是實現(xiàn)細(xì)節(jié)。

  • 應(yīng)在代碼中多使用抽象接口,盡量避免使用那些多變的具體實現(xiàn)類。
  • 不要在具體實現(xiàn)類上創(chuàng)建衍生類。我們對繼承的使用應(yīng)該格外小心。即使是在稍微便于修改的動態(tài)類型語言中,這條守則也應(yīng)該被認(rèn)真考慮
  • 不要覆蓋(override)包含具體實現(xiàn)的函數(shù)
  • 應(yīng)避免在代碼中寫入與任何具體實現(xiàn)相關(guān)的名字,或者是其他容易變動的事物的名字。

第四部分 組件構(gòu)建原則

第 12 章 組件

1.組件是軟件的部署單元,是整個軟件系統(tǒng)在部署過程中可以獨立完成部署的最小實體

  • 例如:.jar, .gem, .dll 文件

2.鏈接加載器讓程序員們可以將程序切分成多個可被分別編譯、加載的程序段

3.組件化的插件式架構(gòu)已經(jīng)成為我們習(xí)以為常的軟件構(gòu)建形式了。

第 13 章 組件聚合

1.構(gòu)建組件相關(guān)的基本原則

  • REP:復(fù)用/發(fā)布等同原則
  • CCP:共同閉包原則
  • CRP:共同復(fù)用原則

2.REP:復(fù)用/發(fā)布等同原則

  • 軟件復(fù)用的最小粒度應(yīng)等同于其發(fā)布的最小粒度。
  • REP 原則就是指組件中的類與模塊必須是彼此緊密相關(guān)的
  • 一個組件不能由一組毫無關(guān)聯(lián)的類和模塊組成,它們之間應(yīng)該有一個共同的主題或者大方向。

3.CCP:共同閉包原則

  • 我們應(yīng)該將那些會同時修改,并且為相同目的而修改的類放到同一個組件中,而將不會同時修改,并且不會為了相同目的而修改的那些類放到不同的組件中。

4.CRP:共同復(fù)用原則

  • 不要強迫一個組件的用戶依賴他們不需要的東西。
  • 不要依賴不需要用到的東西。

5.組件張力圖

 

image.png

看到這三個原則會感到有點熟悉,像共同閉包原則就和 SOLID 中的單一職責(zé)原則類似,共同復(fù)用原則和接口隔離原則看上去也有那么幾分相似,這些知識從不同的角度看待總結(jié)問題的不同術(shù)語。最后這個組件張力圖很有意思,這說明我們在進(jìn)行架構(gòu)設(shè)計的時候是不可能做到每一項都很完美的,這當(dāng)中會有一個取舍的過程,書中講到,一般而言會項目初期會從三角右側(cè)開始,進(jìn)行一段時間后會滑動到左邊,是因為在初期為了效率我們可以犧牲一定的復(fù)用性,但是隨著依賴關(guān)系越來越復(fù)雜,那么我們就要考慮復(fù)用和擴展了。

第 14 章 組件耦合

  • 組件依賴關(guān)系圖中不應(yīng)該出現(xiàn)環(huán)。
  • 當(dāng)組件結(jié)構(gòu)依賴圖中存在循環(huán)依賴時,想要按正確的順序構(gòu)建組件幾乎是不可能的。
  • 打破循環(huán)依賴
  1. 應(yīng)用依賴反轉(zhuǎn)原則(DIP)
  2. 創(chuàng)建一個新的組件,并讓 Entities 與 Authorize 這兩個組件都依賴于它。將現(xiàn)有的這兩個組件中互相依賴的類全部放入新組件
  • 組件結(jié)構(gòu)圖是不可能自上而下被設(shè)計出來的。它必須隨著軟件系統(tǒng)的變化而變化和擴張,而不可能在系統(tǒng)構(gòu)建的最初就被完美設(shè)計出來。
  • 組件依賴結(jié)構(gòu)圖并不是用來描述應(yīng)用程序功能的,它更像是應(yīng)用程序在構(gòu)建性與維護性方面的一張地圖
  • 組件結(jié)構(gòu)圖中的一個重要目標(biāo)是指導(dǎo)如何隔離頻繁的變更
  • 如果我們在設(shè)計具體類之前就來設(shè)計組件依賴關(guān)系,那么幾乎是必然要失敗的。因為在當(dāng)下,我們對項目中的共同閉包一無所知,也不可能知道哪些組件可以復(fù)用,這樣幾乎一定會創(chuàng)造出循環(huán)依賴的組件。

在 Go 中在編譯器上就限制了我們不能出現(xiàn)循環(huán)依賴,所以我們大量的使用了 DIP 的方式,但是講層次拔高一點,從微服務(wù)的角度來講仍然不應(yīng)該出現(xiàn)循環(huán)依賴,如果出現(xiàn)那么在版本發(fā)布的時候可能會導(dǎo)致災(zāi)難性的后果,架構(gòu)的原則都是想通的,我們要時刻警惕循環(huán)依賴的出現(xiàn),對于微服務(wù)來說可以在 api 網(wǎng)關(guān)進(jìn)行判定是否成環(huán)

穩(wěn)定依賴原則

  • 依賴關(guān)系必須要指向更穩(wěn)定的方向
  • 任何一個我們預(yù)期會經(jīng)常變更的組件都不應(yīng)該被一個難于修改的組件所依賴,否則這個多變的組件也將會變得非常難以被修改
  • 讓軟件組件難于修改的一個最直接的辦法就是讓很多其他組件依賴于它。

穩(wěn)定性指標(biāo)

這一部分提出了一個對我現(xiàn)階段非常有用的一個原則,被大量依賴的組件應(yīng)該是穩(wěn)定的,依賴關(guān)系必須要指向更穩(wěn)定的方向,我當(dāng)前處在公共技術(shù)團隊,我們的服務(wù)被外部大量的依賴,所以在變更的時候會非常的麻煩,我們 I 值非常的小,幾乎可以說接近于 0,所以我們的服務(wù)在設(shè)計時一定要滿足開閉原則,保證足夠的擴展性。

  • Fan-in:入向依賴,這個指標(biāo)指代了組件外部類依賴于組件內(nèi)部類的數(shù)量。
  • Fan-out:出向依賴,這個指標(biāo)指代了組件內(nèi)部類依賴于組件外部類的數(shù)量。
  • I:不穩(wěn)定性,I=Fan-out/(Fan-in+Fan-out)。該指標(biāo)的范圍是[0,1],I=0 意味著組件是最穩(wěn)定的,I=1 意味著組件是最不穩(wěn)定的。
  1. 其中一種方法是計算所有入和出的依賴關(guān)系。通過這種方法,我們就可以計算出一個組件的位置穩(wěn)定性(positionalstability)。
  2. 穩(wěn)定依賴原則(SDP)的要求是讓每個組件的 I 指標(biāo)都必須大于其所依賴組件的 I 指標(biāo)。也就是說,組件結(jié)構(gòu)依賴圖中各組件的 I 指標(biāo)必須要按其依賴關(guān)系方向遞減。

穩(wěn)定抽象原則

穩(wěn)定抽象原則說明了越穩(wěn)定的組件應(yīng)該越抽象,從代碼的角度來講,接口是最抽象的組件之一,因為接口一般不會有其他外部的依賴,而被大量依賴,同時還給出一個統(tǒng)計抽象程度的方法,這個可以用來統(tǒng)計一下我們現(xiàn)在的現(xiàn)狀。

  • 只有多變的軟件組件落在痛苦區(qū)中才會造成麻煩
  • 現(xiàn)在我們來看看靠近(1,1)這一位置點的組件。該位置上的組件不會是我們想要的,因為這些組件通常是無限抽象的,但是沒有被其他組件依賴,這樣的組件往往無法使用。
  • 追求讓這些組件位于主序列線上,或者貼近這條線即可。
  • Nc:組件中類的數(shù)量。
  • Na:組件中抽象類和接口的數(shù)量。
  • A:抽象程度,A=Na÷Nc
  • A 指標(biāo)的取值范圍是從 0 到 1,值為 0 代表組件中沒有任何抽象類,值為 1 就意味著組件中只有抽象類。

一個組件的抽象化程度應(yīng)該與其穩(wěn)定性保持一致。

如何才能讓一個無限穩(wěn)定的組件(I=0)接受變更呢?開閉原則(OCP)為我們提供了答案。這個原則告訴我們:創(chuàng)造一個足夠靈活、能夠被擴展,而且不需要修改的類是可能的,而這正是我們所需

假設(shè) A 指標(biāo)是對組件抽象化程度的一個衡量,它的值是組件中抽象類與接口所占的比例。那么:

image.png
  • D 指標(biāo)[8]:距離 D=|A+I-1|,該指標(biāo)的取值范圍是[0,1]。值為 0 意味著組件是直接位于主序列線上的,值為 1 則意味著組件在距離主序列最遠(yuǎn)的位置。
  • 對于一個良好的系統(tǒng)設(shè)計來說,D 指標(biāo)的平均值和方差都應(yīng)該接近于 0

第五部分 軟件架構(gòu)

第 15 章 什么是軟件架構(gòu)

1.軟件架構(gòu)師自身需要是程序員,并且必須一直堅持做一線程序員,絕對不要聽從那些說應(yīng)該讓軟件架構(gòu)師從代碼中解放出來以專心解決高階問題的偽建議

2.如果不親身承受因系統(tǒng)設(shè)計而帶來的麻煩,就體會不到設(shè)計不佳所帶來的痛苦,接著就會逐漸迷失正確的設(shè)計方向。

這個也是常常會遇到的問題,就現(xiàn)在我能觀察到的為例,架構(gòu)師級別的基本上沒有看到過再做一線的程序開發(fā)工作,僅僅是平時的各種管理,規(guī)劃上的事務(wù)就已經(jīng)忙的不可開交,這其實不僅僅導(dǎo)致了架構(gòu)師本身會脫節(jié),同時也會導(dǎo)致下面的同學(xué)很少有機會學(xué)習(xí)到架構(gòu)師們過往的經(jīng)驗。

3.軟件架構(gòu)這項工作的實質(zhì)就是規(guī)劃如何將系統(tǒng)切分成組件,并安排好組件之間的排列關(guān)系,以及組件之間互相通信的方式。

4.設(shè)計軟件架構(gòu)的目的,就是為了在工作中更好地對這些組件進(jìn)行研發(fā)、部署、運行以及維護。

5.如果想設(shè)計一個便于推進(jìn)各項工作的系統(tǒng),其策略就是要在設(shè)計中盡可能長時間地保留盡可能多的可選項。

6.設(shè)計良好的架構(gòu)可以讓系統(tǒng)便于理解、易于修改、方便維護,并且能輕松部署?!杠浖軜?gòu)的終極目標(biāo)就是最大化程序員的生產(chǎn)力,同時最小化系統(tǒng)的總運營成本?!?/p>

7.開發(fā)

  • 實現(xiàn)一鍵式的輕松部署應(yīng)該是我們設(shè)計軟件架構(gòu)的一個目標(biāo)

8.運行

人力成本往往會比機器的成本更高,所以這也就是我們在代碼編寫的過程當(dāng)中對可讀性和性能需要有一個權(quán)衡,如果不是差異過大往往代碼的可讀性需要更為重要

  • 幾乎任何運行問題都可以通過增加硬件的方式來解決,這避免了軟件架構(gòu)的重新設(shè)計
  • 基于投入/產(chǎn)出比的考慮,我們的優(yōu)化重心應(yīng)該更傾向于系統(tǒng)的開發(fā)、部署以及維護
  • 一個設(shè)計良好的軟件架構(gòu)應(yīng)該能明確地反映該系統(tǒng)在運行時的需求。

9.維護

  • 在軟件系統(tǒng)的所有方面中,維護所需的成本是最高的

10.保持可選項

軟件的高層策略不應(yīng)該關(guān)心其底層到底使用哪一種數(shù)據(jù)庫

開發(fā)的早期階段也不應(yīng)該選定使用的 Web 服務(wù)

軟件的高層策略壓根不應(yīng)該跟這些有關(guān)。

在開發(fā)的早期階段不應(yīng)過早地采用依賴注入框架

  • 軟件有行為價值與架構(gòu)價值兩種價值。這其中的第二種價值又比第一種更重要
  • 軟件的靈活性則取決于系統(tǒng)的整體狀況、組件的布置以及組件之間的連接方式。
  • 如果在開發(fā)高層策略時有意地讓自己擺脫具體細(xì)節(jié)的糾纏,我們就可以將與具體實現(xiàn)相關(guān)的細(xì)節(jié)決策推遲或延后,因為越到項目的后期,我們就擁有越多的信息來做出合理的決策。
  • 一個優(yōu)秀的軟件架構(gòu)師應(yīng)該致力于最大化可選項數(shù)量

11.**優(yōu)秀的架構(gòu)師會小心地將軟件的高層策略與其底層實現(xiàn)隔離開,讓高層策略與實現(xiàn)細(xì)節(jié)脫鉤,使其策略部分完全不需要關(guān)心底層細(xì)節(jié),當(dāng)然也不會對這些細(xì)節(jié)有任何形式的依賴。**另外,「優(yōu)秀的架構(gòu)師所設(shè)計的策略應(yīng)該允許系統(tǒng)盡可能地推遲與實現(xiàn)細(xì)節(jié)相關(guān)的決策,越晚做決策越好」

這一點其實很容易被忽略掉,因為我們經(jīng)常做的工作就是細(xì)節(jié)性的工作,在進(jìn)行設(shè)計的時候很容易就不自覺的假定 Web UI,MySQL 數(shù)據(jù)庫這些技術(shù)選型,在這本書的最后一個章節(jié)還會講到,這些細(xì)節(jié)。

第 16 章 獨立性

1.用例

  • 軟件的架構(gòu)必須為其用例提供支持。

2.任何一個組織在設(shè)計系統(tǒng)時,往往都會復(fù)制出一個與該組織內(nèi)溝通結(jié)構(gòu)相同的系統(tǒng)。

3.一個設(shè)計良好的架構(gòu)通常不會依賴于成堆的腳本與配置文件,也不需要用戶手動創(chuàng)建一堆“有嚴(yán)格要求”的目錄與文件

4.如果我們按照變更原因的不同對系統(tǒng)進(jìn)行解耦,就可以持續(xù)地向系統(tǒng)內(nèi)添加新的用例,而不會影響舊有的用例。如果我們同時對支持這些用例的 UI 和數(shù)據(jù)庫也進(jìn)行了分組,那么每個用例使用的就是不同面向的 UI 與數(shù)據(jù)庫,因此增加新用例就更不太可能會影響舊有的用例了。

5.如果有兩段看起來重復(fù)的代碼,它們走的是不同的演進(jìn)路徑,也就是說它們有著不同的變更速率和變更緣由,那么這兩段代碼就不是真正的重復(fù)

6.解耦模式

“如果兩段看似重復(fù)的代碼,如果有不同的變更速率和原因,那么這兩段代碼就不算是真正的重復(fù)”這有個非常典型的例子就是 API 接口的參數(shù)和最后我們模型數(shù)據(jù)雖然很多時候大部分字段是相同的,但是它們的變更速率和原因其實都是不一樣的,如果把他們耦合在一起雖然前期可能可以減少一些代碼的編寫,但是到最后需要擴展時會發(fā)現(xiàn)變更會很困難。之前我還寫了一篇文章 《[Go Web 小技巧(三)Gin 參數(shù)綁定 ](https://lailin.xyz/post/11996.html#2-%E7%94%A8-model-%E5%B1%82%E7%9A%84-struct-%E7%BB%91%E5%AE%9A%E5%8F%82%E6%95%B0)》總結(jié)這種埋坑的技巧 😂

  • 源碼層次:我們可以控制源代碼模塊之間的依賴關(guān)系,以此來實現(xiàn)一個模塊的變更不會導(dǎo)致其他模塊也需要變更或重新編譯
  • 部署層次:我們可以控制部署單元(譬如 jar 文件、DLL、共享庫等)之間的依賴關(guān)系,以此來實現(xiàn)一個模塊的變更不會導(dǎo)致其他模塊的重新構(gòu)建和部署。
  • 服務(wù)層次:我們可以將組件間的依賴關(guān)系降低到數(shù)據(jù)結(jié)構(gòu)級別,然后僅通過網(wǎng)絡(luò)數(shù)據(jù)包來進(jìn)行通信。
  • 一個設(shè)計良好的架構(gòu)應(yīng)該能允許一個系統(tǒng)從單體結(jié)構(gòu)開始,以單一文件的形式部署,然后逐漸成長為一組相互獨立的可部署單元,甚至是獨立的服務(wù)或者微服務(wù)。最后還能隨著情況的變化,允許系統(tǒng)逐漸回退到單體結(jié)構(gòu)

第 17 章 劃分邊界

  • 軟件架構(gòu)設(shè)計本身就是一門劃分邊界的藝術(shù)。
  • 通過劃清邊界,我們可以推遲和延后一些細(xì)節(jié)性的決策,這最終會為我們節(jié)省大量的時間、避免大量的問題。
  • I/O 是無關(guān)緊要的
  • GUI 和 BusinessRules 這兩個組件之間也應(yīng)該有一條邊界線
  • 插件式架構(gòu)的好處
  • 真正核心的是我們業(yè)務(wù)邏輯,而輸入輸出是細(xì)節(jié)

image.png

  • 將系統(tǒng)設(shè)計為插件式架構(gòu),就等于構(gòu)建起了一面變更無法逾越的防火墻。換句話說,只要 GUI 是以插件形式插入系統(tǒng)的業(yè)務(wù)邏輯中的,那么 GUI 這邊所發(fā)生的變更就不會影響系統(tǒng)的業(yè)務(wù)邏輯。
  • 邊界線也應(yīng)該沿著系統(tǒng)的變更軸來畫。也就是說,位于邊界線兩側(cè)的組件應(yīng)該以不同原因、不同速率變化著。

第 18 章 邊界剖析

  • 跨邊界調(diào)用指的是邊界線一側(cè)的函數(shù)調(diào)用另一側(cè)的函數(shù),并同時傳遞數(shù)據(jù)的行為
  • 最簡單的跨邊界調(diào)用形式,是由低層客戶端來調(diào)用高層服務(wù)函數(shù),這種依賴關(guān)系在運行時和編譯時會保持指向一致,都是從低層組件指向高層組件
  • 在單體結(jié)構(gòu)中,組件之間的交互一般情況下都只是普通的函數(shù)調(diào)用,迅速而廉價,這就意味著這種跨源碼層次解耦邊界的通信會很頻繁
  • 服務(wù)之間的跨邊界通信相對于函數(shù)調(diào)用來說,速度是非常緩慢的,其往返時間可以從幾十毫秒到幾秒不等。

不同的邊界的跨邊界調(diào)用的成本是不同的,對于服務(wù)而言跨服務(wù)調(diào)用的成本非常高,這樣我們在進(jìn)行服務(wù)劃分的時候一定要盡量的內(nèi)聚減少頻繁調(diào)用的情況。

第 19 章 策略與層次

1.策略

  • 本質(zhì)上,所有的軟件系統(tǒng)都是一組策略語句的集合
  • 變更原因、時間和層次相同的策略應(yīng)該被分到同一個組件中。反之,變更原因、時間和層次不同的策略則應(yīng)該分屬于不同的組件
  • 依賴關(guān)系的方向通常取決于它們所關(guān)聯(lián)的組件層次。一般來說,低層組件被設(shè)計為依賴于高層組件

2.層次

距離 I/O 越遠(yuǎn)的策略層次越高,也就是說我們常見的 Web UI 應(yīng)該屬于最低層次,我們不應(yīng)該依賴 Web UI 這種輸入輸出設(shè)備。同時給出了組件的劃分原則,變更的時間原因和層次相同的屬于同一個組件。

  • 一條策略距離系統(tǒng)的輸入/輸出越遠(yuǎn),它所屬的層次就越高。而直接管理輸入/輸出的策略在系統(tǒng)中的層次是最低的。
  • 數(shù)據(jù)流向和源碼中的依賴關(guān)系并不總處于同一方向上
  • 我們希望源碼中的依賴關(guān)系與其數(shù)據(jù)流向脫鉤,而與組件所在的層次掛鉤。
  • 低層組件應(yīng)該成為高層組件的插件

第 20 章 業(yè)務(wù)邏輯

1.業(yè)務(wù)邏輯就是程序中那些真正用于賺錢或省錢的業(yè)務(wù)邏輯與過程

2.“關(guān)鍵業(yè)務(wù)邏輯”是一項業(yè)務(wù)的關(guān)鍵部分,不管有沒有自動化系統(tǒng)來執(zhí)行這項業(yè)務(wù),這一點是不會改變的。

3.業(yè)務(wù)實體

  • 業(yè)務(wù)實體這個概念中應(yīng)該只有業(yè)務(wù)邏輯,沒有別的。
  • 業(yè)務(wù)實體這個概念只要求我們將關(guān)鍵業(yè)務(wù)數(shù)據(jù)和關(guān)鍵業(yè)務(wù)邏輯綁定在一個獨立的軟件模塊內(nèi)。
  • 業(yè)務(wù)實體不一定是類

4.用例(usecase)

用例和業(yè)務(wù)實體應(yīng)該是應(yīng)用當(dāng)中最重要的,所以我們的單元測試最低的要求就是要覆蓋所有的 usecase 邏輯,這一部分應(yīng)該保持純凈不依賴數(shù)據(jù)庫,Web 等 I/O 方式

  • 用例本質(zhì)上就是關(guān)于如何操作一個自動化系統(tǒng)的描述,它定義了用戶需要提供的輸入數(shù)據(jù)、用戶應(yīng)該得到的輸出信息以及產(chǎn)生輸出所應(yīng)該采取的處理步驟。
  • 用例中包含了對如何調(diào)用業(yè)務(wù)實體中的關(guān)鍵業(yè)務(wù)邏輯的定義。簡而言之,用例控制著業(yè)務(wù)實體之間的交互方式。
  • 用例除非正式地描述了數(shù)據(jù)流入/流出接口以外,并不詳細(xì)描述用戶界面。
  • 用例并不描述系統(tǒng)與用戶之間的接口,它只描述該應(yīng)用在某些特定情景下的業(yè)務(wù)邏輯,這些業(yè)務(wù)邏輯所規(guī)范的是用戶與業(yè)務(wù)實體之間的交互方式,它與數(shù)據(jù)流入/流出系統(tǒng)的方式無關(guān)。
  • 業(yè)務(wù)實體并不會知道是哪個業(yè)務(wù)用例在控制它們,這也是依賴反轉(zhuǎn)原則(DIP)的另一個應(yīng)用情景
  • 為什么業(yè)務(wù)實體屬于高層概念,而用例屬于低層概念呢?因為用例描述的是一個特定的應(yīng)用情景,這樣一來,用例必然會更靠近系統(tǒng)的輸入和輸出。

5.選擇直接在數(shù)據(jù)結(jié)構(gòu)中使用對業(yè)務(wù)實體對象的引用。畢竟,業(yè)務(wù)實體與請求/響應(yīng)模型之間有很多相同的數(shù)據(jù)。但請一定不要這樣做!這兩個對象存在的意義是非常、非常不一樣的。隨著時間的推移,這兩個對象會以不同的原因、不同的速率發(fā)生變更。

6.這些業(yè)務(wù)邏輯應(yīng)該保持純凈,不要摻雜用戶界面或者所使用的數(shù)據(jù)庫相關(guān)的東西。在理想情況下,這部分代表業(yè)務(wù)邏輯的代碼應(yīng)該是整個系統(tǒng)的核心,其他低層概念的實現(xiàn)應(yīng)該以插件形式接入系統(tǒng)中。業(yè)務(wù)邏輯應(yīng)該是系統(tǒng)中最獨立、復(fù)用性最高的代碼。

再次強調(diào)了不要偷懶,今天剛好看到之前寫的一個反面例子的代碼,代碼里面有一個 GetA 函數(shù),從數(shù)據(jù)庫當(dāng)中獲取A對象數(shù)據(jù)和一些統(tǒng)計數(shù)據(jù),這個函數(shù)中的統(tǒng)計數(shù)據(jù)部分其實只有在一個 Web 頁面的接口中使用到,但是為了偷懶,在其他地方查詢的時候也調(diào)用了這個函數(shù),導(dǎo)致最后很多地方的接口性能都由于這個沒用的統(tǒng)計數(shù)據(jù)多耗費了將近 1s 的時間。

第 21 章 尖叫的軟件架構(gòu)

1.架構(gòu)設(shè)計的主題

  • 軟件的系統(tǒng)架構(gòu)應(yīng)該為該系統(tǒng)的用例提供支持。這就像住宅和圖書館的建筑計劃滿篇都在非常明顯地凸顯這些建筑的用例一樣,軟件系統(tǒng)的架構(gòu)設(shè)計圖也應(yīng)該非常明確地凸顯該應(yīng)用程序會有哪些用例

2.架構(gòu)設(shè)計的核心目標(biāo)

  • 一個良好的架構(gòu)設(shè)計應(yīng)該圍繞著用例來展開,這樣的架構(gòu)設(shè)計可以在脫離框架、工具以及使用環(huán)境的情況下完整地描述用例
  • 良好的架構(gòu)設(shè)計應(yīng)該盡可能地允許用戶推遲和延后決定采用什么框架、數(shù)據(jù)庫、Web 服務(wù)以及其他與環(huán)境相關(guān)的工具
  • 良好的架構(gòu)設(shè)計應(yīng)該只關(guān)注用例,并能將它們與其他的周邊因素隔離。

3.可測試的架構(gòu)設(shè)計

  • 我們在運行測試的時候不應(yīng)該運行 Web 服務(wù),也不應(yīng)該需要連接數(shù)據(jù)庫。我們測試的應(yīng)該只是一個簡單的業(yè)務(wù)實體對象,沒有任何與框架、數(shù)據(jù)庫相關(guān)的依賴關(guān)系。

4.一個系統(tǒng)的架構(gòu)應(yīng)該著重于展示系統(tǒng)本身的設(shè)計,而并非該系統(tǒng)所使用的框架

用例是架構(gòu)設(shè)計當(dāng)中最應(yīng)該關(guān)注的部分,框架數(shù)據(jù)庫Web服務(wù)的選擇都是細(xì)節(jié),這些細(xì)節(jié)應(yīng)該延后選擇,我們的用例不應(yīng)該依賴這些細(xì)節(jié),這樣才能很好的測試

第 22 章 整潔架構(gòu)

1.按照不同關(guān)注點對軟件進(jìn)行切割。也就是說,這些架構(gòu)都會將軟件切割成不同的層,至少有一層是只包含該軟件的業(yè)務(wù)邏輯的,而用戶接口、系統(tǒng)接口則屬于其他層。

2.特點

  • 獨立于框架:這些系統(tǒng)的架構(gòu)并不依賴某個功能豐富的框架之中的某個函數(shù)。
  • 可被測試:這些系統(tǒng)的業(yè)務(wù)邏輯可以脫離 UI、數(shù)據(jù)庫、Web 服務(wù)以及其他的外部元素來進(jìn)行測試。
  • 獨立于 UI:這些系統(tǒng)的 UI 變更起來很容易,不需要修改其他的系統(tǒng)部分。
  • 獨立于數(shù)據(jù)庫:我們可以輕易將這些系統(tǒng)使用的
  • 獨立于任何外部機構(gòu):這些系統(tǒng)的業(yè)務(wù)邏輯并不需要知道任何其他外部接口的存在。

image.png

1.依賴關(guān)系規(guī)則

  • 外層圓代表的是機制,內(nèi)層圓代表的是策略。
  • 源碼中的依賴關(guān)系必須只指向同心圓的內(nèi)層,即由低層機制指向高層策略。
  • 外層圓中使用的數(shù)據(jù)格式也不應(yīng)該被內(nèi)層圓中的代碼所使用,尤其是當(dāng)數(shù)據(jù)格式是由外層圓的框架所生成時。

2.業(yè)務(wù)實體

  • 業(yè)務(wù)實體這一層中封裝的是整個系統(tǒng)的關(guān)鍵業(yè)務(wù)邏輯,一個業(yè)務(wù)實體既可以是一個帶有方法的對象,也可以是一組數(shù)據(jù)結(jié)構(gòu)和函數(shù)的集合。

3.用例

  • 軟件的用例層中通常包含的是特定應(yīng)用場景下的業(yè)務(wù)邏輯,這里面封裝并實現(xiàn)了整個系統(tǒng)的所有用例。這些用例引導(dǎo)了數(shù)據(jù)在業(yè)務(wù)實體之間的流入/流出,并指揮著業(yè)務(wù)實體利用其中的關(guān)鍵業(yè)務(wù)邏輯來實現(xiàn)用例的設(shè)計目標(biāo)。

4.接口適配器

  • 軟件的接口適配器層中通常是一組數(shù)據(jù)轉(zhuǎn)換器,它們負(fù)責(zé)將數(shù)據(jù)從對用例和業(yè)務(wù)實體而言最方便操作的格式,轉(zhuǎn)化成外部系統(tǒng)(譬如數(shù)據(jù)庫以及 Web)最方便操作的格式。

5.層次越往內(nèi),其抽象和策略的層次越高,同時軟件的抽象程度就越高,其包含的高層策略就越多。最內(nèi)層的圓中包含的是最通用、最高層的策略,最外層的圓包含的是最具體的實現(xiàn)細(xì)節(jié)。

6.這里最重要的是這個跨邊界傳輸?shù)膶ο髴?yīng)該有一個獨立、簡單的數(shù)據(jù)結(jié)構(gòu)。

7.「不要投機取巧地直接傳遞業(yè)務(wù)實體或數(shù)據(jù)庫記錄對象。」

看過前面的部分再來看整潔架構(gòu)這一章節(jié)會發(fā)現(xiàn)非常的自然

第 23 章 展示器和謙卑對象

1.謙卑對象模式

  • 謙卑對象模式最初的設(shè)計目的是幫助單元測試的編寫者區(qū)分容易測試的行為與難以測試的行為,并將它們隔離。

2.展示器與視圖

  • 視圖部分屬于難以測試的謙卑對象。這種對象的代碼通常應(yīng)該越簡單越好,它只應(yīng)負(fù)責(zé)將數(shù)據(jù)填充到 GUI 上,而不應(yīng)該對數(shù)據(jù)進(jìn)行任何處理。
  • 展示器則是可測試的對象。展示器的工作是負(fù)責(zé)從應(yīng)用程序中接收數(shù)據(jù),然后按視圖的需要將這些數(shù)據(jù)格式化,以便視圖將其呈現(xiàn)在屏幕上。
  • 展示器則是可測試的對象。展示器的工作是負(fù)責(zé)從應(yīng)用程序中接收數(shù)據(jù),然后按視圖的需要將這些數(shù)據(jù)格式化,以便視圖將其呈現(xiàn)在屏幕上。
  • 視圖部分除了加載視圖模型所需要的值,不應(yīng)該再做任何其他事情。因此,我們才能說視圖是謙卑對象

3.數(shù)據(jù)庫網(wǎng)關(guān)

  • 這些實現(xiàn)也應(yīng)該都屬于謙卑對象,它們應(yīng)該只利用 SQL 或其他數(shù)據(jù)庫提供的接口來訪問所需要的數(shù)據(jù)。
  • 交互器盡管不屬于謙卑對象,卻是可測試的,

4.數(shù)據(jù)映射器(ORM)

這樣的 ORM 系統(tǒng)應(yīng)該屬于系統(tǒng)架構(gòu)中的哪一層呢?當(dāng)然是數(shù)據(jù)庫層。ORM 其實就是在數(shù)據(jù)庫和數(shù)據(jù)庫網(wǎng)關(guān)接口之間構(gòu)建了另一種謙卑對象的邊界。

5.因為跨邊界的通信肯定需要用到某種簡單的數(shù)據(jù)結(jié)構(gòu),而邊界會自然而然地將系統(tǒng)分割成難以測試的部分與容易測試的部分,所以通過在系統(tǒng)的邊界處運用謙卑對象模式,我們可以大幅地提高整個系統(tǒng)的可測試性。

這里主要是將很難進(jìn)行單元測試的行為和容易測試的行為進(jìn)行分離,很難被測試的行為常常會被分離成為一個謙卑對象,這個對象非常的簡單,不會包含很多邏輯

第 24 章 不完全邊界

1.構(gòu)建不完全邊界的方式

架構(gòu)是需要取舍的,我們不可能每一項都做的很完美,邊界的劃分也是這樣,所以就有了不完全的邊界

  • 構(gòu)建不完全邊界的一種方式就是在將系統(tǒng)分割成一系列可以獨立編譯、獨立部署的組件之后,再把它們構(gòu)建成一個組件。
  • 單向邊界
  • 門戶模式

第 25 章 層次與邊界

1.「過度的工程設(shè)計往往比工程設(shè)計不足還要糟糕」

2.現(xiàn)實就是這樣。作為軟件架構(gòu)師,我們必須有一點未卜先知的能力。有時候要依靠猜測——當(dāng)然還要用點腦子。軟件架構(gòu)師必須仔細(xì)權(quán)衡成本,決定哪里需要設(shè)計架構(gòu)邊界,以及這些地方需要的是完整的邊界,還是不完全的邊界,還是可以忽略的邊界

3.架構(gòu)師必須持續(xù)觀察系統(tǒng)的演進(jìn),時刻注意哪里可能需要設(shè)計邊界,然后仔細(xì)觀察這些地方會由于不存在邊界而出現(xiàn)哪些問題。

不要過度優(yōu)化,但是也不要什么都不管的一把梭,架構(gòu)師需要演進(jìn)和取舍的,沒有完美的架構(gòu)只有不斷持續(xù)演進(jìn)優(yōu)化的架構(gòu)。

第 26 章 Main 組件

  • Main 是最細(xì)節(jié)化的部分
  • Main 組件的任務(wù)是創(chuàng)建所有的工廠類、策略類以及其他的全局設(shè)施,并最終將系統(tǒng)的控制權(quán)轉(zhuǎn)交給最高抽象層的代碼來處理。
  • Main 組件中的依賴關(guān)系通常應(yīng)該由依賴注入框架來注入。
  • 我們在這里的重點是要說明 Main 組件是整個系統(tǒng)中的一個底層模塊,它處于整潔架構(gòu)的最外圈,主要負(fù)責(zé)為系統(tǒng)加載所有必要的信息,然后再將控制權(quán)轉(zhuǎn)交回系統(tǒng)的高層組件。

main 是一個程序的入口,這是最細(xì)節(jié)的部分,因為之前為了很多東西不被依賴,我們一般會采用接口來實現(xiàn)依賴反轉(zhuǎn),這時候就會導(dǎo)致我們所有的依賴關(guān)系的構(gòu)建都需要在 main 中進(jìn)行完成,所以一般而言我們會在 main 中引入依賴注入框架。

第 27 章 服務(wù):宏觀與微觀

1.所謂的服務(wù)本身只是一種比函數(shù)調(diào)用方式成本稍高的,分割應(yīng)用程序行為的一種形式,與系統(tǒng)架構(gòu)無關(guān)。

2.服務(wù)所帶來的好處?

  • 解耦合的謬論
  • 獨立開發(fā)部署的謬論
  • 這種理念有一些道理——但也僅僅是一些而已。首先,**無數(shù)歷史事實證明,大型系統(tǒng)一樣可以采用單體模式,或者組件模式來構(gòu)建,不一定非得服務(wù)化。**因此服務(wù)化并不是構(gòu)建大型系統(tǒng)的唯一選擇。

3.橫跨型變更(cross-cutting concern)問題,它是所有的軟件系統(tǒng)都要面對的問題,無論服務(wù)化還是非服務(wù)化的。

4.服務(wù)也可以按照 SOLID 原則來設(shè)計,按照組件結(jié)構(gòu)來部署,這樣就可以做到在添加/刪除組件時不影響服務(wù)中的其他組件。

5.系統(tǒng)的架構(gòu)邊界事實上并不落在服務(wù)之間,而是穿透所有服務(wù),在服務(wù)內(nèi)部以組件的形式存在

6.「服務(wù)邊界并不能代表系統(tǒng)的架構(gòu)邊界,服務(wù)內(nèi)部的組件邊界才是?!?/p>

7.系統(tǒng)的架構(gòu)是由系統(tǒng)內(nèi)部的架構(gòu)邊界,以及邊界之間的依賴關(guān)系所定義的,與系統(tǒng)中各組件之間的調(diào)用和通信方式無關(guān)。

雖然現(xiàn)在微服務(wù)架構(gòu)非?;馃幔旧纤械姆?wù)都是拆分了服務(wù),但是拆分了服務(wù)并不一定表示就解耦合了,也并不一定就真的能獨立部署,想一想這是現(xiàn)在很常見的,一個應(yīng)用必須要和另外一個應(yīng)用一同上線,根本做不了獨立部署。

第 28 章 測試邊界

  • 可測試性設(shè)計
  • 如果測試代碼與系統(tǒng)是強耦合的,它就得隨著系統(tǒng)變更而變更。哪怕只是系統(tǒng)中組件的一點小變化,都可能會導(dǎo)致許多與之相耦合的測試出現(xiàn)問題,需要做出相應(yīng)的變更。
  • 軟件設(shè)計的第一條原則——不管是為了可測試性還是其他什么東西——是不變的,就是不要依賴于多變的東西。
  • 沒有按系統(tǒng)組成部分來設(shè)計的測試代碼,往往是非常脆弱且難以維護的。

不變的組件不要依賴多變的東西,這樣會導(dǎo)致非常難以測試

第 29 章 整潔的嵌入式架構(gòu)

1.雖然軟件本身并不會隨時間推移而磨損,但硬件及其固件卻會隨時間推移而過時,隨即也需要對軟件做相應(yīng)改動

2.雖然軟件質(zhì)量本身并不會隨時間推移而損耗,但是未妥善管理的硬件依賴和固件依賴卻是軟件的頭號殺手。

3.但如果你在代碼中嵌入了 SQL 或者是代碼中引入了對某個平臺的依賴的話,其實就是在寫固件代碼。

4.軟件構(gòu)建過程中的三個階段

  • “先讓代碼工作起來”——如果代碼不能工作,就不能產(chǎn)生價值。
  • “然后再試圖將它變好”——通過對代碼進(jìn)行重構(gòu),讓我們自己和其他人更好地理解代碼,并能按照需求不斷地修改代碼
  • “最后再試著讓它運行得更快”——按照性能提升的“需求”來重構(gòu)代碼。

5.整潔的嵌入式架構(gòu)就是可測試的嵌入式架構(gòu)

6.軟件與固件集成在一起也屬于設(shè)計上的反模式(anti-pattern)

軟件并不會隨著時間磨損但是硬件是會過時的,而且換的還非常頻繁,這時候我們就必須要把硬件以及固件代碼給隔離起來,對了不要認(rèn)為我們不做嵌入式開發(fā)平時就很少接觸到這個,SQL 語句其實也是一種固件代碼

第六部門 實現(xiàn)細(xì)節(jié)

第 30 章 數(shù)據(jù)庫只是實現(xiàn)細(xì)節(jié)

  • 就數(shù)據(jù)庫與整個系統(tǒng)架構(gòu)的關(guān)系打個比方,它們之間就好比是門把手和整個房屋架構(gòu)的關(guān)系
  • 但當(dāng)問題涉及數(shù)據(jù)存儲時,這方面的操作通常是被封裝起來,隔離在業(yè)務(wù)邏輯之外的
  • 數(shù)據(jù)本身很重要,但數(shù)據(jù)庫系統(tǒng)僅僅是一個實現(xiàn)細(xì)節(jié)。

數(shù)據(jù)很重要,但是數(shù)據(jù)庫系統(tǒng)是一個細(xì)節(jié),書上這一章用了一個例子說明有時候可能真的用不到數(shù)據(jù)庫。換個常見的例子,我們可能系統(tǒng)剛開始的時候使用 SQlite 就可以,隨著業(yè)務(wù)發(fā)展用上了 MySQL,然后隨著并發(fā)的提高又會引入緩存組件,這些變化其實和業(yè)務(wù)邏輯都沒有關(guān)系,數(shù)據(jù)庫的變化是不應(yīng)該影響到業(yè)務(wù)邏輯的

第 31 章 Web 是實現(xiàn)細(xì)節(jié)

  • GUI 只是一個實現(xiàn)細(xì)節(jié)。而 Web 則是 GUI 的一種,所以也是一個實現(xiàn)細(xì)節(jié)。作為一名軟件架構(gòu)師,我們需要將這類細(xì)節(jié)與核心業(yè)務(wù)邏輯隔離開來。

第 32 章 應(yīng)用程序框架是實現(xiàn)細(xì)節(jié)

  • 我們可以使用框架——但要時刻警惕,別被它拖住
  • 畢竟 Main 組件作為系統(tǒng)架構(gòu)中最低層、依賴最多的組件,它依賴于 Spring 并不是問題。

框架的選擇要慎重,我們業(yè)務(wù)邏輯本身不能依賴框架

第 33 章 案例分析:視頻銷售網(wǎng)站

  • 系統(tǒng)架構(gòu)設(shè)計中的第一步,是識別系統(tǒng)中的各種角色和用例

這一步看起來簡單,但是非??简炓粋€人的功力

第 34 章 拾遺

  • 分層架構(gòu)無法展現(xiàn)具體的業(yè)務(wù)領(lǐng)域信息。把兩個不同業(yè)務(wù)領(lǐng)域的、但是都采用了分層架構(gòu)的代碼進(jìn)行對比,你會發(fā)現(xiàn)它們的相似程度極高
  • 寬松的分層架構(gòu),允許某些層跳過直接相鄰的鄰居。
  • 一個架構(gòu)設(shè)計原則——內(nèi)容是“Web 控制器永遠(yuǎn)不應(yīng)該直接訪問數(shù)據(jù)層”。
  • 系統(tǒng)由一個或者多個容器組成(例如 Web 應(yīng)用、移動 App、獨立應(yīng)用、數(shù)據(jù)庫、文件系統(tǒng)),每個容器包含一個或多個組件,每個組件由一個或多個類組成。
  • 如果不考慮具體實現(xiàn)細(xì)節(jié),再好的設(shè)計也無法長久。必須要將設(shè)計映射到對應(yīng)的代碼結(jié)構(gòu)上,考慮如何組織代碼樹,以及在編譯期和運行期采用哪種解耦合的模式。
  • 最好能利用編譯器來維護所選的系統(tǒng)架構(gòu)設(shè)計風(fēng)格,小心防范來自其他地方的耦合模式,例如數(shù)據(jù)結(jié)構(gòu)

image.png

這一章對比了四種架構(gòu)風(fēng)格,同時提出了,架構(gòu)設(shè)計是需要考慮實現(xiàn)細(xì)節(jié)的,設(shè)計需要映射到代碼結(jié)構(gòu)和代碼樹上,這個其實和最開始的“軟件架構(gòu)師自身需要是程序員,并且必須一直堅持做一線程序員”交相呼應(yīng)。如果可以在編譯時解決的問題,就不要放到運行時,編譯的問題往往要比運行時的問題好解決,這也是為什么 Go 的依賴注入框架我更加推薦 wire 的原因,同理作者提出了 如果要防止直接中 web控制器調(diào)用數(shù)據(jù)層,那么我們就不應(yīng)該將數(shù)據(jù)層(repo)暴露出來,只需要暴露 usecase 就好了。

總結(jié)

之前其實也大概了解過整潔架構(gòu),從最開始覺得它又臭又長,到現(xiàn)在工作兩三年后覺得“不聽老人言,吃虧在眼前”,當(dāng)我們在對一個架構(gòu)或者是事務(wù)進(jìn)行批判的時候一定要了解它面對的場景以及它的理念,這是最重要的。當(dāng)然軟件領(lǐng)域是沒有銀彈的,我們需要做的是吸收每一種思想,在不同的場景下做不同的取舍,接下來會有幾篇文章結(jié)合毛老師課上講的 Go 工程化相關(guān)的內(nèi)容,以及我在工作當(dāng)中進(jìn)行的一些總結(jié)最后提出一種當(dāng)下我覺得的 Go 項目的組織方式,這種方式不是最好的,但是我覺得是現(xiàn)階段最適合的。推薦大家在仔細(xì)的閱讀一下本書,期望你能有更多的收獲。

參考文獻(xiàn)架構(gòu)整潔之道-羅伯特·C·馬丁-微信讀書

文章來源博客地址: https://lailin.xyz 

 

責(zé)任編輯:姜華 來源: mohuishou
相關(guān)推薦

2021-12-24 09:00:43

Go語言進(jìn)程

2012-08-01 09:38:17

代碼整潔

2022-04-18 09:41:14

Go架構(gòu)設(shè)計

2019-05-14 09:31:16

架構(gòu)整潔軟件編程范式

2012-08-01 09:23:31

代碼

2021-01-06 14:42:09

前端Typescript代碼

2023-09-15 10:33:45

前端工程化commit

2021-12-27 08:27:18

RepoGo 代碼

2021-03-07 09:19:31

React代碼整潔代碼的實踐

2022-12-01 07:46:01

工程化工具

2021-11-22 06:17:26

npm工程化工具

2021-05-18 19:18:50

前端工程化工程

2023-09-28 08:34:26

Docker微服務(wù)

2023-12-25 09:49:01

Golang架構(gòu)Go-Kit

2020-12-09 10:49:33

代碼開發(fā)GitHub

2020-02-29 16:00:20

代碼開發(fā)程序員

2022-08-17 11:33:35

前端配置

2021-06-05 18:01:05

工具Rollup前端

2025-03-26 03:20:00

2022-07-26 17:19:11

前端前端工程化
點贊
收藏

51CTO技術(shù)棧公眾號