為什麼需要 Connascence#
Chapter 5 介紹的 module coupling 是在程序式(procedural)語言時代被提出的。物件導向(object-oriented)的興起讓我們需要更細緻的模型,能描述物件互動中各種微妙差異。Meilir Page-Jones (1996) 提出的 connascence(共生)正是回應這個需求。
「Connascence」源自拉丁文,意為「同生」。在軟體設計中,兩個模組「共生」表示它們的生命週期糾纏在一起:其中一個改動,另一個就要跟著改、或至少要仔細審查可能的破壞性變更。
Connascence 比 module coupling 更細緻,分成兩大類:
- 靜態共生(Static connascence):原始碼層級、編譯期的關係
- 動態共生(Dynamic connascence):執行期的行為依賴
靜態共生#
依強度由弱到強:name → type → meaning → algorithm → position。越靠後越隱晦、共享越多知識。

Figure 6.1: 不同層級靜態共生所需共享知識的對應關係
Connascence of Name(名稱共生)#
最弱層級。模組必須對「名稱」達成共識——變數、方法、服務、欄位的名稱。
def greet(name): # 01
message = f'Hello, {name}!' # 02
print(message) # 03
greet('world') # 05可觀察到三組共生關係:
- 第 1、2 行 → 共享參數名稱
name - 第 2、3 行 → 共享變數名稱
message - 第 1、5 行 → 共享方法名稱
greet
任何一個名稱變更,就有兩處需要同步修改。
Connascence of Type(型別共生)#
模組必須對「型別」達成共識:
private static void Greet(string name) {
string message = $"Hello, {name}";
Console.WriteLine(message);
}
static void Main() {
Greet("world");
}強型別把原本的 name 共生升級成 type 共生。即使是動態語言,型別假設仍然存在——只是延後到執行期才暴露。
Type 比 name 略強,但兩者通常成對出現。
Connascence of Meaning(意義共生)#
模組對「特定值的意義」達成共識——也就是傳遞 magic value:
fun processEmail(msg: EmailMessage, caseId: CaseId) {
val supportCase = repository.load(caseId)
supportCase.appendResponse(msg.body, newStatus = 7) // 7 是什麼?
}Magic value 無法被編譯器驗證,介面顯式程度比 name / type 更低。
可以透過提取常數或引入列舉,把 connascence of meaning 降級成 type 或 name:
enum class Status {
Open, FollowUp, OnHold, Escalated, Closed, Resolved, Reopened
}
supportCase.appendResponse(msg.body, newStatus = Status.FollowUp)Connascence of Algorithm(演算法共生)#
模組必須對「使用同一個演算法」達成共識,才能解讀彼此交換的值:
- 雙方加解密必須用同一套演算法
- 計算 checksum 用 MD5,遠端儲存若用其他雜湊就對不上
fun uploadFile(filePath: String) {
val data = readFile(filePath)
val checksum = calculateMD5(data)
storage.upload(data, checksum)
}Connascence of algorithm 不是「程式碼重複」的問題。重點是「需要同一個演算法才能彼此理解」,演算法存在第三方函式庫或重複實作都一樣。重複的業務邏輯則是更強的層級。
Connascence of Position(位置共生)#
最強的靜態層級。模組必須對「元素的順序」達成共識:
fun sendEmail(data: Array<String>) {
val from = data[0]
val to = data[1]
val subject = data[2]
val body = data[3]
}或是「同型別多個未命名參數」:
fun sendEmail(from: String, to: String, subject: String, body: String) { ... }或是「未命名 tuple 回傳」:
fun getCurrentDateTime(): Pair<DateTime, DateTime> {
return Pair(DateTime.Now, DateTime.UtcNow)
}Position 共生讓整合介面非常脆弱:簡單地調換順序就會破壞所有整合者。看起來與 connascence of name 差不多,但兩者一個顯式、一個隱式,可靠性差距巨大。
動態共生#
動態共生描述執行期的行為依賴,比所有靜態共生都強。由弱到強:execution → timing → value → identity。

