l

2012年5月16日 星期三

Implementation Patterns: Guard Clause

May 15 22:17~23:30

image

前幾天Teddy介紹了「The Art of Readable Code」這本書,有熱心的鄉民馬上就推薦了The Practice of Programming、Code Complete、Clean Code、Code Craft、The Pragmatic Programmer、Beautiful Code、Working Effectively with Legacy Code這幾本。今天Teddy又擠不出什麼料,只好拿出老招,介紹一下書本的內容。今天要介紹的是Teddy的偶像Kent Beck所寫的Implementation Patterns這本書的第70-72頁所介紹的Guard Clause這個implementation patterns。

鄉民們可以把這本書想像成跟GoF的Design Patterns屬於類似性質的書,只不過GoF的書介紹了23個設計層面的樣式,而Kent Beck這本書談的是屬於實作(程式設計)方面的樣式。學會Implementation Patterns也可以讓程式設計師寫出容易閱讀的程式碼。

Implementation Patterns這本書很薄,只有一百五十幾頁,但是算一算應該有介紹一百個左右的patterns。這些patterns都很小,小到讓你會懷疑,這樣也算一個「pattern」喔。總之,Kent Beck說這是patterns,那就是嘍。

言歸正傳,回到今天的主題 Guard Clause,這個中文要怎麼翻?姑且稱作「看守子句」好了…Orz。看一下書中的例子:

void initializeA() {

if (!isInitialized()) {

   …

}

}

void initializeB() {

if(isInitialized())

       return();

}

上面這兩種寫法,鄉民們認為哪一種比較好?請先花六十秒想一下。

 

 

 

 

 

Kent Beck建議採用initializeB()的寫法是比較好的,因為

if(isInitialized())

return();

這樣的寫法就稱為Guard Clause。為什麼要叫做Guard Clause?因為這個if clause(if子句)扮演著「看守」或是說「警衛」的角色。只要這個Guard Clause成立,程式就不會繼續執行下去(做壞事被警衛抓到)。所以讀到initializeB()前兩行程式時,便可知道這個initializeB()可以被呼叫很多次,但是只有第一次會實際執行初始化的動作。

至於第一種寫法的if,代表著一個if-then-else表示式。根據Kent Beck的說法,因為if-then-else表示式裡面的if子句和else子句都是相等重要的控制流程,看到if (!isInitialized()) 的時候,他的腦袋會提醒他等一下要記得去看相對應的else子句的內容。在這個例子裡面當然不需要else子句,所以這種情況使用if-then-else表示式就比較不好。

但是,實際上很多程式設計師會採用第一種寫法。為什麼?因為傳統的程式設計課程告訴鄉民們,一個副程式應該只有一個進入點與一個離開點,所以如果用第二種寫法,initializeB() 就有兩個不同的離開點,以傳統的程式設計思維來看,反而是不好的寫法。Kent Beck認為,傳統的建議是適用在像是FORTRAN或是組合語言這種使用到大量全域變數的程式中。但是對於近代的語言,像是Java,在一個小小的method裡面,大部分存取的都是區域變數。如果還是堅守「一個副程式應該只有一個進入點與一個離開點」,就顯得有點過於保守。

再看一個書中的例子,這也是Teddy寫程式經常會遇到的情況:

void computeA() {

Server server = getServer();

if (server != null) {

    Client client = server.getClient();

    if (client != null) {

         Request current = client.getRequest();

         if (current != null)

              processRequest(current);

    }

}

}

void computeB() {

Server server = getServer();

if (server == null)

    return;

Client client = server.getClient();

if (client == null)

    return;

Request current = client.getRequest();

if (current == null)

    return;

processRequest(current);

}

 

哪一種寫法比較清楚且容易閱讀?Teddy很久以前是比較傾向會寫出computeA()這種寫法,但是學了Guard Clause之後,當然是要改用computeB()這種寫法嘍。

最後再看一個書中的例子:

while (line = reader.readline()) {

if (line.startWith(‘#’) || line.isEmpty())

      continue;

// Normal processing logic

}

其中

if (line.startWith(‘#’) || line.isEmpty())

       continue;

也是Guard Clause。

***

友藏內心獨白:又是一把攜帶方便,簡單又實用的小扁鑽。

沒有留言:

張貼留言