Kent Beck Implementation Pattern Principles(5)-Declarative Expression
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,都能夠獲得宣告式帶來的好處。
結論: 用程式碼建立人看得懂的語言,而不是用程式碼寫出機器看得懂的語言。