2008年05月15日

利用 NullObject 改善程式可讀性,No more if, no more try

Tags: php5 refactoring nullobject

剛在重構一組類別的程式碼時,突然想到 Martin 在《敏捷軟體開發原則、樣式與實務》一書中提到的一個編程技巧,就是在失敗狀況時回傳 NullObject ,避免行為調用者用 iftry 處理失敗狀況,影響程式可讀性。

我重構中的類別程式碼,基本上是一個聚合類別,它包含了其他類別的個體。此聚合類別提供一個方法 get() ,以取得它所包含的個體。外部調用 get() 後取得內容個體後,立即呼叫該個體的一個方法。


原本的程式碼,其架構大略如下所示。

程式碼的語意是調用 $c->get() 取得內容個體,接著調用取出物的 output() 行為。問題在於這段程式碼並未考慮找不到指定內容個體的情形,故第27行的 $c->get('b')->output() 將會引發調用不存在個體之方法的執行錯誤。直覺上,我們的解決的方式就是修改調用行為的這一段程式碼,加上 iftry 的錯誤處理流程。

但這在大型專案或分工團隊中可能會有副作用。例如甲是負責基礎類別的程序員,也就是負責寫上例第1到23行程式碼的人;乙是負責應用流程的程序員,也就是寫第24到27行程式碼的人。現在甲要重構基礎類別,並可能改變行為回傳結果時,按直覺的方式,我們將牽連乙去修改他的程式碼。這就是一種不良副作用。

而 Martin 所說的編程技巧,則是在碰到找不到指定個體時就回傳一個 NullObject,使得調用者可以不用修改程式碼。這個編程技巧用 PHP 實作很簡單,只要利用 PHP5 的 magic method 就能輕鬆實現。如下所示。

NullObject and magic method

我們重構時,增加一個 NullObject 類別,這類別就是什麼事都不做。所以我們再利用 magic method 的 __call() 實作一個什麼都不做的泛用行為。這可以免除 PHP 發出找不到指定行為的執行錯誤。最後,我們只要再把 get() 回傳 false 的動作改成回傳 NullObject 即可。

就這樣,我們完成了重構動作。而調用者的程式碼不必進行任何修改,就無聲無息地略過了錯誤處理流程並保持程式碼簡潔的語意。

Java style

附帶一提。這個編程技巧也可以用來檢視程式語言的動態性對我們的影響。假如我們不用 PHP5 提供的 magic method ,那麼我們就要採用類似 Java 的編程風格來重構,範例如下。

昨天寫下這一段話,第二天就後悔了。因為我想到 Ruby, JavaScript 並沒有 PHP5 __call 的 magic method 機制。而 Ruby, JavaScript 的動態性卻又比 PHP 還優秀。所以用 magic method 機制評定程式語言的動態能力,似乎不太適當。Magic method 果然 magic...

在 Java 編程風格下,我們需要先定義一個具有 output() 行為的介面I。再為 C 類別專門定義一個實作了介面ICNullObject 類別。

這種受限於程式語言動態能力所帶來的編程風格,最顯而易見的累贅就是要定義許多 xNullObject,而它們所作事都一樣: 不做任何事。結果我們為了不做任何事的類別複製了許多重複的程式碼。


Posted by shirock at 樂多Roodo! │16:27 │回應(1)引用(0)Programming
樂多分類:學術/學習 工具:加入樂多書籤編輯本文
Ads by Roodo! 

引用URL

http://cgi.blog.roodo.com/trackback/6028145
回應文章
Ruby 不是有 method_missing 這個 method 的嗎?
Posted by LungZeno at 2008年07月14日 18:18