軟體的終極目的是服務使用者,但在那之前,同一套軟體必須先服務開發者。尤其在強調 refactoring 的流程中,開發者會不斷重新排列、改寫每一個部分。當複雜行為的軟體缺乏好的設計,就難以 refactor 或組合元素——重複開始出現、monolithic 的設計元素無法被重新組合、開發者甚至害怕去看既有的混亂程式碼。

要讓專案隨著開發推進而加速,而非被自身的 legacy 拖垮,就需要一個令人愉快、歡迎變更的設計——一個 Supple Design

Supple Design 是 Deep Modeling 的互補。一旦挖掘出 implicit concepts 並使之 explicit,你就擁有了原料。透過 iterative cycle,你把這些原料錘鍊成有用的形狀:一個簡單而清晰地捕捉關鍵關注點的 model,以及一個讓 client developer 真正能運用 model 的 design。

開發者扮演兩種角色,設計必須同時服務二者:

  • Client developer:將 domain objects 編織進 application code,運用設計的能力。Supple Design 揭示深層的 underlying model,讓 client developer 能用最少的、鬆散耦合的概念,靈活表達領域中各種場景
  • 維護與變更的開發者:設計必須容易理解,追隨 deep model 的輪廓,使大多數變更在彈性點上彎曲,程式碼的效果透明可預測

沒有設計 Supple Design 的公式,但本章整理出一組 patterns,在作者經驗中,當它們適用時,往往能賦予設計 suppleness。

Figure 10.1: Some patterns that contribute to supple design

mindmap
  root((Supple Design))
    使用者端模式
      Intention-Revealing Interfaces
      Side-Effect-Free Functions
      Assertions
    設計原則
      Conceptual Contours
      Standalone Classes
      Closure of Operations
    進階應用
      Declarative Design
      結合 Specification

Intention-Revealing Interfaces#

在 Domain-Driven Design 中,我們想要思考有意義的 domain logic。如果程式碼產生規則的效果卻沒有明確陳述規則,就迫使我們以一步步的軟體程序來思考。沒有與 model 的清楚連結,就難以理解程式碼的效果或預測變更的影響。

物件的美妙之處在於封裝所有細節,讓 client code 變得簡單、可以用更高層次的概念來理解。但如果 interface 無法告訴 client developer 有效使用該物件所需的資訊,他就必須深入 internals——封裝的大部分價值就此喪失。

如果開發者必須考慮一個 component 的實作才能使用它,封裝的價值就失去了。如果非原始開發者必須從實作推斷一個 object 或 operation 的目的,他可能推斷出一個只是偶然被滿足的目的。

Kent Beck 提出了 Intention-Revealing Selector(Beck 1997)。設計的所有 public elements 共同組成其 interface,每個 element 的名稱都是揭示設計意圖的機會。Type names、method names、argument names 結合起來,形成一個 Intention-Revealing Interface

因此:

  • 為 classes 和 operations 命名時,描述其效果和目的,不要提及實現手段
  • 這些名稱應該符合 Ubiquitous Language,讓團隊成員能快速推斷含義
  • 在建立行為之前先撰寫測試,迫使思維進入 client developer 模式
  • 在 public interfaces 中,陳述 relationships 和 rules,但不說明如何執行;描述 events 和 actions,但不說明如何進行

範例:Paint-Mixing Application 的重構#

初始設計只有一個 domain class,唯一能猜到 paint(Paint) 方法做什麼的方式就是閱讀程式碼:

public void paint(Paint paint) {
   v = v + paint.getV();
   // Omitted many lines of complicated color mixing logic
   // ending with the assignment of new r, b, and y values.
}

透過撰寫測試來探索更好的 interface 設計,從 client developer 的角度出發:

public void testPaint() {
    Paint ourPaint = new Paint(100.0, 0, 50, 0);
    Paint blue = new Paint(100.0, 0, 0, 50);

    ourPaint.mixIn(blue);

    assertEquals(200.0, ourPaint.getVolume(), 0.01);
    assertEquals(25, ourPaint.getBlue());
    assertEquals(25, ourPaint.getYellow());
    assertEquals(0, ourPaint.getRed());
}

新的方法名 mixIn() 或許不能告訴讀者關於「混入」另一種 Paint 的所有效果(那需要 Assertions),但它足以讓讀者開始使用這個 class,並讓 client code 的讀者能解讀 client 的意圖。