Figure 6.2: 不同層級動態共生所需共享知識的對應關係
Connascence of Execution(執行共生)#
模組必須以特定順序執行(靜態 position 的動態對應):
interface DbConnection {
fun openConnection()
fun beginTransaction(transactionId: UUID)
fun executeQuery(sql: String): QueryResult
fun commit(transactionId: UUID)
fun rollback(transactionId: UUID)
fun closeConnection()
}執行順序的隱藏規則:
- 所有方法都必須在
openConnection之後 closeConnection之後不可再執行任何方法commit/rollback必須在beginTransaction之後executeQuery必須在交易開始後、提交或回滾前
Connascence of Timing(時序共生)#
不只順序,還要在「特定時間間隔」內:
- DB 連線開了 30 秒沒動作就 timeout
- 車門解鎖後若一段時間沒開門就自動上鎖
- X 光機啟動後 N 秒自動關閉
隱晦的例子:依賴系統時鐘多次取值
fun getTime(): Pair<Int, Int> {
val hour = DateTime.Now.Hour
val minute = DateTime.Now.Minute
return Pair(hour, minute)
}兩行之間若被作業系統 delay 或剛好遇到 daylight saving time 切換,會回傳錯誤結果。可以重構成只取一次:
fun getTime(): Pair<Int, Int> {
val now = DateTime.Now
return Pair(now.Hour, now.Minute)
}但 DB timeout 這種就無法重構掉,因為它本身就是業務需求。
Connascence of Value(值共生)#
不同欄位的「值」必須一起變動,才能維持系統處於正確狀態。
- 算術約束:例如三角形三邊長必須符合「兩邊和大於第三邊」
- 業務不變量:例如「客戶必須先 verified 才能開啟 priority shipping」

Figure 6.3: 三角形邊長必須同時滿足的數學約束
class Customer {
var isVerified: Boolean = false
var priorityShippingEnabled: Boolean = false
fun clearVerification() {
isVerified = false
priorityShippingEnabled = false // 必須一起改
}
fun allowPriorityShipping() {
if (isVerified) {
priorityShippingEnabled = true
}
}
}Connascence of Identity(身分共生)#
最強的層級。多個模組必須引用「同一個」第三方物件實例才能正常運作。
- Object:多個物件必須共用同一個 DB connection pool
- 分散式系統:兩個服務透過讀寫同一個資料庫整合,且都期待立即看到對方的變更

Figure 6.4: 兩個服務透過共用資料庫整合所形成的身分共生
透過 message bus 整合、不要求事務一致性 → 不算 connascence of identity。
Connascence 的整體層級#
兩個模組之間若同時存在多種 connascence,整體層級取「最高」的那一個。
範例:
(res, balance, tran_id) = accounting.process_payment(
account_id='LVG141028',
transaction_type=3,
credit_card='S5hDn175mPiDL4D5ftbtMw=='
)可同時觀察到:
- Name:方法與參數名稱
- Type:
account_id、credit_card是字串、transaction_type是數字 - Meaning:
transaction_type=3是 magic value - Algorithm:
credit_card是 AES 加密過的,雙方須用同一演算法 - Position:回傳的 tuple 順序
整體層級取最高 → connascence of position。
管理 Connascence#
簡單重構就能降級:
- 用 enum / 命名常數 → meaning 降為 type
- 用 named arguments → position 降為 name
不要把「降到 name / type」當成設計目標。許多高層級共生是業務本質決定的,無法被重構消除:timing、algorithm、execution、value、identity 都可能屬於這類。
高層級共生的元件「真的是同生的」,不該被分開——它們應該被放在彼此附近。這是 Chapter 8(距離)與 Chapter 10(平衡)的伏筆。
Connascence 與 Module Coupling 的對照#
| Module Coupling | 對應 Connascence |
|---|---|
| Data | 任何靜態共生(含 position)皆可 |
| Stamp | 至少 type;同樣不超出靜態共生 |
| Control | 沒有對應的 connascence 層級 |
| External | Identity(共享狀態) |
| Common | Identity(共享狀態 + 結構知識) |
| Content | 僅 name(讀私有欄位只需要欄位名) |
Content coupling 在 module coupling 是最強,但在 connascence 是最弱——這代表兩個模型描述的根本是不同面向的耦合!多數 dynamic connascence 在 module coupling 中也找不到對應。
重點整理#
Connascence 的兩大主題:
- Static:要編譯通過、要溝通成功,雙方介面層面要協調的事——name、type、meaning、algorithm、position
- Dynamic:要在執行期運作正確,雙方行為要協調的事——execution、timing、value、identity
下一章將直面 module coupling 與 connascence 的衝突,把兩者整合成更實用的 Integration Strength 模型。