離職後的君子之交淡如水 X 最小知識原則 三分鐘斷捨離,讓每天都早點下班

me
林彥成
2023-10-07 | 3 min.
文章目錄
  1. 1. Least Knowledge Principle

假設住在一個家庭中,家中有不同的成員,每個人都有自己的物品需要收納,例如衣物、書籍、玩具等。

如果家中的每位成員都知道其他成員物品的詳細位置和收納方式且可以修改,最終就需要相互詢問才能找到自己需要的物品,因為每個人都需要記住其他人的物品放在哪裡,互相之間的合作變得複雜。此外,當一個人重新安排或調整了自己的物品時,其他人可能會受到影響,因為他們習慣了特定的收納方式。

家中的不同收納區域代表了一個複雜的收納系統,而每位家庭成員則代表系統中的不同模組或類別。在軟體開發中,這種情況就好比模組之間直接相互依賴,彼此知道對方的內部實現方式,這樣一來,任何變更都可能影響到其他模組,導致系統變得脆弱且難以維護。

Least Knowledge Principle

最少知識原則 Least Knowledge Principle 又叫做 Law of Demeter 認為一個物件 (或模組)不應該了解太多關於其他物件的細節,每個單元只能和自己直接的朋友交談,不能和陌生單元交談。

用去耦合的概念來看,物件間都應該君子之交淡如水,不需要知道太多對方的相關情況。

舉個同事離職的情況來說,人與人之間不需要講得太清楚,同事之間禮貌上交代離職原因就是說出考量家庭因素、生涯發展、個人能力這幾項原因即可,並不用詳細的交代去向還有真正的離職原因。

因為即使清楚知道同事真正的離職原因你也不一定會過得比較好,因為當發現離職同事表現很優秀但年度調薪只有 500 塊,整天跟老闆聊天喝下午茶的調薪 5000 塊,甚至同事交代離職原因也可能會影響團隊的工作氛圍,因為同事們離職後下一個工作年薪平均 +50%-100%,這個就是在工作上過度理解其他模組的壞處。

最少知識原則在工作上就是將他的工作進度、技術細節和專案細節都記錄在文件中,並確保其他同事可以輕鬆地查詢這些資訊,離職當下僅僅需要透過文件不需要花費過多精力去了解對方,這有助於降低耦合度,提高程式碼的可維護性和可讀性。

以之前的例子來說明:

  • 玩家物件: 封裝了自己的屬性和方法,與其他玩家無關
  • 建立函式: createPlayer 和 createAdvancedPlayer 函數只需要知道有關自己的基本屬性和檢查方法,與其他玩家無關
  • 資料處理: processPlayerData 只關心 Player 介面提供的 checkPlayerCondition 和 checkAdvancedPlayerCondition 方法,不需要知道特定玩家物件的內部結構,僅使用抽象的介面提供的公開函式

在軟體開發中,每個模組只關心自己的內部實現,不需要知道其他模組的詳細資訊,變更一個模組不能對其他模組造成意外的影響。離職的同事當然也是。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 定義一個函數,用於檢查玩家的健康狀態
function isHealthy(player) {
return player.health > 10;
}

// 定義一個函數,用於檢查玩家的名稱和傷害值
function hasDesiredNameOrLowDamage(player) {
return player.name === "foo" || player.damage < 5;
}

// 定義一個函數,用於檢查玩家的名稱和敏捷度
function hasDesiredNameOrHighAgility(player) {
return player.name !== "bar" || player.agility > 20;
}

// 定義一個基本玩家函數,接受包含玩家屬性的物件作為參數
function createPlayer(playerData) {
const { name, health, damage } = playerData;
return {
name,
health,
damage,

// 通用的檢查函數,每個玩家都可以使用
checkPlayerCondition() {
return isHealthy(player) && hasDesiredNameOrLowDamage(player);
},
};
}

// 定義進階玩家函數,接受包含進階玩家屬性的物件作為參數
function createAdvancedPlayer(advancedPlayerData) {
const { name, health, damage, agility } = advancedPlayerData;
const player = createPlayer({ name, health, damage });
return {
...player,
agility,

// 進階玩家特有的檢查函數
checkAdvancedPlayerCondition() {
return hasDesiredNameOrHighAgility(player);
},
};
}

// 通用的處理玩家資料函數,接受任何類型的玩家物件作為參數
function processPlayerData(player) {
if (player.checkPlayerCondition()) {
// 做一些基本玩家的操作
console.log(`${player.name} 符合基本條件`);
}

if (
player?.checkAdvancedPlayerCondition &&
player?.checkAdvancedPlayerCondition()
) {
// 做一些進階玩家的操作
console.log(`${player.name} 符合進階條件`);
}

// 非常長的函數內容...
if (statusCode === 20100) {
// ...
}

if (statusCode === 20101) {
// ...
}
}

// 建立玩家物件,使用包含屬性的物件作為參數
const player1 = createPlayer({
name: "玩家1",
health: 100,
damage: 10,
});
const player2 = createAdvancedPlayer({
name: "玩家2",
health: 150,
damage: 15,
agility: 20,
});

// 處理玩家資料
processPlayerData(player1);
// 輸出:玩家1 符合基本條件
processPlayerData(player2);
// 輸出:玩家2 符合基本條件
// 輸出:玩家2 符合進階條件

喜歡這篇文章,請幫忙拍拍手喔 🤣


share