整個 subdomains 可以被切分為獨立的 modules,並封裝在 Intention-Revealing Interfaces 背後。利用這種方式來聚焦專案和管理大系統的複雜度,將在第 15 章 Distillation 中進一步討論。

Side-Effect-Free Functions#

Operations 可以大致分為兩類:

  • Queries:從系統獲取資訊,可能只是存取變數中的資料,也可能基於資料執行計算
  • Commands(又稱 modifiers):影響系統狀態變更的操作

在電腦科學中,side effect 指對系統狀態的任何影響。本章的定義更窄:任何會影響未來操作的系統狀態變更

當涉及任意深度的巢狀呼叫時,預測所有後果變得非常困難。開發者呼叫的操作可能有二級、三級的效果——它們在各種意義上都變成了 side effects。沒有安全可預測的抽象,開發者就必須限制組合爆炸,為可行的行為豐富度設下低天花板。

不產生 side effects 而回傳結果的操作稱為 functions。Function 可以多次呼叫並每次回傳相同的值,可以呼叫其他 functions 而不必擔心巢狀深度,且更容易測試。

緩解 side effects 問題的兩種方式:

  1. 嚴格隔離 commands 和 queries:確保造成變更的方法不回傳 domain data,並保持盡可能簡單。所有查詢和計算都在不產生 observable side effects 的方法中執行
  2. 使用 Value Object 回傳計算結果:不修改現有物件,而是創建並回傳一個代表計算結果的新 Value Object

Value Objects 是 immutable 的,這意味著除了創建時呼叫的 initializers,它們的所有操作都是 functions。Value Objects 像 functions 一樣,使用更安全、更容易測試。

因此:

  • 將程式邏輯盡可能放入 functions——回傳結果且無 observable side effects 的操作
  • 嚴格將 commands 隔離為非常簡單的操作,且不回傳 domain information
  • 透過將複雜邏輯移入 Value Objects 來進一步控制 side effects

範例:再次重構 Paint-Mixing Application#

接續前面的例子,mixIn() 方法中做了很多事情:

public void mixIn(Paint other) {
   volume = volume.plus(other.getVolume());
   // Many lines of complicated color-mixing logic
   // ending with the assignment of new red, blue,
   // and yellow values.
}

Figure 10.5: The side effects of the mixIn() method

Color 是此領域中的重要概念。將它抽取為一個 explicit object——Pigment Color(而非泛稱 Color,因為顏料混色與 RGB 光混色不同)。Pigment Color 是一個 Value Object,因此應被視為 immutable。混色不會改變原有的 Pigment Color,而是產生一個代表新顏色的新物件:

public class PigmentColor {
    public PigmentColor mixedWith(PigmentColor other,
                                        double ratio) {
       // Many lines of complicated color-mixing logic
       // ending with the creation of a new PigmentColor object
    }
}

public class Paint {
    public void mixIn(Paint other) {
       volume = volume + other.getVolume();
       double ratio = other.getVolume() / volume;
       pigmentColor =
          pigmentColor.mixedWith(other.pigmentColor(), ratio);
    }
}

現在 Paint 中的修改程式碼盡可能簡單。新的 Pigment Color class 捕捉知識並顯式地溝通,提供一個 Side-Effect-Free Function,其結果容易理解、容易測試、且安全地與其他操作結合使用。

Assertions#

將複雜計算分離為 Side-Effect-Free Functions 可以縮小問題規模,但 Entities 上仍殘留著會產生 side effects 的 commands。Assertions 使 side effects 變得 explicit 且更容易處理。

當 side effects 僅由實作隱含定義時,大量委派的設計會變成因果的糾結。理解程式的唯一方式是追蹤分支路徑的執行——封裝的價值喪失,追蹤具體執行擊敗了抽象。

Design by Contract 學派更進一步,對 classes 和 methods 做出開發者保證為真的 assertions(Meyer 1988):

  • Post-conditions:描述操作的 side effects,呼叫方法後保證為真的結果
  • Preconditions:為了讓 post-condition 保證成立而必須滿足的條件
  • Class invariants:任何操作結束時,關於物件狀態的斷言。也可以為整個 Aggregates 宣告 invariants,嚴格定義 integrity rules

所有這些 assertions 描述的是狀態而非程序,因此更容易分析。如果你信任 post-condition 的保證,就不必擔心方法如何運作。

因此:

  • 陳述 operations 的 post-conditions 以及 classes 和 Aggregates 的 invariants
  • 如果無法直接在程式語言中編碼 Assertions,就撰寫 automated unit tests
  • 尋找具有一致概念集的 models,引導開發者推斷預期的 Assertions

