<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>軟體架構 · Tommy Cheese</title>
    <link>https://tommycheese.github.io/zh-TW/tags/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84/</link>
    <description>Tommy Cheese 的個人部落格，記錄軟體工程、人工智慧與學習實踐。</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-TW</language>
    <lastBuildDate>Mon, 29 Jul 2024 06:13:15 +0800</lastBuildDate>
    <atom:link href="https://tommycheese.github.io/zh-TW/tags/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84/index.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>再讀整潔架構之道（六）邊界</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%85%AD/</link>
      <pubDate>Mon, 29 Jul 2024 06:13:15 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%85%AD/</guid>
      <description>&lt;h2 id="什么是边界"&gt;什麼是邊界&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;軟體架構設計本身就是一門劃分邊界的藝術。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;邊界的作用是將軟體劃分為各種元素，以便約束邊界兩側的依賴關係。在專案初期劃分的邊界可以方便我們將一些決策儘量延後進行，確保未來這些決策不會對系統額&lt;strong&gt;核心業務邏輯&lt;/strong&gt;造成干擾。劃分邊界同時可以降低或消除架構中不必要耦合，系統中存在的耦合——尤其是那些過早做出的、不成熟的決策所導致的耦合（如採用的框架，資料庫等元素，這些細節應該被推遲）是系統最消耗人力資源的部分。&lt;/p&gt;
&lt;p&gt;那麼如何劃分邊界呢？可以遵循如下的基本原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;邊界應該劃分在那些無關的事情中間：如 GUI 與業務邏輯，這意味著輸入輸出（GUI）對於業務邏輯來說並不重要，業務邏輯可以選擇多種不同的 GUI 實現；&lt;/li&gt;
&lt;li&gt;邊界線應該沿著系統的變更軸來畫。也就是說，位於邊界線兩側的元件應該以不同原因、不同速率變化著。指導變更軸心的原則包括模組/類級別的SRP原則和元件級別的CCP原則。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在瞭解劃分邊界的重要作用，並且瞭解如何劃分邊界後，可以進一步探討該如何劃分邊界了，即劃分邊界的基本流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;首先將系統分割成元件，其中一部分是系統的核心業務邏輯元件，另一部分是與核心業務無關但負責提供必要功能的外掛；&lt;/li&gt;
&lt;li&gt;通過對原始碼的修改，讓這些非核心元件依賴於系統的核心業務邏輯元件；&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;劃分邊界是一種對依賴反轉原則和穩定抽象原則的具體應用，依賴剪頭應該由底層具體實現指向高層抽象的方向。&lt;/p&gt;
&lt;h2 id="边界剖析"&gt;邊界剖析&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;一個系統的架構是由一系列軟體元件以及它們之間的邊界共同定義的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;邊界具有多種形式，本章以跨邊界呼叫為例來剖析邊界。&lt;/p&gt;
&lt;p&gt;在執行時，跨邊界呼叫指的是邊界線一側的函式呼叫另一側的函式，並同時傳遞資料的行為。構造合理的跨邊界呼叫需要我們對原始碼中的依賴關係進行合理管控，若不對依賴管控，可能導致一個模組的原始碼發生變更時，其他模組的原始碼也可能會隨之發生變更或重新編譯，並需要重新部署，劃清邊界有助於減少這種情況的發生。&lt;/p&gt;
&lt;p&gt;跨邊界呼叫包含不同的類別：原始碼層次、部署層次、服務層次以及物理邊界。&lt;/p&gt;
&lt;h3 id="源码层次的跨边界调用"&gt;原始碼層次的跨邊界呼叫&lt;/h3&gt;
&lt;p&gt;原始碼層次的跨邊界調用出現在單體架構中，這類架構一般都需要利用某種動態形式的多型來管理其內部的依賴關係。&lt;/p&gt;
&lt;p&gt;最簡單的跨邊界呼叫形式，是由低層客戶端來呼叫高層服務函式，這種依賴關係在執行時和編譯時會保持指向一致，都是從低層元件指向高層元件。&lt;/p&gt;
&lt;p&gt;（原示意圖暫缺）&lt;/p&gt;
&lt;p&gt;但當高層元件中的客戶端需要呼叫低層元件中的服務時，我們就需要運用動態形式的多型來反轉依賴關係（圖中的Service介面是某種形式的SPI）。&lt;/p&gt;
&lt;p&gt;（原示意圖暫缺）&lt;/p&gt;
&lt;h3 id="部署层次的跨边界调用"&gt;部署層次的跨邊界呼叫&lt;/h3&gt;
&lt;p&gt;部署層次的跨邊界呼叫將其所有可部署的單元打包成一個便於操作的檔案格式，除這一點以外，這種按部署層次解耦的元件與單體結構幾乎是一樣的。&lt;/p&gt;
&lt;p&gt;部署層次的跨邊界呼叫涉及到物理邊界，因為不同部署的單元需要跨越物理邊界來完成通訊。常見的物理邊界包括動態連結庫、執行緒、本地程序等。&lt;/p&gt;
&lt;h3 id="服务层次的跨边界调用"&gt;服務層次的跨邊界呼叫&lt;/h3&gt;
&lt;p&gt;服務是系統架構中最強的邊界形式，一個服務就是一個程序。在劃分架構邊界時，一定要儘可能地控制通訊次數。在這個層次上通訊必須能夠適應高延時情況。除此之外，我們可以在服務層次上使用與本地程序相同的規則。也就是讓較低層次服務成為較高層次服務的“外掛”。&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（五）軟體架構</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%94/</link>
      <pubDate>Sun, 28 Jul 2024 19:23:23 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%94/</guid>
      <description>&lt;p&gt;是時候進入軟體架構了。在之前已經介紹了模組/類、元件的相關內容，這一部分會重點介紹軟體架構的基本定義。&lt;/p&gt;
