DDD 主客體命名法
關注公衆號:DDD 和微服務
微信號:linksgo2016
同名知乎:少個分號
- 主客體命名法
曾經我也糾結命名的問題,想使用簡單快速地方法解決,但是由於沒有意識到命名也是設計的一部分,爲此吃了不少的虧。我希望讀到這篇文章的朋友不要犯我同樣的問題,命名真的不是通過某些 ”AI“ 工具、插件能自動生成這麼簡單。
在這套方法裏面,我延續主客體思維,如果不瞭解的朋友可以先去看 《主客體建模法》。使用主客體命名法,不僅可以寫出主謂賓結構的語句,還能通過命名改善軟件設計。
主客體命名法可以做到下面這樣,代碼如詩一樣優美。
首先,我們將需要起名字對象進行分類:
-
對客體命名
-
對主體命名
-
對行爲命名
對客體命名
根據 DDD 的統一語言原則,名詞往往代表着一個業務概念,並需要在團隊中和開發人員、業務人員對齊。編程就是使用特定的算法操作一組數據,這些數據代表着業務中的某些概念。
讀過《主客體建模法》的朋友可能知道我要說什麼了,這些概念就是代碼中的客體。如果想要獲得良好的命名,就需要對這些概念進行定義。
一個對象就是一個概念,對象中的屬性就是這個概念的內涵,這個對象被用來表達的範圍就是它的外延。
這裏需要普及一下邏輯學中內涵和外延。內涵是指一個概念的典型特徵,外延是指它能描述事物的集合。比如兔子有長長的耳朵是內涵,兔子在地球上指代的動物就是它的外延。
當我們說白色的兔子不是兔子的時候,說的是 “兔子” 這個概念不是 “白色兔子” 的概念;當我們說白色的兔子是兔子的時候,說的是 “白色兔子” 概念表達的集合是概念 “兔子” 表達的集合的子集。
所以對客體起名字的關鍵在於定義這個客體的概念,使用擬物的方式起名。
我們可以通過概念圖(可以搜索概念圖相關的文章)來定義,也可以直接用語言來表達。比如當我們給系統中用戶相關起名字的時候可以這樣定義:
-
用戶:在系統中用來標識軟件使用者身份的對象,可以通過關鍵屬性來進行登錄。
-
客戶:在系統中關於參與人的個人資料,不具備登錄能力,客戶可以關聯用戶也可以不關聯。
-
賬戶:用戶擁有用於存放資金的對象,關鍵屬性爲餘額。
-
用戶組成員:用戶在某個用戶組下的身份,持有這個用戶組的權限。
-
商戶:在系統中表達一個資源的空間,在實際業務中對應法人。
-
商戶管理員:用戶在一個商戶下的身份,具有管理這個商戶資源的權限。
對於容易混淆的” 地址 ",也可以這樣定義:
-
地址:地址庫中的地址,屬於站點元數據。
-
用戶地址:用戶個人資料下保存的地址,可能引用自地址庫也可以不引用。
-
收貨地址:在訂單中使用的地址,可以引用自用戶地址也可以不引用。
對主體命名
在代碼操作中操作這類客體的就是主體,那麼主體怎麼命名呢?
其實很簡單,我們只需要區分好他們的功能就行了。假如有 A、B、C 三個人去荒野求生,他們到了一個小島靠打獵爲生。A 負責打獵,B 負責加工,C 負責存儲。反應快的朋友可能知道我要說什麼了,這不就是代碼中的分層嗎。看看這樣命名是否合適:
-
A:Hunter
-
B:Processer
-
C:Storekeeper
看下我們代碼是不是類似的:
-
負責處理 API 請求的類叫做 Controller
-
處理業務邏輯的類叫做 Service
-
負責生成 SQL 的類叫做 Mapper
所以對主體起名字的關鍵在於定義他們的能力或者職責,然後使用擬人的方法起名。
對行爲命名
有了主體、客體,只要給行爲一個動詞,也就是我們的方法名,我們就可以像主謂賓一樣寫出句子了,是不是很簡單?
但是這個時候很多朋友就犯難了,我除了會 get、take、do 這類詞彙之外,找不到其它詞彙了。
實際上這是對業務理解不夠,或者英語詞彙量的限制。這類詞彙在英語中叫做小詞,往往威力無窮,但表達能力拉胯。這裏介紹一個學習英語的技巧,如果我們出國旅遊,其實也只需要 get、take、do、I、it 等幾個詞就夠了。如果想要買東西,就指着想要買的東西說,I take it,老闆自然就知道你的意思。然後不斷用更準備的詞去代替這些詞,然後英語就可以漸進提升。
英語的學習的關鍵不是背單詞,關鍵在於表達能力。但是不使用更準確的詞彙,表達能力就會受限。同理,我們可以使用 doXXX 來完成所有的業務,也能寫出整潔的代碼,但是表達能力非常弱。
所以對方法進行命名,只需要找一個合適的動詞即可。
那麼,動詞如果真的不夠用怎麼辦?
試想,如果有兩個方法,類名、方法名、參數都相同,那麼需要思考一個問題,這兩個方法的區別是什麼?這也是方法簽名爲什麼這樣定義的原因。
- 關於命名的反模式
下面通過一些命名的反模式,來對比主客體命名法的優點。
命名毫無意義
使用 a、b、c 進行命名,就像四川人使用 “大娃、二娃、幺娃子” 來命名一樣,只能算小名,沒人能看得懂。
還有使用拼音(甚至粵語拼音)、符號、不統一的風格,批評這類命名的文章已經很多了,不是本文的重點。
不遵守主客思維
下面幾個是需要重點介紹的反模式。
不遵守主客思維的命名有拿物品作爲主語,這類命名我稱爲 “成精” 命名法。比如我總喜歡用的例子,訂單中的結賬方法、商品中的發貨方法,可讀性非常差。
提示一下,由於主客體具有相對性,擬人的不一定不能作爲客體,就好比理髮師也能被其他理髮師理髮一樣。但是主體我們儘量使用擬人法,特殊情況是當一個對象操作它自己的屬性時候,我們能看做一個局部的主客關係,也能作爲主體。
過於抽象
在一個系統中,如果看到命名全是 xxxData、xxxMessage、xxxInfo 等非常通用和抽象的詞彙,基本沒有表達能力,造成混亂。
這是由於我們對客體認識不足造成的,按照前面對客體進行重新定義,這也是設計的一部分。
主體或者客體冗餘
在主客體命名法中,行爲只需要一個動詞,或者動詞短語即可,如果你的方法名形如:
-
createUser 保存用戶
-
merchantUpdate 商戶更新
當我們的方法被調用時,帶上參數,會看起來彆扭:
orderService.createUser(user)
如果能熟練的掌握主客體命名法,就能寫出這樣的代碼:
orderService.create(user)
如果主體、客體能表達完整的含義,行爲就是用一個動詞即可;如果不能,就使用一個動詞短語。
- 起名字可以反思設計
很多建模和架構問題甚至不需要費神去解決,找到一個恰如其分的名字可能就解決了。
命名是編程中非常讓人頭疼的事情,但是你可能不會相信,取一個好名字你的建模問題也解決了,這個問題說起來還真是挺有意思,否則也不值得寫一篇文章了。
在保險領域,業務有一個需求是在正式提交簽約後,保單才具有法律效應,正式生效。但是在受理簽約之前,用戶會提交一些材料,這些材料幾乎和最終的保單一模一樣。
最初的開發人員設計了 Policy 這個對象,並增加了一個狀態屬性,但狀態爲生效後保單才成爲合法的保單。這樣做看起來沒有問題,但是隨着業務的變化,簽約前和簽約後慢慢開始有了差異,仍然使用 Policy 這個對象不是很好。開發人員準備準備將這些差異分離,這個時候出現了兩個派別,併發生了爭吵。
主分派:簽約前後,這是兩個不同對象應該分離。
主合派:他們明明都是 Policy,怎麼能分了,再說分開了簽約前叫什麼呢?
主分派:…… 好像確實不知道叫什麼。
主合派:看吧,你都不知道叫什麼,還是別拆吧。
這類對話在我培訓或者諮詢工作中,聽到不下 10 次,如果有明確的命名來區分概念,往往大家很認同拆分,但是在不知道如何起名的時候,問題就變得模棱兩可。
所以說,命名的問題,本質是一個設計問題。
上面問題最終通過找到一個業界公認的詞彙得以解決——投保單,英文中叫做 insurance slip。類似的概念還有客戶、用戶、賬戶的三戶設計,當我們找到了命名後,建模問題往往迎刃而解。
小的時候幾乎每家都有一本書《姓名與人生》,用來給新生兒起名字。它提供了一套根據筆畫來判斷名字是否足夠好的理論,雖然現在看來有點扯,但是也意味着人們對名字的重視情況。
優秀的開發者對待命名應該像對待自己孩子的名字一樣,畢竟他們有一個共同點就是,被廣泛使用後基本上很難被修改。
在開源社區,優秀的圖形庫 mxGraph 爲了命名這事兒討論了好幾年,直到 2021 纔開始下決心修改它。
“反者道之動”,反過來想也能幫我們更好的發現問題。
當我們無法給出一個良好的命名的時候,反過來思考,當我們無法給出一個良好的命名的時候,是不是意味着我們的軟件設計需要改善呢?
- 總結
-
給行爲的主體命名,使用擬人法,想象在給人起名字。比如 Manager、Handler、Controller。
-
給行爲的客體命名,使用擬物法,想象在給物品起名字。
-
對行爲起一個符合主體、客體 ” 身份 “的動詞,比如 handle、save、process 等。
-
嘗試連成一個句子,避免冗餘和重複。主體 + 行爲 + 客體 = 主謂賓結構。
-
使用具象化的命名,不使用抽象的名字。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/YyYxaCKhJOLEB6fyBjyQpg