範例:回到 Paint Mixing#

回顧 mixIn(Paint) 操作的模糊之處:receiver 的 volume 增加了 argument 的 volume,但 argument 本身的 volume 未被改變。以物理油漆的理解,混合過程應該耗盡另一桶油漆,使其 volume 歸零。

先陳述 post-condition:

After p1.mixIn(p2):
  p1.volume is increased by amount of p2.volume.
  p2.volume is unchanged.

問題在於這些屬性不符合我們邀請開發者思考的概念。但修改 argument 的 volume 為零也有問題——原來的設計者有充分理由:程式最終要報告被添加的未混合油漆清單(應用的終極目的是幫助使用者決定要混合哪些油漆)。

這個尷尬似乎指向缺失的概念。讓我們尋找新 model。

經過進一步的知識梳理和 refactoring to deeper insight,設計被拆分為 StockPaintMixedPaint。現在只有一個 command mixIn(),它只是將物件加入一個 collection——從對 model 的直覺理解就能看出效果。所有其他操作都是 Side-Effect-Free Functions

Figure 10.10: A class diagram with ASSERTIONS for Paint mixing

public void testMixingVolume {
   PigmentColor yellow = new PigmentColor(0, 50, 0);
   PigmentColor blue = new PigmentColor(0, 0, 50);

   StockPaint paint1 = new StockPaint(1.0, yellow);
   StockPaint paint2 = new StockPaint(1.5, blue);
   MixedPaint mix = new MixedPaint();

   mix.mixIn(paint1);
   mix.mixIn(paint2);
   assertEquals(2.5, mix.getVolume(), 0.01);
}

新 model 捕捉並溝通了更多的 domain 知識。Invariants 和 post-conditions 合乎常理,因此更容易維護和使用。

Intention-Revealing Interfaces 的溝通性、Side-Effect-Free Functions 的可預測性、以及 Assertions 的明確性,共同讓封裝和抽象變得安全。

Conceptual Contours#

有時人們把功能切得很細以允許彈性組合;有時又把它們大塊合併以封裝複雜度;有時追求一致的粒度。這些都是過度簡化,不適合作為通用規則。

  • 當 model 或 design 的元素被嵌入 monolithic construct 中,功能就會被重複,external interface 無法表達 client 可能關心的一切
  • 另一方面,過度分解 classes 和 methods 會不必要地複雜化 client,迫使 client objects 理解小碎片如何組合。更糟的是,概念可能完全喪失——「半個鈾原子不是鈾」

大多數 domains 深處存在邏輯一致性。當我們找到一個與 domain 某部分共鳴的 model,它更可能與我們後來發現的其他部分一致。這就是為什麼反覆 refactoring 最終會導向 suppleness——隨著程式碼適應新理解的概念或需求,Conceptual Contours 就會浮現。

High cohesionlow coupling 在所有尺度的設計中都扮演角色,但要避免滑入機械化的觀點。對於每個決定,問自己:「這是基於 current model 中特定關係的權宜之計,還是呼應了 underlying domain 的某個輪廓?」

因此:

  • 將設計元素(operations、interfaces、classes、Aggregates)分解為內聚單元,考慮你對 domain 中重要分界的直覺
  • 透過連續的 refactorings,觀察變化與穩定的軸線,尋找解釋這些剪切模式的 underlying Conceptual Contours
  • 將 model 與 domain 中一致的面向對齊

即使設計遵循了 Conceptual Contours,仍然需要修改和 refactoring。當連續的 refactoring 趨於局部化、不撼動 model 的多個廣泛概念時,這就是 model 契合度的指標。遇到迫使廣泛變更的需求,是我們對 domain 理解需要精煉的訊號。

範例:Accruals 的 Contours#

在第 9 章中,一個 loan tracking system 基於對會計概念更深入的理解而被重構。新 model 只比舊的多一個 object,但責任的劃分已大幅改變:

  • Schedules:原本透過 Calculator classes 的 case logic 來計算,現在被展開為不同類型的 fees 和 interest 的獨立 classes
  • Payments:之前分別處理的 fees 和 interest 的支付,現在被合併在一起

Figure 10.12: This model accommodates adding new kinds of Accrual Schedules.

開發者能自信預測的一個變更是新增 Accrual Schedules——那些需求已在等候。選擇的 model 使引入新 schedules 變得容易。