&lt;h2 id="写在最前面"&gt;寫在最前面&lt;/h2&gt;
&lt;p&gt;🌟軟體架構師自身需要是程式設計師，並且必須一直堅持做一線程式設計師，如果不親身承受因系統設計而帶來的麻煩，就體會不到設計不佳所帶來的痛苦，接著就會逐漸迷失正確的設計方向。&lt;/p&gt;
&lt;h2 id="什么是软件架构"&gt;什麼是軟體架構？&lt;/h2&gt;
&lt;p&gt;軟體架構工作的實質就是討論規劃如何將系統切分成元件，並安排好元件之間的排列關係與通訊方式，即確定邊界。&lt;/p&gt;
&lt;p&gt;軟體架構設計的目的一般有兩個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;為了在工作中更好的對這些元件進行研發、部署、執行以及維護；&lt;/li&gt;
&lt;li&gt;如果想設計一個便於推進各項工作的系統，其策略就是要在設計中儘可能長時間地&lt;strong&gt;保留儘可能多的可選項&lt;/strong&gt;；&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;一個軟體系統的架構質量和該系統是否能正常工作（行為）的關係並不大，畢竟世界上有很多架構設計糟糕但是工作正常的軟體系統。真正的麻煩往往會出現在這個軟體系統的開發、部署以及後續的補充開發中。這也間接反映的作者的觀點：軟體系統的架構價值在一定程度上大於其行為價值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;軟體架構設計的目標如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心目標：一個良好的架構設計應該圍繞著用例來展開，良好的架構設計應該只關注用例，並能將它們與其他的周邊因素隔離，隔離的方式是儘可能推遲選擇，保留更多的可選項；&lt;/li&gt;
&lt;li&gt;主要目標：支撐軟體系統的全生命週期，設計良好的架構可以讓系統便於理解、易於修改、方便維護，並且能輕鬆部署；&lt;/li&gt;
&lt;li&gt;終極目標：最大化程式設計師的生產力（解放生產力），同時最小化系統的&lt;strong&gt;總&lt;/strong&gt;運營成本；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;軟體架構的設計重點是將&lt;strong&gt;策略&lt;/strong&gt;彼此分離，然後將它們按照變更的方式進行重新分組，也就是劃分出邊界，在這一部分可以依據的原則包括SRP、CCP、CSP，SDP、SAP等等。&lt;/p&gt;
&lt;h2 id="软件架构的职责"&gt;軟體架構的職責&lt;/h2&gt;
&lt;p&gt;軟體架構具有開發、部署、執行以及維護多個方面的職責。&lt;/p&gt;
&lt;p&gt;在開發上：軟體架構要方便軟體系統的開發，因此不同的團隊應該使用不同的軟體架構設計。這在一定程度上也體現了康威定律。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;為什麼康維定律能夠體現團隊的架構？看影片瞭解更多：&lt;a href="https://www.bilibili.com/video/BV1bb421E7i6/?spm_id_from=333.337.search-card.all.click&amp;amp;vd_source=8c0f6bcaf6b2e92f574553f45e565994"&gt;康威定律：為什麼你的架構會反映團隊結構？_嗶哩嗶哩_bilibili&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在部署上：軟體架構要實現軟體的一鍵式輕鬆部署；&lt;/p&gt;
&lt;p&gt;在執行上，&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;正如前面提到的，架構對於軟體系統的高效執行遠小於其他幾個方面，作者認為這主要源於增加硬體資源可以彌補架構在執行方面的考慮不足，而架構設計通常需要更為昂貴的人力資源；&lt;/li&gt;
&lt;li&gt;除了架構設計對於高效執行的作用，架構還應該能反應系統在執行時的需求，也就是說，設計良好的系統架構應該可以使開發人員對系統的執行過程一目瞭然。架構應該起到揭示系統執行過程的作用，架構應該將系統中的用例、功能以及該系統的必備行為設定為對開發者可見的一級實體，簡化它們對於系統的理解，《尖叫的軟體架構》一章就是在重點說明這個問題；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在維護上，軟體系統維護的成本一般是最高的，維護的成本可以分為&lt;strong&gt;探秘&lt;/strong&gt;與&lt;strong&gt;風險&lt;/strong&gt;兩類：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;探秘：探秘(spelunking)的成本主要來自我們對於現有軟體系統的挖掘，目的是確定新增功能或被修復問題的最佳位置和最佳方式；&lt;/li&gt;
&lt;li&gt;風險：風險(risk)，則是指當我們進行上述修改時，總是有可能衍生出新的問題，這種可能性就是風險成本；&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="保持可选项"&gt;保持可選項&lt;/h2&gt;
&lt;p&gt;回憶之前提到的軟體架構提到的兩種價值：“行為價值”和“架構價值”，提升架構價值的方式就是讓軟體更軟，而讓軟體更軟的方式就是讓軟體架構儘可能多的保持可選項。&lt;/p&gt;
&lt;p&gt;軟體系統的元素分為&lt;strong&gt;策略&lt;/strong&gt;與&lt;strong&gt;細節&lt;/strong&gt;兩類，策略體現的是軟體中所有的業務規則與操作過程，因此它是系統的真正價值所在，細節則是指那些讓操作該系統的人、其他系統以及程式設計師們與策略進行互動，但是又不會影響到策略本身的行為。如 IO 裝置、資料庫等等，細節選項應該是儘可能保留的。&lt;/p&gt;
&lt;p&gt;軟體架構師的目標就是要建立一種系統形態，該形態會以策略為&lt;strong&gt;最基本的元素&lt;/strong&gt;，並讓細節與策略脫離關係，以允許在具體決策過程中&lt;strong&gt;推遲或延遲與細節相關的內容&lt;/strong&gt;。因為越到專案的後期，我們就擁有越多的資訊來做出合理的決策。一個優秀的軟體架構師應該致力於最大化可選項的數量。&lt;/p&gt;
&lt;p&gt;作者使用裝置無關性的例子來說明推遲細節的重要性。現代作業系統的輸入輸出裝置種類多樣，而在計算機發展的最初時期，打孔紙帶是主流（幾乎是唯一）的一種輸入輸出方式，程式設計人員很自然的就將讀取/輸出紙帶的程式碼耦合到了系統程式碼當中，當磁帶出現後，開發人員不得不開發新的程式碼來適配磁帶輸入輸出，光碟出現後…為了應對這種重複開發，適應性差的問題，開發人員提出了裝置無關行的概念，即將裝置抽象成函式，OS會利用函式與輸入輸出裝置互動，而開發人員只需要提供這些函式的具體實現。此時輸入輸出就成為了OS的外掛，這也是開閉原則的雛形（擁抱👏新增、抗拒🥊修改！）。&lt;/p&gt;
&lt;h2 id="保持独立性"&gt;保持獨立性&lt;/h2&gt;
&lt;p&gt;良好的軟體架構應該有充足的獨立性。&lt;/p&gt;
&lt;p&gt;解耦可以讓我們保證軟體架構的獨立性。&lt;/p&gt;
&lt;p&gt;解耦分為水平解耦與垂直解耦。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;水平解耦（按層解耦）：系統可以被解耦成若干個水平分層——UI、資料庫等等；&lt;/li&gt;
&lt;li&gt;用例解耦（垂直解耦）：在水平分層解耦的同時，也按用例將其切分成多個垂直分片，如：將增加訂單的用例UI與刪除訂單的用例UI分開。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果我們按照變更原因的不同對系統進行解耦，就可以持續地向系統內新增新的用例，而不會影響舊有的用例。&lt;/p&gt;
&lt;p&gt;同樣，解耦的層次也可以是不同的。可以在原始碼、部署以及服務三個不同的層次來完成解耦。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原始碼層次：控制原始碼中模組之間的依賴關係，系統的元件通過函式呼叫來進行互動。這種模式叫做單體結構；&lt;/li&gt;
&lt;li&gt;部署層次：控制部署單元（如 Jar 檔案）之間的依賴關係，系統的元件可能使用跨執行緒（注意不是跨網路）的通訊、Socket 通訊或共享記憶體融通訊；&lt;/li&gt;
&lt;li&gt;服務層次：將元件間的依賴降低到資料結構級別，然後通過網路資料包進行通訊，如微服務通過 rpc、rest 互動。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;並沒有嚴格的標準說明那個解耦層次是更好的，因為隨著專案的逐漸成熟，最好的解耦模式可能會發生變化。一個設計良好的架構應該能允許一個系統從單體結構開始，以單一檔案的形式部署，然後逐漸成長為一組相互獨立的可部署單元，甚至是獨立的服務或者微服務。最後還能隨著情況的變化，允許系統逐漸回退到單體結構。一個設計良好的架構在上述過程中還應該能保護系統的大部分原始碼不受變更影響。對整個系統來說，&lt;strong&gt;解耦模式也應該是一個可選項&lt;/strong&gt;。我們在進行大型部署時可以採用一種模式，而在進行小型部署時則可以採用另一種模式。&lt;/p&gt;
&lt;p&gt;在瞭解解耦之後，讓我們看看解耦對系統獨立性到底有何影響。&lt;/p&gt;
&lt;h3 id="解耦对系统运行独立性的意义"&gt;解耦對系統執行獨立性的意義&lt;/h3&gt;
&lt;p&gt;如果不同面向之間的用例得到了良好的隔離，那麼需要高吞吐量的用例就和需要低吞吐量的用例互相自然分開了。如果 UI 和資料庫的部分能從業務邏輯分離出來，那麼它們就可以執行在不同的伺服器上。而且需要較大頻寬的應用也可以在多個伺服器上執行多個例項。&lt;/p&gt;
&lt;h3 id="解耦对系统开发独立性的意义"&gt;解耦對系統開發獨立性的意義&lt;/h3&gt;
&lt;p&gt;只要系統按照其水平分層和用例進行了恰當的解耦，整個系統的架構就可以支援多團隊開發，不管團隊組織形式是分功能開發、分元件開發、分層開發，還是按照別的什麼變數分工都可以&lt;/p&gt;
&lt;h3 id="解耦对系统部署独立性的意义"&gt;解耦對系統部署獨立性的意義&lt;/h3&gt;
&lt;p&gt;如果解耦工作做得好，我們甚至可以在系統執行過程中熱切換(hot-swap)其各個分層實現和具體用例。在這種情況下，我們增加新用例就只需要在系統中新增一些新的jar檔案，或啟動一些服務即可，其他部分將完全不受影響&lt;/p&gt;
&lt;h2 id="什么是代码重复"&gt;什麼是程式碼重複&lt;/h2&gt;
&lt;p&gt;程式碼中的重複可以分為兩種：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;真正的重複：程式碼中需要被消滅的部分；&lt;/li&gt;
&lt;li&gt;虛假的重複：看起來重複的程式碼，實際上是不同的演進路徑，也就是擁有不同的變更速率與變更邊緣。CRP闡述：對於使用頻次不同的程式碼需要拆分到不同的元件中，如果在閱讀程式碼時沒有考慮到這一點就很有可能誤認為虛假的重複為重複程式碼，這些“重複”有時候是必須的，還記得計算工資的例子嗎？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（第五篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>領域驅動設計初識</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</link>
      <pubDate>Fri, 26 Jul 2024 19:49:45 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/ddd%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1%E5%88%9D%E8%AF%86/</guid>
      <description>&lt;h1 id="领域驱动设计概述"&gt;領域驅動設計概述&lt;/h1&gt;
