Kent Beck Implementation Pattern Principles(3)-Symmetry

Z-xuan Hong
7 min readDec 29, 2019

--

這禮拜要介紹的Principle: Symmetry(對稱性),乍聽之下有點抽象,但在深入理解後,Symmetry將會幫助我們有效的撰寫程式。

我們先來看看Kent Beck的定義:

Symmetry in code is where the same idea is expressed the same way everywhere it appears in the code.

如果把Symmetry放到程式的世界,則是在任何地方相同的想法都以相同的方式描述。

例如所有物件的Field都擁有相同的Life time、一個集群的函數都接收相同的參數。

為什麼要Symmetry呢?因為當程式碼能夠以Symmetry的方式呈現時,只要看部分的程式碼,即可快速的理解另一部分,如此一來可讀性便會大大提升。透過Symmetry來傳遞Communication的價值。

void process() {
insertKey();
count++;
closeDoor();
}

上述這段程式碼取自Kent Beck書中,大家能看出哪邊怪怪的嗎?如果以Symmetry的角度去看這段程式碼,我們會發現count++跟insertKey、closeDoor的抽象程度不一致(count++是實作細節,而insertKey、closeDoor是意圖表達明確的函數)。

既然我們知道這段程式碼違反了Symmetry,在我們修正他之前,我們先來探討這樣的程式碼有哪些缺點:

  • 可讀性:抽象程度不一樣,不好理解,抽象程度越是一致可讀性越高,可以讓我們在不需理解瑣碎的實作細節情況下,了解程式要解決的問題
  • 擴充性:因為不好理解,通常會導致後續接手的人直接Hard Code功能(如果程式很Clean,通常會讓人比較想好好修改他,因為看得懂且改得動),容易產生更多Bug
  • 彈性:沒有適當的抽象化,會降低彈性,因為抽象化的好處就是能讓我們找到共同行為,然後透過Polymorphism的方式去減少判斷邏輯,讓系統容易擴充
void process() { 
insertKey();
openDoor();
closeDoor();
}

經過修改後的程式碼,我們可以明確的看出,count++其實是在做開門的動作,透過簡單的extract method,可以讓程式碼的可讀性大大的提升,基本上一秒就能理解process在解決什麼問題。

Symmetry可以有效地幫助我們達到SRP,為什麼這樣說呢?因為當我們把所有的Method都以這樣的形式表達時,會發現Method數量越來越多(High Level、Medium Level、Low Level),這時就是給我們一種提示這個物件可能違反SRP,沒有妥善的劃分責任到適合的物件。

假設我們要把Student的資訊,寫入檔案:

public void save(Student student) {
StudentDTO dto = StudentDTOConverter.fromDomain(student);
String str = "";
try {
ObjectMapper Obj = new ObjectMapper();
str = Obj.writeValueAsString(dto);
}
catch (IOException e) {
throw new RuntimeException(e);
}

byte[] strToBytes = (str + "\n").getBytes();
try {
Files.write(Paths.get("./src/main/resources/dto.txt"),
strToBytes,
StandardOpenOption.APPEND);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

上述的程式碼,明顯有幾點違反了Symmetry Principle

  • StudentDTOConverter的抽象程度(High Level Intention)跟其他部分的抽象程度不一致(Implementation Detail)
  • 不能看到程式碼所要表達的意圖(Intention Revealing)

因此我們做了幾點修改

  • 針對兩個try-catch區塊做Extract method,並且給他們Intention Revealing Name

修改後如下:

public void save(Student student) {
StudentDTO dto = StudentDTOConverter.fromDomain(student);
String jsonStr = toJson(dto);
write(jsonStr);
}

我們可以看到,修改後不必要的細節都去除,只剩下程式碼High Level的操作邏輯。

但是這樣會導致一個問題,這個Class明顯違反SRP

  • 把Student儲存的責任
  • 把StudentDTO轉換成Json格式的責任
  • 把檔案寫進去特定路徑的責任

因此我們針對這兩個Method使用Extract Class,讓責任好好的被分配到適當的物件當中

public void save(Student student) {
StudentDTO dto = StudentDTOConverter.fromDomain(student);
FileUtility.write(JsonUtility.toJson(dto));
}
public class JsonUtility {
public static String toJson(Object o) {
try {
ObjectMapper Obj = new ObjectMapper();
String jsonStr = Obj.writeValueAsString(o);
return jsonStr;
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public class FileUtility {
private static Path path = Paths.get("./src/main/resources/dto.txt");

public static void write(String jsonStr) {
byte[] strToBytes = (jsonStr + "\n").getBytes();
try {
Files.write(path, strToBytes, StandardOpenOption.APPEND); //Append mode
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

有人可能會問說為什麼要為了兩個Method抽了兩個物件,是不是有點過頭了?但是轉換Json格式和儲存不只有Save Student的Class需要可能還有其他Class需要,因此變成Utility Class增加了程式碼Reuse的程度。

從SRP的角度我們可以知道轉換Json Format和File Store是Different rate of change,因此需要抽離這兩個責任。

結論:Symmetry透過讓任何部分的程式碼抽象程度都維持一致(HIgh、Medium、Low),透過適當的抽象可以讓我們察覺物件責任分配不均(違反SRP)。

--

--

Z-xuan Hong
Z-xuan Hong

Written by Z-xuan Hong

熱愛軟體開發、物件導向、自動化測試、框架設計,擅長透過軟工幫助企業達到成本最小化、價值最大化。單元測試企業內訓、導入請私訊信箱:t107598042@ntut.org.tw。

No responses yet