當後續需求要求處理提前和延遲付款的詳細規則時,開發者欣喜地發現幾乎相同的規則適用於 interest payments 和 fee payments,這意味著新 model elements 可以自然地連接到單一的 Payment class。這種擴展的容易度並非來自預測了變更,也不是因為設計如此萬能可以容納任何可能的變更——而是因為在先前的 refactoring 中,設計與 domain 的底層概念對齊了

Standalone Classes#

相互依賴使 models 和 designs 難以理解、測試和維護,而且相互依賴很容易堆積。

  • 每個 association 都是一個 dependency
  • 每個 method argument 的 type 也是 dependency
  • 每個 return value 也是

依賴數量的影響呈指數級增長:一個 dependency 需要同時思考兩個 classes;兩個 dependencies 需要思考三個 classes 及其相互關係;三個以上就像滾雪球。

即使在 Module 內部,解讀設計的困難度也會隨著 dependencies 的增加而急劇上升。Refined models 被蒸餾到每一個剩餘的連接都代表概念間的根本意義。在一個重要子集中,dependencies 可以被減少到,產生一個可以完全獨立理解的 class。

Implicit concepts(無論已識別或未識別)與 explicit references 一樣佔據心智負擔。例如,Paint 物件持有三個代表 red、yellow、blue 色值的 public integers。創建 Pigment Color 物件並沒有增加概念數量或 dependencies——它使已經存在的概念更 explicit、更容易理解。

因此:

  • 每個 dependency 都是嫌疑犯,直到被證明對 object 背後的概念而言是基本的
  • Low coupling 是 object design 的基本原則。當你能做到時,走到底——消除所有其他概念
  • 嘗試將最複雜的計算分解為 Standalone Classes,也許透過 modeling 由更多連接的 classes 持有的 Value Objects
  • 消除 dependencies 不應意味著任意地將一切還原為 primitives

Closure of Operations#

數學中的概念:如果取兩個實數相乘,得到的仍是實數——實數在乘法運算下是封閉的(closed)。

在軟體設計中,這個性質非常有用。XSLT 的基本用途是將一個 XML document 轉換為另一個 XML document——這種 XSLT 操作在 XML documents 的集合下是封閉的。Closure 極大地簡化了對操作的理解,且容易想像串連或組合封閉操作。

因此:

  • 在適當的地方,定義一個 operation,其 return type 與 argument(s) 的 type 相同
  • 如果 implementer 有參與計算的 state,那麼 implementer 實際上是操作的一個 argument,因此 argument(s) 和 return value 應與 implementer 同型
  • 這種操作在該型別的實例集合下是封閉的,提供 high-level interface 而不引入對其他概念的 dependency

此 pattern 最常應用於 Value Object 的操作。Entity 的 life cycle 在 domain 中有意義,你不能隨便憑空創造一個來回答問題。

當你實驗、尋找減少相互依賴的方式時,有時會到達此 pattern 的一半——argument 與 implementer 相同但 return type 不同,或 return type 與 receiver 相同但 argument 不同。這些不是封閉的,但它們確實提供了 Closure 的部分好處。

範例:從 Collections 中篩選#

在 Java 中,要從 Collection 選取子集需要使用 Iterator,逐一測試每個元素,將匹配的累積到新 Collection 中——引入了額外的概念 Iterator 及其機械複雜度。

在 Smalltalk 中,可以直接對 Collection 呼叫 select: 操作,傳入測試作為 argument,回傳只包含通過測試的元素的新 Collection。這些操作的 return value 與 implementer 匹配,可以串連起來如一系列 filters。它們不引入與篩選子集問題無關的額外概念。

Declarative Design#

Assertions 可以帶來更好的設計,但 handwritten software 無法有真正的保證。無論設計多麼 Model-Driven,我們仍然在寫程序來產生概念交互的效果,且花大量時間寫不增加意義的 boilerplate code。

Declarative Design 通常指一種將程式(或部分程式)寫成某種可執行規格的方式——對 properties 的精確描述實際控制軟體。這可以透過 reflection mechanism 或 compile time code generation 來實現。

然而,Declarative Design 在實踐中有其陷阱:

  • 宣告語言表達力不足,且框架使得超出自動化部分的軟體擴展非常困難
  • Code-generation 技術以破壞性方式將生成的程式碼合併到手寫程式碼中,破壞 iterative cycle

許多嘗試 declarative design 的意外後果是 model 和 application 的簡化(dumbing-down),開發者被框架限制所困,被迫執行 design triage 以交付成果。