&lt;p&gt;領域驅動設計（DDD, Domain-Driven Design）是一種模型驅動設計的方法，它通過領域模型捕捉領域知識，使用領域模型構造&lt;strong&gt;更易維護&lt;/strong&gt;的軟體.&lt;/p&gt;
&lt;p&gt;DDD的設計過程分為戰略設計與戰術設計，其中戰略設計面向領域、子域以及限界上下文的設計，而戰術設計面向實體、值物件、領域事件等設計，關係如下：&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/DDD1.png" alt="img"&gt;&lt;/p&gt;
&lt;h2 id="战略设计阶段相关概念"&gt;戰略設計階段相關概念&lt;/h2&gt;
&lt;h3 id="领域"&gt;領域&lt;/h3&gt;
&lt;p&gt;領域是系統要解決問題的領域，如商品資訊管理就可以是系統要解決問題的領域.&lt;/p&gt;
&lt;h3 id="子域"&gt;子域&lt;/h3&gt;
&lt;p&gt;根據使用語言的不同可以將領域劃分為不同的子域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核心域：決定產品核心競爭力的子域，是最為重要、業務最核心、個性的部分；&lt;/li&gt;
&lt;li&gt;通用域：被多個子域使用的通用功能子域，比如用到的通用系統，例如認證、許可權等等，這類應用沒有企業特點限制，不需要做太多的定製化；&lt;/li&gt;
&lt;li&gt;支撐域：不包含核心功能與通用功能的子域，具有企業特性，但不具有通用性，例如資料程式碼類的資料字典等系統；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="限界上下文"&gt;限界上下文&lt;/h3&gt;
&lt;p&gt;限界上下文，即限定使用不同模型來解決不同問題所產生的不同區域.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;限界上下文是一個子域或者多個子域的集合&lt;/strong&gt;，&lt;strong&gt;要保證一個限界上下文須支援一個完整的業務流程&lt;/strong&gt;，&lt;strong&gt;保證這個業務流程所涉及的領域都在一個限界上下文中&lt;/strong&gt;.限界上下文是微服務拆分的依據，即每個限界上下文對應一個微服務.&lt;/p&gt;
&lt;p&gt;那麼為什麼要進行限界上下文呢？這是因為&lt;strong&gt;上下文&lt;/strong&gt;的存在導致編碼十分困難，舉個例子說明：&lt;/p&gt;
&lt;p&gt;例如在招聘域我們可能會使用“平臺”來表明來自於哪個學校、機構；在跳水域我們使用“平臺”來表明選手從那個跳臺跳水；而在鐵路交通運輸領域，我們可能會使用“平臺”來描述鐵路站臺……在某一天的某一個機會，跳水選手、HR和乘務員聚到了一起，並且他們都不瞭解對方的身份，如果此時HR問跳水選手說你來自哪個平臺……&lt;/p&gt;
&lt;p&gt;看到了吧，同樣是使用“平臺”這個詞語，但三個人在&lt;strong&gt;沒有約定&lt;/strong&gt;的情況下可能對於詞語的理解不盡相同，解決這個問題的辦法就是把跳水域、招聘域和交通域隔離，分別定義域內的術語，在一個域內討論就可以最大可能的避免歧義，這就是約定.這也是為什麼最好把業務所涉及的領域定義在一個限界上下文中：減少歧義.&lt;/p&gt;
&lt;p&gt;在軟體系統的設計中同樣會遇到類似的問題，如果你負責設計一個足夠大的計算機軟體產品，產品涉及到跳水服務、招聘服務與乘車服務，系統沒有對服務做任何切分，你在跳水有關業務邏輯中使用platform資料結構描述跳臺、在招聘業務中使用platform代表平臺、在交通業務中使用platform代表站臺，當這三個業務不可避免的交匯到一起時，災難就發生了，面對滿螢幕不同語義但幾乎同名的變數，我們能怎麼辦？&lt;/p&gt;
&lt;p&gt;顯然，限定某一個領域術語的細節，我們就可以在領域中暢通無阻的使用這個術語了，這就是限界上下文的重要作用.&lt;/p&gt;
&lt;h2 id="战术设计阶段相关概念"&gt;戰術設計階段相關概念&lt;/h2&gt;
&lt;h3 id="值对象和实体"&gt;值物件和實體&lt;/h3&gt;
&lt;p&gt;首先了解一下值物件和實體.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;值物件是通過屬性值來識別的物件，即如果兩個值物件的內部值都是相同的，那麼我們就認為這兩個值物件是相同的.顯然，值物件的屬性不可變.&lt;/li&gt;
&lt;li&gt;實體是擁有唯一標識和狀態，且具有生命週期的業務物件.實體的屬性是可變的，如果兩個實體的屬性是完全相同的，我們也不認為他們是相同的實體，只有他們兩個的標識（如ID）是相同的才會被認為是相同的實體.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;舉例區分值物件和實體：&lt;/p&gt;
&lt;p&gt;假如現在我們有一個白色值物件Color white{R:255, G:255, B:255}和一個輪胎實體tire{Air:&lt;em&gt;,Size:&lt;/em&gt;}：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可變性：白色中的每一個屬性都是不可變的，因為一旦變化此物件就不再代表”白色“；而輪胎中的氣壓、尺寸等值可以改變，因為了即便這些引數發生了變化它仍然是輪胎；&lt;/li&gt;
&lt;li&gt;可比較性：正是由於值物件的值不可變性賦予了其相等的規則，兩個白色物件的值一定會是相同的，因此如果兩個值物件的內部值都是相同的，那麼我們就認為這兩個值物件是相同的，實體則由於其屬性的可變性而喪失了這個特點.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;對於實體而言，其形態一般有四種：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;失血模型：模型僅僅包含資料的定義和getter/setter方法，業務邏輯和應用邏輯都放到服務層中.這種類在Java中叫POJO.&lt;/li&gt;
&lt;li&gt;貧血模型：貧血模型中包含了一些業務邏輯，但不包含依賴持久層的業務邏輯.這部分依賴於持久層的業務邏輯將會放到服務層中.&lt;/li&gt;
&lt;li&gt;充血模型：充血模型中包含了所有的業務邏輯，包括依賴於持久層的業務邏輯.&lt;/li&gt;
&lt;li&gt;脹血模型：脹血模型就是把和業務邏輯不相關的其他應用邏輯（如授權、事務等）全部都放到領域模型中.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;資源庫Repo&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Repo是針對Entity設計的儲存操作，Repo中的操作應該儘可能低階、命名要簡短，並且不能定義太多.Java中的MyBatis Mapper可以看作是Repo的一種實現.&lt;/p&gt;
&lt;h3 id="聚合与聚合根"&gt;聚合與聚合根&lt;/h3&gt;
&lt;p&gt;聚合是一種更大範圍的封裝，把一組有相同生命週期、在業務上不可分隔的實體和值物件放在一起考慮，只有聚合根可以對外暴露引用，聚合也是一種內聚性的表現.聚合根之間也可相互呼叫，聚合根抽象出來一般名字為名詞.&lt;/p&gt;
&lt;p&gt;聚合根通過以下幾種手段實現封裝：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必須通過操作聚合根來實現操作整個聚合，外部操作不允許直接操作聚合中的元素.比如紫色（外觀）+輪胎+鋼架+…=汽車，我們開車時不能也不會去單獨操作輪胎、方向盤、外觀…，相反，我們通過操作汽車這個聚合根來間接操作其他實體/值物件.&lt;/li&gt;
&lt;li&gt;聚合定義了一組&lt;strong&gt;邊界&lt;/strong&gt;，邊界內所有的元件必須對業務邏輯有效；&lt;/li&gt;
&lt;li&gt;必須在一個原子性的事務中操作聚合，否則可能會出現異常，也就是說聚合是操作的單元，從倉庫取出聚合、操作完成後放回是一個原子性的操作；如何理解原子性？加入汽車聚合根有輪子實體、鋼/鋁/碳架實體，你不能拆掉汽車的一個輪子檢修完後不裝上.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="领域事件domain-events"&gt;領域事件Domain Events&lt;/h3&gt;
&lt;p&gt;當實體的屬性發生變化時就會產生領域事件，領域事件是領域專家認為重要的事情.領域事件是發生在領域中且值得注意的事件.而領域事件通常意味著領域物件狀態的改變.&lt;strong&gt;領域事件在系統中起到了傳遞訊息、觸發其他動作的作用，是解耦領域模型的重要手段之一&lt;/strong&gt;.我們往往利用&lt;strong&gt;訊息佇列&lt;/strong&gt;來傳遞領域事件，這樣所有訂閱此訊息的子域都會進行自己內部的響應操作.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;訊息匯流排也是一種實現方法👋.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如輪胎實體氣壓發生變化時，會釋放領域leaked漏氣事件，此漏氣事件傳遞了“輪胎漏氣”這個訊息，從而引發一系列其他的響應，如動能系統變化、方向盤手感變化等等，領域事件通常會使用過去式命名來說明事件已經發生，不可撤銷.&lt;/p&gt;
&lt;h3 id="领域服务domain-service"&gt;領域服務Domain Service&lt;/h3&gt;
&lt;p&gt;有些領域中的動作看上去&lt;strong&gt;並不屬於任何物件&lt;/strong&gt;.它們代表了領域中的一個重要的行為，不能忽略它們或者簡單地把它們合併到某個實體或者值物件中.當這樣的行為從領域中被識別出來時，推薦的實踐方式是將它宣告成一個服務，這個服務就是領域服務.&lt;/p&gt;
&lt;p&gt;領域服務不是微服務中的“服務”概念.服務和聚合根概念有些相近，他們都可以操作多個實體，但是二者理念和作用不同，&lt;strong&gt;聚合根是對實體的組合，而服務是用於同步多個實體的狀態，比如將某個item實體加入到list實體&lt;/strong&gt;（比如把mail投遞到inbox）.所以Servie一般抽象出來都是動詞.&lt;/p&gt;
&lt;h2 id="ddd领域建模设计领域模型"&gt;DDD領域建模（設計領域模型）&lt;/h2&gt;
&lt;p&gt;DDD領域建模一般步驟如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根據需求劃分出初步的子域和限界上下文，以及上下文之間的關係；&lt;/li&gt;
&lt;li&gt;進一步分析每個上下文內部，識別出哪些是實體，哪些是值物件，並確定實體需要使用哪種程式碼形態：失血、貧血、充血、脹血；&lt;/li&gt;
&lt;li&gt;對實體、值物件進行關聯和聚合，劃分出聚合的範疇和聚合根；&lt;/li&gt;
&lt;li&gt;為聚合根設計資源庫repo，並思考實體或值物件的建立方式；&lt;/li&gt;
&lt;li&gt;在工程中實踐領域模型，並在實踐中檢驗模型的合理性，倒推模型中不足的地方並重構.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;這就是DDD採用兩階段設計原則——先進行戰略設計、隨後進行戰術設計。&lt;/p&gt;
&lt;p&gt;在實踐中，建議實體採用失血模型（實體方法只包括setter/getter）或者貧血模型（包含不涉及資料庫操作的簡單邏輯，如屬性合法性校驗）；&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;實際上這是DDD建模方式之一，是自頂向下的設計方法，還存在一種方式是使用自下而上的方法，即先確定領域模型——實體、值物件等，再確定子域、限界上下文…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;（本節完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>領域驅動設計</category></item>
    <item>
      <title>再讀整潔架構之道（四）元件建構原則</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%9B%9B/</link>
      <pubDate>Sat, 06 Jul 2024 17:27:18 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E5%9B%9B/</guid>
      <description>&lt;h1 id="再读整洁架构之道四组件构建原则"&gt;再讀整潔架構之道（四）元件構建原則&lt;/h1&gt;
