Kent Beck Implementation Pattern Principles(5)-Declarative Expression

Z-xuan Hong
6 min readFeb 28, 2020

--

Declarative的中文為宣告式,但這跟程式有甚麼關係呢?

相信大家都有聽說過一段Martin fowler的引言:

Any fool can write code that a computer can understand. Good programmers write code that humans can understand

大意就是任何人都能寫出電腦看得懂的程式,但好的工程師能寫出人看得懂的程式。

為何看得懂那麼重要 ?

Kent Beck說過,在開發過程當中,修改遠比執行還要多,但修改的前提就是要先能夠看懂程式碼所要表達的意圖(Intention Revealing),如果程式碼意圖表達不明,就容易造成不必要的Bug或是難以新增功能,導致專案容易延期,這應該是很多失敗專案的通病,因此要降低專案維護的成本最重要的關鍵就是程式碼可讀性(當然還有擴充性,可測試性,可預測性)。

當我們清楚了,讓程式變軟最大的關鍵在於可讀性之後。

Declarative Expression Principle正是可以用來幫助我們提升程式碼可讀性的好工具。

Implementation Pattern原文 :

Another principle behind the implementation patterns is to express as much of my intention as possible declaratively. For those parts of a program that are more like simple facts, without sequence or conditionals, it is easier to read code that is simply declarative.

大意就是盡可能的用宣告式的手法表達程式碼的意圖,陳述簡單的事實(Simple Fact)而不是複雜的條件跟邏輯,來提升程式碼可讀性。

相信有寫過程式都知道,當程式碼邏輯一複雜時,如果沒有好好切割,程式很快就會變成沒人看得懂的Dirty Code。好幾層的迴圈夾雜著若干的判斷式,跟一堆用Exception當成Main Control Flow。都是會讓腦袋爆掉的寫法。後人維護內心只會有罵不完的髒話。

因此這個原則告訴我們: 盡可能用程式描述要解決的問題,而不是一開始就寫一堆複雜條件。也就是寫程式前,先從What下手,而不是How下手,藉由這種方法,來提供程式碼的抽象程度,並且找出相似的邏輯。

DDD中提到的Ubiquitous Language In Code也是一樣的概念,要提升程式碼的可讀性,就必要用Ubiquitous Language(簡單一點的說法,就是讓人看得懂的語言)去寫程式,讓程式表達出要解決的Problem Domain,而不是只有一堆條件跟邏輯。

還是聽不太懂?舉個簡單的例子:,

  • 打開購物車的程式碼能否讓開發人員清楚的知道是解決購物車問題。
  • 打開聊天室的程式碼能否讓開發人員清楚的知道是解決聊天室問題。

而最近火紅的Functional Programming也有這樣的優勢,不像傳統語言是Step By Step,而是用簡短且精確的表達式去描述程式要解決的問題。

Java目前也提供了很多Functional Programming的API,讓我們更輕易的達到Declarative Expression的效果。

接下來用實際例子讓大家更能清楚明白Declarative Expression的意思 :

public static void main(String[] args) {
List<String> names = Arrays.asList("Kent", "Martin", "Bob", "Kevin");
List<String> result = new ArrayList<>();
for (String name: names) {
if (name.startsWith("K")) {
result.add(name);
}
}
int total = 0;
for (String name: result) {
total += name.length();
}
System.out.println("Total Length of Names Start With K: " + total);
}

大家花花幾秒鐘思考看看這段程式碼要表達什麼意圖?

  • 第一個迴圈用來找出開頭為K的姓名。
  • 第二個迴圈用來算出這些姓名的總長度。

上述程式缺點如下 :

  • Step by Step的寫法,其實很難看出程式碼要解決的問題。
  • 太多的Temp Variable去紀錄目前狀態。導致腦袋要建立小型的Mental Model才能理解程式的流程。

接下來以宣告式的手法來處理,看看修改後的程式碼可讀性是否提升 :

public static void main(String[] args) {
List<String> names = Arrays.asList("Kent", "Martin", "Bob", "Kevin");
int total = names.stream()
.filter(name -> name.startsWith("K"))
.mapToInt(String::length)
.sum();
System.out.println("Total Length of Names Start With K: " + total);
}

從修改後的程式碼有幾項優點:

  • Immutability: 程式沒有一堆狀態,完全由語言內部處理。因此可預測性高(因為狀態不會變化,不需怕受到其他地方影響),需要理解的Scope很小。
  • Parallelable: 由於沒狀態,能夠輕鬆以平行化去處理,並且不用處理thread safe問題。
  • Declaratively: 可讀性高,基本上5秒內可以看出要解決甚麼問題,沒有多餘的變數,多餘的迴圈,只有實實在描述程式碼要解決的問題。

經過這次的介紹,希望大家能清楚Declarative的含意,不管是用Functional API去表達或是用Extract Method加上Intention Revealing Name,都能夠獲得宣告式帶來的好處。

結論: 用程式碼建立人看得懂的語言,而不是用程式碼寫出機器看得懂的語言。

--

--

Z-xuan Hong
Z-xuan Hong

Written by Z-xuan Hong

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

No responses yet