Domain-Specific Languages#

一種有時是 declarative 的方法。Client code 以針對特定 domain 的特定 model 量身定制的程式語言編寫。程式極具表達力,與 Ubiquitous Language 有最強的連結。

但 DSL 也有缺點:

  • 要精煉 model,開發者需要能修改語言本身(語法宣告、語言解釋功能、底層 class libraries)
  • 在同一語言中實作 application 和 model 有其 seamlessness 的價值
  • 難以 refactor client code 以符合修訂的 model 及其關聯的 DSL

A Declarative Style of Design#

一旦你的設計具有 Intention-Revealing Interfaces、Side-Effect-Free Functions 和 Assertions,你就已踏入 declarative 的領域。許多 declarative design 的好處在你擁有可組合的元素——能溝通其意義、具有已特徵化或明顯的效果、或完全沒有 observable effects——時就已獲得。

延伸 Specifications 的 Declarative Style#

第 9 章涵蓋了 Specification 的基本概念。Specification 是已建立的形式化方法 predicate 的改編。Predicates 有其他有用的性質可以選擇性地利用。

使用 Logical Operators 組合 Specifications#

Predicates 可以用 ANDORNOT 操作來組合和修改。這些邏輯操作在 predicates 下是封閉的,因此 Specification 的組合展現了 Closure of Operations

public interface Specification {
   boolean isSatisfiedBy(Object candidate);
   Specification and(Specification other);
   Specification or(Specification other);
   Specification not();
}

應用範例——一種既 volatile 又 explosive 的化學品需要同時滿足 ventilated 和 armored 的 Specifications:

Specification ventilated = new ContainerSpecification(VENTILATED);
Specification armored = new ContainerSpecification(ARMORED);
Specification both = ventilated.and(armored);

或者,如果有多種 ventilated Container,某些物品可以放入任一種:

Specification either = ventilatedType1.or(ventilatedType2);

禁止將沙子存放在特殊容器中:

Specification cheap = (ventilated.not()).and(armored.not());

從簡單元素構建複雜 specifications 的能力增加了程式碼的表達力,組合以 declarative style 撰寫。

Figure 10.14: COMPOSITE design of SPECIFICATION

實作方式是透過 AndSpecificationOrSpecificationNotSpecification 等 classes,每個都實作 AbstractSpecification,形成 Composite pattern。實作可以有多種方式——重要的是一個捕捉 domain 關鍵概念的 model,以及忠於該 model 的實作。

完整的通用性並非總是必要的。特別是 AND 比其他操作更常被使用,且產生較少的實作複雜度。不要害怕只實作 AND,如果那就是你需要的全部。

Subsumption#

這個功能通常不需要且可能難以實作,但偶爾能解決真正困難的問題。

考慮化學倉庫打包器:每個 Chemical 有一個 Container Specification,Packer Service 保證所有 Drums 被分配到滿足規格的 Containers。但當法規改變時,使用者希望能產出一份現在有更嚴格要求的化學品類型清單

引入新操作:

boolean subsumes(Specification other);

更嚴格的 Spec subsumes(涵蓋)較不嚴格的——它可以取而代之而不遺漏任何先前的要求。

Figure 10.15: The SPECIFICATION for a gasoline container has been tightened.

在 Specification 的語言中,新 Specification subsumes 舊 Specification,因為任何滿足新 Spec 的 candidate 也會滿足舊的。如果把每個 Specification 視為 predicate,subsumption 等同於 logical implication

對於只使用 AND 操作的 Composite Specification,證明 subsumption 相對簡單:只需檢查 subsuming Specification 的 leaf Specifications 是否是 subsumed 的 leaf Specifications 的超集

當 OR 和 NOT 被納入時,這些證明變得複雜許多。在大多數情況下,最好透過做出選擇來避免這種複雜度——要嘛放棄部分操作符,要嘛放棄 subsumption。

Angles of Attack#

面對龐大系統時,不能一次處理整個設計。需要選擇目標,以下是兩個廣泛的方法:

Carve Off Subdomains#

逐步啃咬系統。可以把 model 中可視為專門數學的部分分離出來;應用程式執行複雜的狀態變更規則時,把它拉到一個獨立的 model 或簡單 framework 中,讓你宣告規則。每一步不僅新 module 變乾淨,留下的部分也更小更清晰。

在一個領域產生大的影響,使設計的一部分真正 supple,比把努力分散各處更有用。

Draw on Established Formalisms#