&lt;p&gt;第三篇主要敘述如何設計模組和類，在本篇將會更近一步，敘述如何進行元件的設計。&lt;/p&gt;
&lt;p&gt;元件是軟體的部署單元，是整個軟體系統在部署過程中可以獨立完成部署的最小實體，元件可以被單獨開發，元件化的外掛式架構已經成為我們習以為常的軟體構建形式了。&lt;/p&gt;
&lt;h2 id="组件聚合"&gt;元件聚合&lt;/h2&gt;
&lt;p&gt;元件聚合告訴我們哪些模組和類應該組合在一起形成元件。主要有三個原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;複用/釋出等同原則；&lt;/li&gt;
&lt;li&gt;共同閉包原則；&lt;/li&gt;
&lt;li&gt;共同複用原則；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="复用发布等同原则rep"&gt;複用/釋出等同原則REP&lt;/h3&gt;
&lt;p&gt;REP指出軟體複用的最小粒度應等同於其釋出的最小粒度。&lt;/p&gt;
&lt;p&gt;REP是從程式碼複用角度考慮的，它規定了具備相同主題和功能的程式碼可以進行組合形成元件。REP建議軟體複用應該按照元件來進行的。&lt;/p&gt;
&lt;p&gt;通俗來講，某個元件打包釋出後可能會形成版本號或唯一標識，我們通過引入現成程式碼庫的方式複用程式碼，引入的單位就是元件打包的結果，引入的憑據就是版本號。&lt;/p&gt;
&lt;h3 id="共同闭包ccp"&gt;共同閉包CCP&lt;/h3&gt;
&lt;p&gt;CCP指出我們應該將那些會同時修改，並且為相同目的而修改的類放到同一個元件中，而將不會同時修改，並且不會為了相同目的而修改的那些類放到不同的元件中。&lt;/p&gt;
&lt;p&gt;CCP是從程式碼維護角度考慮的，對於由於同一原因/修改目的程式碼應該組合形成元件，這樣就可以有效地降低因軟體釋出、驗證及部署所帶來的工作壓力。&lt;/p&gt;
&lt;p&gt;前面說過，CCP是SRP的元件版，SRP和CCP都可以用以下的語言概括：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;將由於相同原因而修改，並且需要同時修改的“東西”放在一起。將由於不同原因而修改，並且不同時修改的東西分開。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在SRP中，東西是指函式和其他組成類或模組的成分；&lt;/li&gt;
&lt;li&gt;在CCP中，東西是指構成元件的類和模組。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="共同复用crp"&gt;共同複用CRP&lt;/h3&gt;
&lt;p&gt;CRP指出不要強迫一個元件的使用者依賴他們不需要的東西。&lt;/p&gt;
&lt;p&gt;CRP規定了對於使用場景不同、使用頻次不同的程式碼需要進行拆分到不同的元件中。CRP 是為了避免不必要的切分，是ISP介面隔離原則的普適版。&lt;/p&gt;
&lt;p&gt;CRP是介面隔離的普適版，ISP和CRP都可以用以下的語言概括：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;不要依賴不需要用到的“東西”。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在ISP中，東西是指包含不需要的方法的函式/類；&lt;/li&gt;
&lt;li&gt;在CCP中，東西是指包含不需要的函式的類/模組；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="思考"&gt;思考&lt;/h3&gt;
&lt;p&gt;那麼REP、CCP、CRP三者之間有什麼關係呢？&lt;/p&gt;
&lt;p&gt;實際上三者存在著&lt;strong&gt;競爭關係&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;REP和CCP是黏合性原則，它們指導我們應該把哪些類/模組放在一起形成元件，而CRP屬於排他性原則，它知道我們哪些類/模組不應該放在一起。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/image-20240706143525187.png" alt="image-20240706143525187"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果架構師只關注REP和CCP，那麼會發現軟體依賴了的元件中包含了很多並不需要的部分，這樣會導致太多不必要的釋出；&lt;/li&gt;
&lt;li&gt;如果架構師只關注CRP和CCP，那麼會發現軟體的複用會變得非常困難；&lt;/li&gt;
&lt;li&gt;如果架構師只關注REP和CRP，那麼會發現如果部分類/模組需要變更，很多相關模組也不可避免的要跟隨變更；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;軟體架構師的任務就是要在這三個原則中間進行取捨，元件的構成安排應隨著專案重心的不同，以及研發性與複用性的不同而不斷演化，因此確定專案朝著哪個方向演進是要動態判斷的。&lt;/p&gt;
&lt;h2 id="组件耦合"&gt;元件耦合&lt;/h2&gt;
&lt;p&gt;元件耦合告訴我們如何安排元件之間的關係。它包含三個原則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;無依賴環原則ADP；&lt;/li&gt;
&lt;li&gt;穩定依賴原則SDP；&lt;/li&gt;
&lt;li&gt;穩定抽象原則SAP；&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="无依赖环原则adp"&gt;無依賴環原則ADP&lt;/h3&gt;
&lt;p&gt;ADP告訴我們元件依賴關係圖中不應該出現環。&lt;/p&gt;
&lt;p&gt;版本號控制機制解決了一覺醒來綜合徵的問題，而此機制需要遵循ADP，首先讓我們讀一下作者是怎麼介紹一覺醒來綜合徵的：&lt;/p&gt;
&lt;p&gt;當你花了一整天的時間，好不容易搞定了一段程式碼，第二天上班時卻發現這段程式碼莫名其妙地又不能工作了。這大機率是其他工作人員修改了你專案所依賴的元件。&lt;/p&gt;
&lt;p&gt;要想解決這個問題一般有兩種方案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每週構建&lt;/li&gt;
&lt;li&gt;版本號控制機制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;每週構建&lt;/u&gt;&lt;/strong&gt;：每個人先在自己的程式碼倉工作，每週固定時間（比如週五）再進行專案構建並處理可能的衝突問題。&lt;/p&gt;
&lt;p&gt;這種方式的侷限性很明顯：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;隨著專案越來越大，整合工作會越來越難以按時完成&lt;/li&gt;
&lt;li&gt;整個專案會變得越來越難以構建與測試，團隊反饋週期會越來越長，研發質量自然也會越來越差&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此版本號控制機制需要被引入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;版本號控制機制&lt;/u&gt;&lt;/strong&gt;：每當一個元件釋出新版本時，其他依賴這個元件的團隊都可以自主決定是否立即採用新版本。&lt;/p&gt;
&lt;p&gt;版本號控制機制&lt;strong&gt;不允許元件結構依賴關係圖中出現環&lt;/strong&gt;，也就是要遵守無依賴換原則ADP，否則一覺醒來綜合徵是不可避免的。為什麼呢？&lt;/p&gt;
&lt;p&gt;這是因為如果存在依賴環路，環路里的所有元件被組合成了一個更大的元件，這就要求他們都必須使用相同的版本，這樣其他的元件才能成功的完成依賴。同樣，存在依賴換的系統在測試時也會成為一個棘手的問題，雖然打樁mock技術已經被廣泛應用，但同時為環路中的元件重複打樁也已經十分的不優雅了。&lt;/p&gt;
&lt;p&gt;那麼如何消除迴圈依賴呢？方案有兩個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用依賴反轉建立介面；&lt;/li&gt;
&lt;li&gt;建立新元件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;應用DIP解決迴圈依賴很好理解，下面講述一個在工作中遇到的案例來說明如何使用建立新元件來打破迴圈依賴。&lt;/p&gt;
&lt;p&gt;現在有這樣的一個場景，團隊正在使用領域驅動設計的方法設計系統架構，現在實體Entity Dog需要依賴Dog Repo完成Save儲存操作。此時Entity模組依賴了Repo模組，很不幸的是在Entity模組中我們還定義了Dog PO（簡直糟透了），Repo的Save又需要使用PO來完成物件的轉換並存儲。此時Entity和Repo之間就發生了迴圈依賴，幸運的是，在Go中模組之間的相互依賴是不允許的，檢查起會提醒“迴圈匯入”錯誤，怎麼處理這個問題呢？&lt;/p&gt;
&lt;p&gt;答案是我們需要新建立一個PO模組，並把Entity中的PO移動到模組，Repo中依賴PO的函式也要劃分到PO中，這樣就消除了依賴環。使用依賴注入方法也是一個選擇，如果你使用SprintBoot，@AutoWired即可解決此問題，但其本質也是建立了一個新元件——依賴池，消除了依賴環。&lt;/p&gt;
&lt;p&gt;類似的例子還有很多很多，是否還記得我們使用anaconda下載Python庫時遇到的庫版本衝突問題……&lt;/p&gt;
&lt;p&gt;在這裡多插一句，筆者在設計元件結構的時候很自然的就想到要和系統功能對應，也就是說，元件應該是和系統功能一一對應的，這樣元件依賴圖就和系統功能模組劃分也一一對應起來。想當然認為在系統設計的一開始元件依賴結構圖也就產出了。&lt;/p&gt;
&lt;p&gt;但作者特別提到，自頂向下的設計是不可能的。讓我們看看書中是怎麼描述的：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;/p&gt;
&lt;p&gt;元件結構圖必須隨著軟體系統的變化而變化和擴張，而不可能在系統構建的最初就被完美設計出。因為元件依賴結構圖並不和功能一一對應，它更像是應用程式在構建性與維護性方面的一張地圖。&lt;/p&gt;
&lt;p&gt;被設計並實現出來的模組越來越多，專案中就逐漸出現了要對元件依賴關係進行管理的需求，我們希望將專案變更所影響的範圍被限制得越小越好，因此需要應用單一職責原則(SRP)和共同閉包原則(CCP)來將經常同時被變更的類聚合在一起。&lt;/p&gt;
&lt;p&gt;元件結構圖中的一個重要目標是指導如何隔離頻繁的變更。我們不希望那些頻繁變更的元件影響到其他本來應該很穩定的元件。&lt;/p&gt;
&lt;p&gt;另外，隨著應用程式的增長，建立可重用元件的需要也會逐漸重要起來。這時CRP又會開始影響元件的組成。最後當迴圈依賴出現時，隨著無迴圈依賴原則(ADP)的應用，元件依賴關係會產生相應的抖動和擴張。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="稳定依赖原则sdp"&gt;穩定依賴原則SDP&lt;/h3&gt;
&lt;p&gt;SDP告訴我們依賴關係必須要指向更穩定的方向。通常來說，要做到軟體架構的越底層越穩定。&lt;/p&gt;
&lt;p&gt;任何一個我們預期會經常變更的元件都不應該被一個難於修改的元件所依賴，否則這個多變的元件也將會變得非常難以被修改。這就是軟體開發的困難之處，我們精心設計的一個容易被修改的元件很可能會由於別人的一條簡單依賴而變得非常難以被修改，通過遵循SDP，這樣的問題就能夠被避免。&lt;/p&gt;
&lt;p&gt;判斷元件是否穩定，可以使用穩定性指標。作者定義穩定性指標可以通過如下公式計算：
$$
I=\frac{Fan-out}{Fan-in+Fan-out}
$$
其中Fan-in為元件的入依賴數量，Fan-out為出依賴數量，這意味著一個元件的出依賴越少越穩定，因為如果出依賴為 0 意味著沒有因素可以讓元件改變。&lt;/p&gt;
&lt;p&gt;穩定依賴原則(SDP)的要求就是要讓每個元件的I指標都必須大於其所依賴元件的I指標（即被指向的元件更穩定）。&lt;/p&gt;
&lt;p&gt;SDP並不是要求所有元件都應該是穩定的，我們設計元件架構圖的目的就是要決定應該讓哪些元件穩定，讓哪些元件不穩定，所有元件都穩定的架構是不靈活的，不靈活的架構沒有足夠的架構價值。&lt;/p&gt;
&lt;h3 id="稳定抽象原则sap"&gt;穩定抽象原則SAP&lt;/h3&gt;
&lt;p&gt;SAP指出一個元件的抽象化程度應該與其穩定性保持一致。&lt;/p&gt;
&lt;p&gt;高層策略等應該存在穩定的元件中，但這樣做會導致高層策略很難修改，幸運的是開閉原則 OCP 告訴我們穩定的元件可以被設計成容易擴充套件的，抽象類具有穩定抽象能力，可以設計一個抽象類具有較好的穩定性並且易於擴充套件和修改。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;抽象類是介面和類之間的緩衝地帶。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;穩定抽象原則(SAP)為元件的穩定性與它的抽象化程度建立了一種關聯。原則要求穩定的元件同時應該是抽象的，這樣它的穩定性就不會影響到擴充套件性。同時一個不穩定的元件應該包含具體的實現程式碼，這樣它的不穩定性就可以通過具體的程式碼被輕易修改。因此，如果一個元件想要成為穩定元件，那麼它就應該由介面和抽象類組成，以便將來做擴充套件。&lt;/p&gt;
&lt;p&gt;和穩定性類似，我們也可以使用抽象程度指標來衡量元件的抽象程度：
$$
A=\frac{N_c}{N_a}
$$
其中Nc是指元件中類的數量，而Na是元件中抽象類和介面的數量。A指標的取值範圍是從0到1，值為0代表元件中沒有任何抽象類，值為1就意味著元件中只有抽象類。&lt;/p&gt;
&lt;h3 id="思考-1"&gt;思考🤔&lt;/h3&gt;
&lt;p&gt;SDP和SAP有什麼關聯嗎？&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一點，從SDP、SAP與DIP上說&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;實際上，SDP+SAP=元件DIP。因為SDP要求的是讓依賴關係指向更穩定的方向，而SAP則告訴我們穩定性本身就隱含了對抽象化的要求，即依賴關係應該指向更抽象的方向。&lt;/p&gt;
&lt;p&gt;怎麼理解這句話呢？&lt;/p&gt;
&lt;p&gt;可以想到，DIP在類上的作用是為了保證靈活性，SDP+SAP實際上就是為了保證元件在穩定的基礎上足夠靈活，並且 DIP 和 SDP+SAP 在軟體架構上的影響都是具體實現類指向抽象類/介面，因此從作用上看SDP+SAP=元件DIP。&lt;/p&gt;
&lt;p&gt;但是DIP和SDP+SAP還是有不同之處，對類來說，設計是沒有灰色地帶的，一個類要麼是抽象類，要麼就不是。SDP與SAP這對原則是應用在元件層面上的，我們要允許一個元件部分抽象，部分穩定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;其次，從SAP和SDP的目的達成來說&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此外，SDP指出依賴應該指向穩定的位置，SAP要求我們在設計元件時，高層的策略應該設計在抽象的元件，以便我們在架構設計時將策略與細節分開，同時便於我們進行外掛式開發，SDP和SAP他們在目的上是一致的。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;看到了吧，這裡又提到了&lt;strong&gt;外掛式開發&lt;/strong&gt;這個詞，上次是在哪裡提到的？對，依賴反轉DIP，這也說明了SAP+SDP確實類似於元件DIP。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="主序列"&gt;主序列&lt;/h3&gt;
&lt;p&gt;把元件穩定性指標I與元件抽象程度指標A結合在一起，畫出一張圖，a=-i+1這條直線就叫做主序列線。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://tommycheese.github.io/blogimages/image-20240706171725797.png" alt="image-20240706171725797"&gt;&lt;/p&gt;
&lt;p&gt;整個區域可以分為三個區域：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;痛苦區：靠近(0,0)的區域，在這個區域內的元件十分穩定但同時又十分的具體，因此不易改變，如資料庫表；工具庫也是處於痛苦區的元件，雖然其 I 指標為 1（工具庫依賴非常多的元件，因此其不穩定），但不能被修改，否則很多程式碼會出現問題；&lt;/li&gt;
&lt;li&gt;無用區：處於(1,1)附近的元件，該位置上的元件通常是無限抽象的，但是沒有被其他元件依賴，這樣的元件往往無法使用，對於這個區域中的軟體元件來說，其原始碼或者類中的設計問題通常是由於歷史原因造成的，比如忘記清除的舊程式碼；&lt;/li&gt;
&lt;li&gt;主序列線區：在整條主序列線上的區域，元件所能處於最優的位置是線的兩端(0, 1)與(1, 0)。一個優秀的軟體架構師應該爭取將自己設計的大部分元件儘可能地推向這兩個位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼衡量元件的綜合性能呢？答案是計算它與主序列線的距離，來量化一個系統設計與主序列的契合程度，這個值為D值，值為0意味著元件是直接位於主序列線上的，值為1則意味著元件在距離主序列最遠的位置。
$$
D=|A+I-1|
$$
這樣我們就可以用D指標大於0多少來指導元件的重構與重新設計。&lt;/p&gt;
&lt;p&gt;除此以外，D還可以有更多的作用，比如計算設計中所有元件的D指標的平均值和方差，用統計學的方法來量化分析一個系統設計：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;對於一個良好的系統設計來說，D指標的平均值和方差都應該接近於0；&lt;/li&gt;
&lt;li&gt;方差作為元件的“達標紅線”來使用，我們可以通過它找出系統設計中那些不合常規的元件；&lt;/li&gt;
&lt;li&gt;還可以按照時間追蹤 D 的方差值，來觀察隨著時間的變化，軟體系統架構的穩定和抽象變化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（第四篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（三）SOLID 設計原則</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%89/</link>
      <pubDate>Fri, 05 Jul 2024 14:11:30 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%89/</guid>
      <description>&lt;h1 id="再读整洁架构之道三solid原则"&gt;再讀整潔架構之道（三）SOLID原則&lt;/h1&gt;
