目標 用最少的人力成本滿足構建和維護該系統的需求 目標 衡量指標 版本迭代 -- 工程師團隊規模 版本迭代 -- 代碼總行數 版本迭代 -- 代碼變更行數 衡量指標 軟體系統的價值 行為價值 按需求文檔編寫代碼 可用性 功能性bug 性能 穩定性 緊急,但是並不總是重要,在緊急重要矩陣中占據A、C位 ...
目標
用最少的人力成本滿足構建和維護該系統的需求
衡量指標
版本迭代 -- 工程師團隊規模
版本迭代 -- 代碼總行數
版本迭代 -- 代碼變更行數
軟體系統的價值
行為價值
按需求文檔編寫代碼
可用性
功能性bug
性能
穩定性
緊急,但是並不總是重要,在緊急重要矩陣中占據A、C位置
架構價值
Soft :當需求變更時,所需的軟體變更必須簡單方便
變更實施的難道應該和變更的範疇(scope)成等比,而與變更的具體形狀(shape)無關
不緊急,占據B、D,D的工作就是過度設計,過度設計會提升開發和維護成本
編程範式
目的 :設置限制,告訴我們不可以做什麼
現有範式 :
結構化編程 :
目的 :對控制權的直接轉移進行了限制和規範
內容 :可以用順序介面、分支結構、迴圈結構這三種結構構造出任何結構。限制goto的使用
意義 :用代碼把一些已證明的結構串聯起來,就可以推導出整個程式的正確性。實際上沒有辦法證明每個程式段是正確的,只能證偽,如果所有的基本單原都無法證偽,那麼整個就是無法證偽的,那目前就是正確的。
延伸 :物理學與數學的區別,物理學的基本公式都是沒有辦法證明的,只能證偽,所以物理是實驗科學,沒有一個公式是完全靠得住的,只是目前靠得住。數據的基本公式都是可以證明的。
面向對象編程 :
目的 :對程式控制權的間接轉移進行了限制和規範
定義 :
封裝 :只暴露部分函數,數據則完全不暴露
繼承
多態 :其實只是函數指針的一種應用,通過介面和實現,抽象類和繼承,替代了函數指針的使用
意義 :函數指針,是跨越組件邊界的方法,是組件獨立部署的基礎,依賴反轉的基礎。依賴反轉指的是讓依賴與控制流向相反。
函數式編程 :
目的 :對賦值進行了限制和規範
趨勢 :如果有足夠大的存儲量和計算量,應用程式可以用事件溯源的方式,用完成不可變的函數式編程,只通過事物記錄,從頭計算狀態
意義 :所有的競爭問題、死鎖問題、併發問題都是由可變變數導致的。
應用 :通過將狀態修改的部分和不需要修改的部分分隔成單獨的組件,提高系統的穩定性和效率
設計原則 :SOLID
意義 :
如何將數據和函數組織成類
如何將類鏈接起來成為組件和程式
內容 :
OCP : 開閉原則
目標 :讓系統易於擴展,同時限制每次修改所影響的範圍
實現 :劃分組件,並將組件間依賴關係按層次結構進行組織
本原則是我們進行架構設計的主導原則
SRP :單一職責原則
目標 :指導類、組件拆分
定義 :任何一個軟體模塊,都應該有且只有一個被修改的原因,“被修改的原因”指系統的用戶或所有者
痛點 :同樣的一塊邏輯,如果服務於兩個價值主體 :因為一個價值主體而修改,那麼第二個價值主體期望的功能將被影響。比如CTO和COO都要員工的工時,分別用於計算薪資和彙報,兩者的計算方式可能目前是相同的,一方有了更改,另一方就bug了
如果一塊代碼,歸屬於兩個團隊共同維護 :就會有代碼合併問題
LSP : 里氏替換原則
目標 :指導介面與實現方式(邊界處理)
內容 :不是實現了同一個介面,它們的行為就一致並可以互相替換,長方形正方形是典型的案例
如果兩個組件,替換之後需要分別做特別的設置,那就說明抽象的還不足夠,會引入許多if-else,可以同配置清單等方式消除
ISP :介面隔離原則
目標 :指導介面的定義(邊界處理)
內容 :不依賴任何不需要的組件、類、方法
如果不同的用戶分別使用一個大介面的幾個不同的方法,那麼應該把這個大介面拆分為針對這些用戶的小介面
DIP : 依賴反轉原則
目標 :指導依賴方向(依賴)
內容 :組建間跨越邊界的源碼依賴的方向永遠與控制流的方向相反
組件
定義 :是軟體的部署單元,是整個軟體系統可以獨立完成部署的最小實體
拆分三原則 :
REP :復用、發佈等同原則 -- 內容 :軟體復用的最小粒度應等同於 其發佈的最小粒度
CCP :共同閉包原則
內容 :將為了相同目的而同時修改的類放在同一個組件中,是SRP原則在組件層面的描述
執行 :對大部分應用程式而言,可維護性的重要性遠遠大於可復用性
因為一個原因需要做修改,這個修改最後在同一個組件中,如果分散在多個組件中,那麼開放、提交、部署的成本都會提升
CRP :共同復用原則
內容 :不要強迫一個組件依賴它不需要的東西,是ISP原則在組件層面的描述
張立圖 :
架構設計中有許多矛盾,研發性和復用性的矛盾,而研發性本身又有粘性(CCP)和排斥性的矛盾(CRP)
架構師做的往往是在這個張立圖中找到一個最符合現在需要的點,而這個平衡也是不斷變化的,根據項目的規模、迭代的節奏等。
依賴三原則 :
無依賴環原則 :
互相依賴的組件,實際上組成了一個大組件,這三個組件要一起發佈、一起做單元測試
通過依賴反轉原則可以解依賴環
穩定依賴原則 :
內容 :
依賴必須指向更穩定的方向,介面是最穩定的。
組件的穩定性,指的是組件的變更空難度,影響因素有很多,比如代碼的體量大小、複雜度、清晰度等,但最最重要的一個因素就是依賴的數量 -- 讓組件難於修改的一個最直接的方法就是讓很多其他組件依賴於它
定量指標 :不穩定性(|) = 出向依賴數量 / (入向依賴數量 + 出向依賴數量)
方法 :可以通過抽介面,共同依賴介面的方式,修正違反文檔依賴的地方
穩定抽象原則 :
內容 :
一個組件的抽象化程度應該與其穩定性保持一致
為了防止高階架構設計與高階策略難以修改,通常抽象出穩定的介面或抽象類。越穩定的庫就應該越抽象,這樣它的穩定性就不會影響它的擴展性
定量描述 :抽象程度(A) = 組件中抽象類和介面的數量 / 組件中類的數量
將不穩定性和抽象程度分別作為橫軸和縱軸,畫一個二維的圖,(0,1)-(1,0)連線就是主序列線。靠近(0,0)的區域是痛苦區,改動成本很大,但是又很具體。靠近(1,1)的是無用區,非常抽象,但是沒有別的組件依賴它,改動成本很小,通常是廢棄的。
離主序列先的距離D = | A + | -1 | ,可以定量化的衡量一個組件的健康程度。在D滿足期望的條件下,約靠近(0,1)和(1,0)越好
軟體架構
目的 :
終極目的 :最大化程式員的生產力,最小化系統的總運營成本
細化目的 :支撐軟體系統的全生命周期,讓系統便於理解、易於修改、方便維護、輕鬆部署
方針 :
選型指的是無關緊要的細節設計
選型例子 :
具體選用那個存儲方式,或那個資料庫
資料庫 :擅長於內容的查詢
文件 :擅長於文件的快速查找和整體讀取
如果硬碟被淘汰時,用什麼存儲系統差別不大
使用哪種web服務
使用哪種框架 :
框架的使用文檔是開發者角度寫的,他自然吹噓自己能力,希望你完全耦合他們的框架
風險 :
產品發展,框架不再滿足需求
框架本身朝著我們不需要的方向演進
未來我們可能希望遷到一個新的更好的框架上
邊界約完善,開發和部署成本越高,所以不完全邊界能解決的,不要用完全邊界,低層次解耦能解決的,不要用高層次解耦
內容 :
組件拆分 :
拆分 :
水平分層 :
一條策略距離系統的輸入、輸出越遠,它的層次越高
例子 :
UI界面
應用獨有的業務邏輯
領域普適的業務邏輯
存儲
按用例垂直切分
每個用例幾乎涉及到所有的水平分層,如何做到新加用例,不影響舊的用例
比如 :訂單,聊天
重覆 :
如果兩段代碼,看起來重覆,但是走的是不同的演進路徑,就不是真正的重覆
解耦模式 :
源碼層次 :做了介面、類依賴上的(不完全的)解耦,但是放在同一個組件中,通常放在不同的路徑下
部署層次 :任然運行在同一個機器上,彼此通過函數調用通訊
服務層次 :
運行在不同的機器上,通過url、網路數據包等方式進行通訊
服務不等同於模塊,比如橫跨型變更需要改動所有服務,但是可能並不會改動架構
從上到下,(開發、部署)成本依次升高,如果低層次的解耦已經滿足需要,不要 進行高層次的解耦
組件是一組描述如何將輸入轉化為輸出的策略語句的集合,這些策略的變更原因、時間、層次相同
組件排列(依賴):依賴關係與數據流控制流脫鉤,與組件所在層次掛鉤。所以組件的依賴是與組件的水平分層息息相關的
業務實體 :
包含關鍵業務數據和業務邏輯
與界面無關、與存儲無關、與框架無關,只有業務邏輯,沒有別的
用例 :
特定場景下的業務邏輯 :
三要素 :
需要用戶提供的輸入數據(註意解耦輸入方式,這裡只關心數據)
用戶應該得到的輸出數據(註意解耦輸出方式,這裡只關心數據)
從輸入數據到輸出數據,應該採取的處理步驟
註意 :
不要把業務實體直接當做輸入數據對象或者輸出數據對象,因為他們會以不同的原因的速率發生變更
介面適配器 :
整個MVC
對存儲、設備、界面等的介面聲明和使用
框架與驅動程式 :
因為 與硬體太相關的部分,比如用戶界面,是不可測的,所以這裡的邊界處理通常使用謙卑對象模式
謙卑對象要有自知之明,簡化到不能再簡化,不應該包含對數據的任何處理。數據處理全部放到介面適配器(比如視圖模型)中。
測試層 :
測試也是一個組件
測試關鍵之外是耦合,測試如何依賴所有的其他組件的所有介面,那測試就是脆弱的,任何改動都引起n個case失效。
解法是給測試層單獨寫一套特有的API
組件通信 :
方式 :
介面調用
服務調用
完全邊界 :
調用雙方都聲明介面
專用的輸入數據類型
專用的返回數據類型
不完全邊界
省掉最後一步
保留到源碼層次的解耦
聲明好介面,做好分割後,任然放在一個組件中。等到時機成熟時再拆出來獨立編譯部署
單向邊界 :
正常的切割,應該使用兩個介面,兩個雷各自使用對方的介面而不是直接使用類,但是這樣的開發成本很大,所以,只實現一個介面,高層用介面調用底層,而底層直接使用高層的類
門戶模式 :
控制權的間接轉移不用介面和實現去做,而是用門戶類去做,介面都不用聲明瞭。
軟體系統的聲明周期 :
開發 :
不同團隊負責的組件不交叉
不使用大量複雜的腳手架
部署 :
減少組件數量,內部組件外部組件結合的方式
不依賴成堆的腳本和配置文件
運行 :
這方面的價值對架構的影響最小
不同吞吐量、不同的響應時長要求,是架構設計要考慮的點。採用微服務?單線程?多線程?
架構應起到揭示系統運行的作用 :用例、功能、行為設置應該都對開發者可見的一級實體,以類、函數或模塊的形式占據明顯位置,
命名能清洗地描述對應的功能
維護 :
探秘成本 :
對現有軟體系統的挖掘,確定新功能或修複問題的最佳位置和方式
風險成本 :
做改動時,可能衍生出新的問題