從零開始創建緊密的概念框架不是每天都能做的事。但你常常可以使用和改編在你的 domain 或其他 domain 中早已建立的概念系統。許多商業應用涉及會計——會計定義了一套完善的 Entities 和規則,容易改編為 deep model 和 supple design。

作者個人最愛的 formalism 是數學。許多 domains 的某處包含數學——找到它、挖掘出來。專門的數學是乾淨的、可按清楚規則組合的,且人們覺得容易理解。

範例:整合所有 Patterns —— Shares Math#

第 8 章講述了一個建構 syndicated loan system 的 model breakthrough 故事。此範例深入其中一個功能:當借款人進行 principal payment 時,資金按 lenders 在 loan 中的份額按比例分配

初始設計#

Loan class 的 distributePrincipalPayment() 方法既計算份額分配又修改 Loan——這是危險的。

分離 Commands 和 Side-Effect-Free Functions#

重構後分為兩個方法:

Map distribution =
   aLoan.calculatePrincipalPaymentShares(paymentAmount);
aLoan.applyPrincipalPaymentShares(distribution);

Functions 已將大量複雜度封裝在 Intention-Revealing Interfaces 背後。但當加入 applyDrawdown()calculateFeePaymentShares() 等時,程式碼開始膨脹。

Making an Implicit Concept Explicit#

Share objects 在實作中是被動的,以複雜、低層次的方式被操縱——因為大多數關於 shares 的規則和計算不適用於單一 share,而是作為整體的份額組合。缺失的概念是:shares 作為組成整體的部分而相互關聯。

引入 Share Pie:代表特定 Loan 的總分配。它是一個 Entity,其 identity 在 Loan 的 Aggregate 中是 local 的。

Share Pie 成為 Value Object:洞察的連鎖反應#

將 Share Pie 改為 Value Object 意味著它必須是 immutable。increase()decrease() 不再被允許;要改變值就必須替換整個 Pie。進一步走向 Closure of Operations——不是「增加」Share Pie 或添加 Shares,而是將兩個 Share Pies 相加:結果是新的、更大的 Share Pie。

Shares Math 的四個操作:

  • plus(SharePie):兩個 Pies 的組合是每個 owner 的 share 的組合
  • minus(SharePie):兩個 Pies 的差是每個 owner 的 share 的差
  • prorated(double):金額可按所有 shareholders 的比例分配
  • getAmount():整體等於各部分之和

新設計的 Suppleness#

Loan class 的方法變得如此簡潔:

public SharePie calculatePrincipalPaymentDistribution(
                                     double paymentAmount) {
   return shares.prorated(paymentAmount);
}

public void applyPrincipalPayment(SharePie paymentShares) {
   setShares(shares.minus(paymentShares));
}

每個短方法都陳述其意義。Applying a principal payment 意味著從 loan 逐份額減去付款。Distributing a principal payment 是按 shareholders 比例分配金額。

其他交易類型也能輕鬆宣告。例如,計算偏差——每個 lender 與其約定貢獻的偏差:

SharePie originalAgreement =
   aFacility.getShares().prorated(aLoan.getAmount());
SharePie actual = aLoan.getShares();
SharePie deviation = actual.minus(originalAgreement);

此設計之所以能如此容易地重新組合和在程式碼中溝通,歸功於以下特性:

  • 複雜邏輯被封裝在專門的 Value Objects 中,使用 Side-Effect-Free Functions。因為 Share Pies 是 Value Objects,數學操作可以自由創建新實例來替換過時的。plus()minus()prorated() 可以自由用於中間計算,期待它們如其名稱所暗示的那樣運作,且不做更多
  • 狀態修改操作簡單且以 Assertions 特徵化。Shares Math 的高層抽象允許交易的 invariants 以 declarative style 簡潔撰寫
  • Model 概念解耦;操作牽涉最少的其他型別。Share Pie 的部分方法展現了 Closure of Operations。Share Pie 只與一個其他 class(Share)密切互動,因此它是自包含的、容易理解、容易測試、容易組合以形成 declarative transactions
  • 熟悉的形式化使協定容易掌握。人們看到 Shares Math 就認出一個已知的系統,且因為設計小心地與算術規則保持一致,那些人不會被誤導

把問題中對應於數學形式化的部分抽取出來,我們為 Shares 達到了 supple design,進一步蒸餾了核心的 Loan 和 Facility 方法。Supple Design 對軟體應對變更和複雜度的能力有深遠影響,它常常取決於相當細節的 modeling 和 design 決策。