&lt;p&gt;構建軟體模組的主要目標有三個：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使軟體可容忍被改動&lt;/li&gt;
&lt;li&gt;使軟體更容易被理解&lt;/li&gt;
&lt;li&gt;構建可在多個軟體系統中複用的元件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SOLID原則的主要作用就是告訴我們如何將資料和函式組織成為類，以及如何將這些類連結起來成為程式。SOLID原則指導我們如何設計模組，在架構設計層面，我們會有其他的設計原則。&lt;/p&gt;
&lt;p&gt;SOLID原則是指單一職責原則&lt;strong&gt;S&lt;/strong&gt;RP、開閉原則&lt;strong&gt;O&lt;/strong&gt;CP、裡式替換原則&lt;strong&gt;L&lt;/strong&gt;SP、介面隔離原則&lt;strong&gt;I&lt;/strong&gt;SP和介面反轉&lt;strong&gt;D&lt;/strong&gt;IP。&lt;/p&gt;
&lt;h2 id="单一职责原则srp"&gt;單一職責原則SRP&lt;/h2&gt;
&lt;p&gt;SRP定義每個軟體模組只對一個功能負責，&lt;strong&gt;只有一個理由可以讓模組改變&lt;/strong&gt;，任何一個軟體模組都應該只對某一類行為者負責。&lt;/p&gt;
&lt;p&gt;元件層面的SRP被稱作共同閉包原則CCP。&lt;/p&gt;
&lt;p&gt;倘若設計模組時沒有遵循SRP會發生什麼呢？一起看一個例子。&lt;/p&gt;
&lt;p&gt;如果財務部門、人事部門、研發部門共同依賴於一個工資、工時計算的程式，程式包含三個函式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CalculateSalary：計算工資；&lt;/li&gt;
&lt;li&gt;CalculateTime：計算工時；&lt;/li&gt;
&lt;li&gt;Save：儲存資訊；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三個部門平靜地使用著程式，突然有一天，財務部的薪資計算發生變化，因此財務部門的維護人員更改了CalculateSalary函式；然後，詭異的事情發生了，人事部門、研發部薪資也同時變化了…&lt;/p&gt;
&lt;h2 id="开闭原则ocp"&gt;開閉原則OCP&lt;/h2&gt;
&lt;p&gt;“擁抱新增、抗拒修改！”&lt;/p&gt;
&lt;p&gt;OCP指出：如果軟體系統想要更容易被改變，那麼其設計就必須允許新增程式碼來修改系統行為，而非只能靠修改原來的程式碼。設計良好的計算機軟體應該易於擴充套件，同時抗拒修改。&lt;/p&gt;
&lt;p&gt;OCP不僅適用於類和模組的設計，也適用於元件的設計。他起源於裝置無關性的概念。具體來說，我們使用依賴反轉將IO裝置設計成外掛，這樣的我們就能很輕易的新增IO裝置，而不去修改原有的IO裝置。&lt;/p&gt;
&lt;p&gt;OCP的實現方式是通過將系統劃分為一系列元件，並且將這些元件間的依賴關係按層次結構進行組織，使得高階元件不會因低階元件被修改而受到影響。&lt;/p&gt;
&lt;h2 id="里式替换lsp原则"&gt;裡式替換LSP原則&lt;/h2&gt;
&lt;p&gt;LSP指出：如果想用可替換的元件來構建軟體系統，那麼這些元件就必須遵守同一個約定，以便讓這些元件可以相互替換。&lt;/p&gt;
&lt;p&gt;LSP的本質是一個可替換性原則：如果對於每個型別是S的物件o1都存在一個型別為T的物件o2，能使操作T型別的程式P在用o1替換o2時行為保持不變，我們就可以將S稱為T的子型別。&lt;/p&gt;
&lt;p&gt;LSP可以且應該被應用於軟體架構層面，因為一旦違背了可替換性，該系統架構就不得不為此增添大量複雜的應對機制。&lt;/p&gt;
&lt;h2 id="接口隔离原则isp"&gt;介面隔離原則ISP&lt;/h2&gt;
&lt;p&gt;ISP指出：應該在設計中避免不必要的依賴，任何層次的軟體設計如果依賴了它並不需要的東西，就會帶來意料之外的麻煩。&lt;/p&gt;
&lt;h2 id="依赖反转dip"&gt;依賴反轉DIP&lt;/h2&gt;
&lt;p&gt;DIP指出該設計原則指出高層策略性的程式碼不應該依賴實現底層細節的程式碼，恰恰相反，那些實現底層細節的程式碼應該依賴高層策略性的程式碼。&lt;/p&gt;
&lt;p&gt;通俗理解依賴反轉：這需要使用控制流和原始碼依賴流來說明。控制流譬如A元件流向B元件，此時B需要知道A的實現，A也需要引入B的模組，程式碼依賴上也就不可避免的A元件流向了B元件。此時系統行為決定了控制流，而控制流則決定了原始碼依賴關係，軟體架構就別無選擇。但是通過引入介面，B元件此時只需要實現介面，介面引入B，A引入介面，此時A就無需引入B模組，此時的控制流和原始碼依賴是反向的，也就是“依賴反轉”。&lt;/p&gt;
&lt;p&gt;依賴反轉給了軟體架構設計的自由，如果想要設計一個靈活的系統，在原始碼層次的依賴關係中就應該多引用抽象型別（介面、抽象類等），而非具體實現。&lt;/p&gt;
&lt;p&gt;DIP可以歸納出幾條編碼守則：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;應在程式碼中多使用抽象介面，儘量避免使用那些多變的具體實現類；&lt;/li&gt;
&lt;li&gt;不要在具體實現類上建立衍生類；&lt;/li&gt;
&lt;li&gt;不要覆蓋(override)包含具體實現的函式；&lt;/li&gt;
&lt;li&gt;應避免在程式碼中寫入與任何具體實現相關的名字，或者是其他容易變動的事物的名字。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（第三篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（二）程式設計範式</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%8C/</link>
      <pubDate>Fri, 05 Jul 2024 07:25:33 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%BA%8C/</guid>
      <description>&lt;h1 id="再读整洁架构之道二"&gt;再讀整潔架構之道（二）&lt;/h1&gt;
