物件導向程式的九個體操練習

最近在翻 The ThoughtWorks Anthology(知名軟體顧問公司 Thoughtworks 出的文集),裡面有篇 Object Calisthenics 蠻有意思的。

好的物件導向設計很難,我們都很同意何謂好的設計原則:高內聚力(cohesion)、低耦合(loose coupling)、不重複程式(Don’t Repeat Yourselp)、封裝(encapsulation)、可測試性、易閱讀性等等,但是實際寫的時候卻不容易化身為一行行的程式碼。這篇作者列了九條規則,並建議你練習寫個千行程式嚴格遵守看看,用以改善你的OO實作能力。

初次看到這九條時覺得有點誇張,但其實濃縮了不少OO想法在裡面,如果有閱讀過重構或物件導向設計原則等概念,應該能夠聯想到很多東西,挺有趣的。

1. 每個函式裡面只能有一層縮排,如果需要多一層,請多寫一個 method 去呼叫。

這個規則其實就是要求嚴格遵守 Compose Method:將邏輯操作轉換為細目等級相同的步驟,避免過深的邏輯而無法迅速了解,相信大家應該都有看(寫)過M型程式吧 :p

2. 不要使用到 else 這個關鍵字。

避免寫出複雜的 nested conditional 程式。不論是”重構“或是”重構-向範式前進“這兩本書,都有很多篇幅花在討論如何簡化條件邏輯,作法包括

a. 重構一書提到的 Replace Nested Conditional with Guard Clauses 方式,直接使用 return 返回,不要再 else 了。

b. 請愛用 Ternary Operator:也就是 boolean-expression ? expr1 : expr2。很多簡單的 if else 都可以用 Ternary Operator 簡化到一行一目了然。舉個 Ruby code 例子:


if ( is_something )
"foo"
else
"bar"
end

如果改成三重操作子就俐落多了:

( is_something )? "foo" : "bar"

另外初心者也常寫出根本不需要 if else 的情況:

def is_foobar
if ( a > 0 )
return true
else
return false
end
end

其實只需要這樣就可以了:

def is_foobar
( a > 0 )
end

c. 第三招要先念點書,請善用物件導向的多型(polymorphism)能力,請參考設計模式的 Strategy pattern 或重構的 Replace Conditional with Polymorphism

3. 所有基本型別都包裝成物件

如果你要用到 integer 或 string 等基本型別,請多用一層帶有意義 Class 包裝起來。例如在傳遞 “年” 跟 “月” 的參數時,不要只用 integer 傳遞哪一年哪一個月,而是用個 Date 物件包裝起來。

為什麼呢? 透過這種小型物件,我們可以寫出較容易維護的程式,一看就懂那個 integer 代表的意義是什麼。而且只用基本型別,編譯器也無法幫助(偵測)你寫出你真正想要的正確”語意”。另外一個小型物件好處是提供了很好的放 method 的地方,例如在這方面 Ruby 就做的很好,因為所有東西都是物件,所以甚至是 integer 或 string 都可以直接有自己的基本操作,例如:

-123456.abs # 123456
"FOObar".downcase # "foobar"。
Time.now.year # 2008

4. 每一行只能出現 dot 一次(即每行只能呼叫一次 method)

這規則對 Ruby 可能太嚴格了…XD 規則的目的是要減少耦合,避免破壞封裝原則:你只能玩你的玩具,而不是玩具的玩具。細節請找 The Law of Demeter (“只對你的朋友說話”),引用 Qing 長輩的漫談程式碼的相依性:”在遵守所謂的Law of Demeter時,在某一物件的某一函式中所能呼叫函式的對象是受限制的,它只能是:該物件本身、傳入該函式的引數、在該函式中所產生的物件、以及該物件的資料成員。很明顯的,你不能在此函式中呼叫其他函式所回傳物件的函式,因為其他函式所回傳的物件,並不在上述的對象之中。

5. 不要縮寫名稱

我超愛這個規則,每次看到有人用縮寫亂取 method name 或 variable name,就覺得很抓狂。即使用 full name 會長了點,但是我寧願一看就懂而不是玩猜謎(例 what’s sm? short_message? smart_phone? single_machine?)。即使用暫時變數我也覺得應該好好命名,而不是只用一個 “t”。我自己在 Textmate 的經驗是只要按 Esc 就可以自動比對出你要的 full name,從來都不覺得打完整名稱會很辛苦。

如果你真的覺得太長,就應該好好重新思考:是不是這個 method 放錯 class 了? (放到別的 class 之下也許不需要這麼長就可以知道作用)。另一個取名常犯的錯誤是不需要重複 context (語境):例如一個叫 Order Class,它的 method name 就不需要命名為 ship_order (即@order.ship_order),只需要取名 ship (即@order.ship)就好了。

6. 保持東西輕薄

class 不超過50行,每個 package 不超過10個檔案。50行的程式不會超過一個螢幕,這樣螢幕不用捲就可以一掃而過。這個挑戰在於你要努力去 grouping 有共通 logical sense 的東西。

7. 不要用任何一個含超過兩個 instance 變數的 class

這大概是最難的練習了,作者認為當一個 Class 處理超過一個 state variable 時,就應該另建一個新的 class 分解及包裝起來。實際的作法我想可以參考重構的 Extract Class 方法。

8. 使用第一級 collections

任何包含 collection 的類別就不應該再含有別的變數,也就是你應該把 collection 當做一個很基本的類別來使用。

9. 不要使用任何 getters/setters/properties

目標是要練習 “Tell, don’t ask” 原則,你應該盡量告訴物件你想要什麼(tell),而不是去問(ask)物件的狀態,然後做決定,再告訴物件做什麼。更詳細可以看看 The Pragmatic Bookshelf 的 Tell, Don’t Ask 一文 (這篇也有提到Law of Demeter)。

參與討論

6 則留言

  1. 自動引用通知: e-ipro
  2. 感謝分享! 簡潔樸實! 言簡意賅! 可以感受到厚實的底蘊!!!

發佈留言

發表迴響