--- tags: 學習筆記, 架構 --- [無瑕的程式碼:整潔的軟體設計與架構篇](https://www.books.com.tw/products/0010786994) === ## 資料科學:可證偽的學科 ## 程式設計的架構 * 結構化程式:在直接的控制移轉加上規範 * 使用if else while作流程控制 * **不再使用goto** * 架構上的關聯:資料管理 * 所有模組都可以被遞迴的分解成可證偽的獨立單元 * 只要測試無法證明這些獨立單元不正確,那這些獨立單元對我們的目的來說就有足夠的正確性 * 物件導向程式:在間接的控制移轉加上規範 * 將函式的區域變數、內部的各個區段程式包裝起來變成member、method * **只允許函式指標指向相同或是相似的物件(多型)** * 架構上的關聯:元件分離 * 多型的好處:去耦合:無論在何處,程式碼的依賴都可以被反向 * 控制程式碼的依賴方向 * 可獨立部署性:元件中的程式碼變化可以指重新部署該元件-->變更容易 * 可獨立開發性:系統中的模組可獨立部署-->可由不同團隊獨立開發 * 將策略與細節獨立開來(高低層級、粒度) * 函數式程式 * 值的不變性 * **不允許直接改變變數的值或是限制的非常嚴格** * 架構上的關聯:函式 * 所有的競爭、死結、平行更新問題都是因為變數的可變性 * 在不可變的架構上,我們紀錄的不是狀態,而是交易事件的相關資訊,當前狀態是透過所有交易事件的迭代結果呈現 | 交易描述 | A存款狀態 | 交易事件 | | --------------- | --------- | -------------- | | A存款共有500元 | 500 | 500 | | A轉帳給B:100元 | 400 | 500-100 | | A轉帳給C:300元 | 100 | 500-100-300 | | D轉帳給A:50元 | 150 | 500-100-300+50 | 例子舉得不是很好,但可以一併考慮一下交易併發發生時,如果轉出100跟轉出300同時發生在CRUD的模式下會發生的問題(如果沒有合理的Transaction控制),以及如果只記錄交易事件的影響 | 交易描述 | A存款狀態 | 交易事件 | | -------------------------------- | --------- | ------------------------- | | A存款共有500元 | 500 | 500 | | A轉帳給B:100元、A轉帳給C:300元 | 400? 200? | 500-100-300? 500-300-100? | | D轉帳給A:50元 | 350? 250? | 500-100-300+50? 500-300-100+50 | * [事件來源模式](https://docs.microsoft.com/zh-tw/azure/architecture/patterns/event-sourcing) * 由於儲存空間和運算資源的限制,我們無法讓所有程式所有系統都以不可變元件組成,折衷的選擇是區分出可變與不可變的系統 ## 設計原則SOLID * [SRP](https://zh.wikipedia.org/wiki/%E5%8D%95%E4%B8%80%E5%8A%9F%E8%83%BD%E5%8E%9F%E5%88%99):單一職責原則(Single responsibility principle) * 每個模組應只有唯一一個**理由**需要改變 * 一個模組應該只對唯一一個角色(使用者或利益相關者)負責 * 意外重複: * 一個物件有多個方法,這些方法可能個別只有一個使用者,但是整體來說是多個使用者共用這個物件: * 計算薪水(會計處的需求) * 計算人力工時(人資處的需求) * 假設一開始計算薪水與計算人力工時共用了一個private method 計算加班時數 * 若某一天人資處針對計算加班時數的算法有所調整,而會計處仍使用原先方式,災難就會發生 * 程式碼衝突: * 因為負責對象不同,導致可能會有同時修改同份程式碼的問題 * 除了開發面上的程式碼衝突,我們更難去確認利用工具解決完程式碼衝突後,這份程式實際運行起來,業務情境是否衝突 * 開發上的解決方案:[Facade模式](https://openhome.cc/Gossip/DesignPattern/FacadePattern.htm) * 架構層面SRP決定了架構邊界的變化軸 * [OCP](https://zh.wikipedia.org/wiki/%E5%BC%80%E9%97%AD%E5%8E%9F%E5%88%99):開放封閉原則(Open-Close principle) * 為了易於更改,應該設計成允許透過增加新程式碼而非更改現有程式碼來更動系統行為:**一個軟體製品應該對於擴展是開放的,但對於修改是封閉的** * 應該決定出誰應該被保護:A應該被保護免於受到B改變的影響,那麼B則應該依賴於A * 較高層級的元件(資料運算的業務邏輯)應免受較低層級元件變更的影響(資料呈現的邏輯) * [LSP](https://zh.wikipedia.org/wiki/%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99):替換原則(Liskov substitution principle) * 子系統應該可以互相交換 * 指導繼承的使用(繼承關係的重點應該在於行為) * 重點在於錯誤的繼承關係會對使用這個父物件的人產生影響,他必須去考慮不同子物件的特性,這就不是繼承了 * [正方形矩形問題](https://wadehuanglearning.blogspot.com/2017/08/php-oo-lsp.html) * 在架構上LSP是為了避免違反可替代性後,系統不得不產生大量額外機制 * [ISP](https://zh.wikipedia.org/wiki/%E6%8E%A5%E5%8F%A3%E9%9A%94%E7%A6%BB%E5%8E%9F%E5%88%99):介面隔離原則(Interface segregation principle) * 不應該依賴到無需使用的東西 * 你所依賴的模組超過你所需要的就是有問題的 * 程式上這意味著不必要的重新編譯與部署 * 架構上這同樣意味著不必要的部署,更糟的是如果其中一個環節部署失敗,也會導致更多的失敗 * [DIP](https://zh.wikipedia.org/wiki/%E4%BE%9D%E8%B5%96%E5%8F%8D%E8%BD%AC%E5%8E%9F%E5%88%99):依賴反向原則(Dependency inversion principle) * 細節依賴於策略而非策略依賴於細節(實作邏輯應該依賴於抽象邏輯) * 最靈活的系統是原始碼的依賴關係只涉及抽象不涉及具體 * 實際上來說想要避免依賴具體元素(String)是很難的,因此想要避免的是依賴**容易變化**的具體元素 * 不要參考易變的具體類別 * 不要從易變的具體類別衍生 * 不要改寫具體函式 * [依賴反轉跟工廠模式的關係](https://kknews.cc/zh-tw/tech/4breqlx.html) ## [元件內聚性](https://medium.com/@f40507777/%E5%85%83%E4%BB%B6%E5%85%A7%E8%81%9A%E6%80%A7-1a1c334fc3f7) * REP:再使用性(The Reuse/Release Equivalence Principle) * 再使用性的細微度就是發佈的細微度 * 良好的模組管理工具是有必要的 * 應該要能管理好使用到元件的版本 * CCP:共同封閉原則(The Common Closure Principle) * 將那些會因相同理由、在相同時間發生變化的類別收集到相同的元件之中 * 將那些在不同時間因不同理由發生變化的類別分割到不同的元件之中 * 元件版本的[SOLID#SRP](#設計原則SOLID) * CRP:共同重複使用原則(The Common Reuse Principle) * 不要強迫元件的使用者依賴他們不需要的東西 * 元件版本的[SOLID#ISP](#設計原則SOLID) * 這三個原則與CAP定理有點類似,是互相拉扯的關係:隨著專案開發時期的推移,著重點是有所不同的 * REP、CCP:太多不必要的版本 * CCP、CRP:難以重用 * CRP、REP:有過多的元件變更 ## 元件耦合性 * ADP:無環依賴原則(Acyclic Dependencies Principle) * 在元件的依賴關係途中不允許出現環 * 如果依賴循環就會導致工作之間的互相影響 * 將這些環之間的依賴拉出成元件(Library),而非在原始程式碼中就直接互相影響 * 利用[DIP](#設計原則SOLID)將依賴關係反轉,把原本依賴關係的環轉成有向無環 * 產生一個新的被雙方同時依賴的元件[Adapter Pattern](https://github.com/QianMo/Unity-Design-Pattern/tree/master/Assets/Structural%20Patterns/Adapter%20Pattern) * 這些方法最終都會讓**依賴關係不斷地被延長** * 元件的依賴關係跟功能關係不能混為一談,元件的依賴關係實際上應該是可建置性與可維護性 * SDP:穩定依賴原則(Stable Dependencies Principle) * 朝著穩定的方向進行依賴 * 被越多元件依賴就越穩定,因為許多元件需要使用到這個元件就意味著具有更大的責任,會影響到很多依賴於他的元件,因此變動也就越加慎重 * I不穩定性:I=Fan-out/(Fan-in + Fan-out) * Fan-out:依賴的元件數 * Fan-in:被依賴的元件數 * 依賴關係應該指向I越小的方向 * 但一個系統中若所有元件都是穩定的,也就意味著系統無法異動。因此,良好的狀態應該是一部分選定的元件穩定、另一部分元件則不穩定,減少介於兩者之間的元件 * SAP:穩定抽象原則(Stable Abstractions Principle) * 元件的抽象程度應該與元件的穩定程度一致 * 抽象程度的計算可以視為元件中類別的總數與抽象類別、介面的比值 * 抽象性(A)=(抽象類別、介面)總數/類別總數 * 根據抽象性與不穩定性可以畫出一個二維座標軸(X軸為I、Y軸為A) * 優良的元件 * (0,1):穩定而抽象 * (1,0):不穩定而具體 * 高度穩定且具體(0,0) * 僵化而無法擴展如DB的Schema * 不穩定且抽象(1,1) * 無用的介面:沒有人依賴於他的抽象介面 ## 架構 * 邊界 * 透過將各個策略決策之間畫上邊界,將有助於建立出一個符合上述所有提到的優良軟體所應該具備特性的軟體 * 邊界是為了避免不同層級的策略互相干擾,以便更專注地思考如何才能更好 * 所有決策都應該盡量抽象而非具體 * 使用什麼技術、資料儲存方式、輸入輸出應該怎麼處理都不是架構應該關心的事情 * 越早決定這些細節,會將架構限制的越死也越難變更 * 利用各種抽象介面可以很好的先去處理關鍵功能的建立 * 最後在將具體應該要使用的持久層、呈現方式等決定 * 業務邏輯 * 核心業務邏輯:無關軟體技術,不論是否在電腦上執行,他都可以為企業賺取或節省商業資金。 * 核心邏輯:對於業務最重要的邏輯 * 核心資料:這組邏輯會需要取用的資料 * 這類邏輯最為單純,不會因為其他任何因素而影響(不管在電腦上、手機上、員工手動計算)都是一樣的結果,發生改變時也會一起被改變 * 業務使用情境:在軟體上才會有意義的邏輯 * 定義和規範自動化流程的方式,諸如畫面必須等待哪些東西被填寫完畢才能執行下一步 * 描述的是應用程式特定的業務邏輯(使用者希望的) * 但使用情境不包含資料應該如何被呈現 * 這些業務邏輯應該單純,不受使用者介面、資料庫等問題影響 * 最獨立最可重複利用的程式 * 良好架構的特徵 * 獨立於框架:框架只是工具,不應該被框架侷限 * 可測試:業務邏輯在沒有UI、資料庫等任何外部的情況下也應該可以被測試 * 獨立於UI:在資料流完好的情況下UI可以很容易地被調整 * 獨立於資料庫:用檔案用SQL用NOSQL都可以良好的將資料保存與取出,業務邏輯應該不被綁定在資料庫中 * 獨立於任何外部代理介面 * 依賴規則 由外而內的依賴,依賴關係只能指向內部,朝向更高層級的策略 由外至內依序如下: * Web、裝置、UI、DB、外部介面 * 介面轉接層:將資料從使用案例和核心邏輯的形式轉換為適合於外部代理的格式(DB、Web) * 使用情境層 * 核心業務邏輯層
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up