&lt;p&gt;程式設計範式指的是程式的編寫模式，它告訴我們應該在什麼時候採用什麼樣的程式碼結構。&lt;/p&gt;
&lt;p&gt;截止目前，一共出現了三種程式設計範式：結構化程式設計範式、物件導向程式設計範式和函數語言程式設計。&lt;/p&gt;
&lt;p&gt;作者認為，每種程式設計方式不是在給架構設計者的武器庫進行擴充，相反，架構師和程式設計師的武器已經夠多了，這三種程式設計範式是在對他們利用的武器進行&lt;strong&gt;限制&lt;/strong&gt;，這也是為什麼他們叫做“範式”。&lt;/p&gt;
&lt;h2 id="结构化编程范式"&gt;結構化程式設計範式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;結構化程式設計對程式控制權的直接轉移進行了限制和規範，特別指的是限制了程式中goto的隨意使用。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="分解程序"&gt;分解程式&lt;/h3&gt;
&lt;p&gt;Dijkstra希望使用數學推導方法對程式進行推理證明：讓程式就成為了一種歐幾里得結構，這樣可以用一些已經證明的結構串聯起新的程式，從而進一步推導整個程式的正確性。&lt;/p&gt;
&lt;p&gt;他還發現，如果程式中包含大量的goto，那麼程式會變得難以分解，而goto語句的作用完全可以依靠分支和迴圈來完成。&lt;/p&gt;
&lt;p&gt;這樣，結構化程式設計範式表明，程式可以進行降解拆分，一個大型問題拆分為一系列高階函式的組合，而這些高階函式各自又可以繼續被拆分為一系列低階函式，如此無限遞迴。更重要的是，每個被拆分出來的函式也都可以用結構化程式設計範式來書寫。&lt;/p&gt;
&lt;p&gt;但是，這種形式化證明的程式設計方式沒有成為主流，科學證明法是目前使用較多的方法。&lt;/p&gt;
&lt;h3 id="科学证明法"&gt;科學證明法&lt;/h3&gt;
&lt;p&gt;科學理論和科學定律可以被證偽，但是沒有辦法被證明，類似的，程式只能測試證偽，不能證明，即“測試只能展示Bug的存在，並不能證明不存在Bug”。因此，利用科學證明法，結構化程式設計範式就可以促使我們先將一段程式遞迴降解為一系列可證明的小函式，然後再編寫相關的&lt;strong&gt;測試&lt;/strong&gt;來試圖證明這些函式是錯誤的。如果這些測試無法證偽這些函式，那麼我們就可以認為這些函式是足夠正確的，進而推導整個程式是正確的。&lt;/p&gt;
&lt;h2 id="面向对象编程范式"&gt;物件導向程式設計範式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;物件導向程式設計對程式控制權的間接轉移進行了限制和規範。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;具體來說，物件導向程式設計凡事使用&lt;strong&gt;多型特性&lt;/strong&gt;限制了函式指標的使用。這可以理解為函式指標只能進行有限制的指向，比如java中的多型一般出現在有繼承關係的兩個類的物件之間。&lt;/p&gt;
&lt;p&gt;作者也認為多型是OOP的最大特性，利用多型可以做到&lt;strong&gt;依賴反轉&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;依賴反轉即引入介面，完全控制系統中所有原始碼的依賴關係，而不需要收到系統控制流的制約。&lt;/p&gt;
&lt;p&gt;依賴反轉保證了我們可以對系統進行外掛式的開發，如將web UI和資料庫與業務邏輯之間的依賴進行反轉，可以保證主業務邏輯與UI和資料庫的解耦，這樣UI和資料庫就成了主業務邏輯的外掛。&lt;/p&gt;
&lt;p&gt;物件導向程式設計就是以多型為手段來對原始碼中的依賴關係進行控制的能力，這種能力讓軟體架構師可以構建出某種外掛式架構，讓高層策略性元件與底層實現性元件相分離，底層元件可以被編譯成外掛，實現獨立於高層元件的開發和部署。&lt;/p&gt;
&lt;h2 id="函数式编程范式"&gt;函數語言程式設計範式&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;函數語言程式設計對程式中的賦值進行了限制和規範。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;換句話說，函數語言程式設計語言中變數是不可變的。&lt;/p&gt;
&lt;p&gt;作者有以下的觀點，這一段話來自原文：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“所有的競爭問題、死鎖問題、併發更新問題都是由可變變數導致的。如果變數永遠不會被更改，那就不可能產生競爭或者併發更新問題。如果鎖狀態是不可變的，那就永遠不會產生死鎖問題"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果不考慮儲存器與處理器的速度限制，那麼不可變性是可行的，但實際上我們必須考慮這些因素，因此可變性只能在一定程度上可行，需要讓不可變性在一定程度上可行需要完成&lt;strong&gt;可變性的隔離&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id="可变性的隔离"&gt;可變性的隔離&lt;/h3&gt;
&lt;p&gt;可變性隔離的一種常見方式是將應用程式，或者是應用程式的內部服務進行切分，&lt;strong&gt;劃分為可變的和不可變的兩種元件&lt;/strong&gt;。不可變元件用純函式的方式來執行任務，期間不更改任何狀態。這些不可變的元件將通過與一個或多個非函式式元件（即可變元件）通訊的方式來修改變數狀態。&lt;/p&gt;
&lt;p&gt;一個例子是GIT版本管理工具。它通過移動指標來記錄檔案的變化：增刪改，但實際上檔案並沒有被真正的修改和刪除，只存在增加和檢索查詢兩種情況，這不就是不可變性的一個例子嗎。&lt;/p&gt;
&lt;p&gt;同時，mysql中的事務管理、事務性記憶體的工作也都運用了這個思想。&lt;/p&gt;
&lt;h2 id="总结"&gt;總結&lt;/h2&gt;
&lt;p&gt;三種程式設計範式與軟體架構具有密切的關係：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多型是我們跨越邊界的手段；&lt;/li&gt;
&lt;li&gt;函數語言程式設計是我們規範和限制資料存放位置與訪問許可權的手段；&lt;/li&gt;
&lt;li&gt;結構化程式設計則是個模組的實現基礎；&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;三種程式設計範式與軟體架構的三大關注重點不謀而合：&lt;strong&gt;元件獨立性&lt;/strong&gt;（物件導向）、&lt;strong&gt;資料管理&lt;/strong&gt;（函式式）以及功能性（結構）。&lt;/p&gt;
&lt;h2 id="再次思考"&gt;再次思考&lt;/h2&gt;
&lt;p&gt;三種程式設計範式都對程式設計師提出了新的&lt;strong&gt;限制&lt;/strong&gt;。每個範式都約束了某種編寫程式碼的方式，沒有一個程式設計範式是在增加新能力。也就是說，程式設計範式帶來的是——什麼不應該做。&lt;/p&gt;
&lt;p&gt;（第二篇完）&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    <item>
      <title>再讀整潔架構之道（一）</title>
      <link>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%80/</link>
      <pubDate>Wed, 03 Jul 2024 23:10:20 +0800</pubDate>
      <guid>https://tommycheese.github.io/zh-TW/blogs/%E5%86%8D%E8%AF%BB%E6%95%B4%E6%B4%81%E6%9E%B6%E6%9E%84%E4%B9%8B%E9%81%93%E4%B8%80/</guid>
      <description>&lt;h2 id="前记"&gt;前記&lt;/h2&gt;
&lt;p&gt;為什麼是再讀？因為在這之前筆者已經閱讀過一遍架構之道這本書，但當時缺乏專案的鍛鍊和試錯，總覺得讀的不深，終於有幸在工作期間接觸到了一些專案和需求，更幸運的是專案組在開發時採用的架構正好是整潔架構，詳細的說，是事件驅動開發DDD+整潔架構，關於DDD的詳細內容將會在之後討論，筆者就重新閱讀了這本書📚，收穫頗豐，因此這一個系列，我準備用自己的拙見描述一下整潔架構，更準確的說，應該是是讀書筆記，以茲同仁。&lt;/p&gt;
&lt;p&gt;這本書的詳細地址為：&lt;a href="https://weread.qq.com/web/bookDetail/480322f072021a3248038c8"&gt;架構整潔之道-羅伯特 C. 馬丁-微信讀書 (qq.com)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;本系列會以圖書的章節組織作為思路，跟隨作者的腳步逐漸深入，同時，由於是再讀，所以不可避免的也會穿插一些圖書中其他部分的內容，所以，如果你在閱讀時發現了一些難於理解或者沒聽說過的內容，那它大機率是在之後章節中的內容。&lt;/p&gt;
&lt;p&gt;讓我們開始吧。&lt;/p&gt;
&lt;h2 id="设计与架构的含义"&gt;設計與架構的含義&lt;/h2&gt;
&lt;p&gt;設計與架構，本質上並沒有區別，底層設計細節和頂層架構資訊共同定義了軟體系統。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;軟體架構的終極目標是用最小的人力成本來滿足構建和維護系統的需求&lt;/strong&gt;，因此成本可以評估一個軟體架構設計的優劣。&lt;/p&gt;
&lt;p&gt;沒有經過設計、匆匆構建的系統可能成為&lt;strong&gt;亂麻系統&lt;/strong&gt;，亂麻系統在構建過程中被長期忽視程式碼質量和設計結構最佳化。認真設計軟體架構可以在一定程度上避免系統成為亂麻系統，從而降低成本。&lt;/p&gt;
&lt;p&gt;軟體開發具有兩個核心特點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要想跑得快，要先跑得穩；&lt;/li&gt;
&lt;li&gt;過度自信只會使得重構設計陷入和原專案一樣的困局。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="两个价值维度"&gt;兩個價值維度&lt;/h2&gt;
&lt;p&gt;軟體系統包含兩種價值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;行為價值：讓機器按照某種指定方式運轉，給系統的使用者創造或者提高利潤；&lt;/li&gt;
&lt;li&gt;架構價值：軟體要夠靈活，更改軟體的成本要做到與需求的範疇相關，與形狀無關。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;怎麼理解需求的範疇與形狀？
筆者自己理解範疇即需求大致的範圍和所屬&lt;strong&gt;領域&lt;/strong&gt;，而形狀指需求的細節內容。&lt;/p&gt;
&lt;p&gt;那麼哪個價值維度更重要呢？
作者認為架構價值比行為價值更重要，因為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果某程式可以正常工作，但是無法修改，那麼當需求變更的時候它就不再能夠正常工作了，我們也無法通過修改讓它能繼續正常工作。因此，這個程式的價值將成為0；&lt;/li&gt;
&lt;li&gt;如果某程式目前無法正常工作，但是我們可以很容易地修改它，那麼將它改好，並且隨著需求變化不停地修改它，都應該是很容易的事。因此，這個程式會持續產生價值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;業務部門與研發部門經常犯的共同錯誤是沒有把真正緊急並且重要的功能和緊急但是不重要的功能分開，結果就是重要的系統架構問題讓位給了不重要的系統行為功能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;平衡系統架構的重要性與功能的緊急程度這件事，是軟體研發人員自己的職責&lt;/strong&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;筆者注：同時，即便同樣是研發人員，不同的小組也會存在類似問題，正如某些前端部門認為後臺只需要提供一系列介面很容易，而不知道良好設計的重要性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="为好的软件架构持续斗争"&gt;為好的軟體架構持續鬥爭✊&lt;/h3&gt;
&lt;p&gt;如果忽視軟體架構的價值，系統將會變得越來越難以維護，終會有一天，系統將會變得再也無法修改。如果系統變成了這個樣子，那麼說明軟體開發團隊沒有和需求方做足夠的抗爭，沒有完成自己應盡的職責。&lt;/p&gt;
</description>
    <category>軟體架構</category><category>整潔架構</category></item>
    </channel>
</rss>
