不相依的程式,就不該被人為的耦合
。例如一般普通的 enums,不該被包含在特定類別內。因為這會導致整個程式,都被迫知道這個特定類別。同樣的,無特殊用途的靜態方法 (static),也不應被宣告在特定的類別中。
我們應該仔細、審慎的思考,各個變數、函式該處在的位置,而非草率地安置它們,導致程式間不必要的耦合。
而草率地決定變數、函式位置,除了會導致耦合外,也會使程式間的職責錯置
。依循著最少驚奇原則,程式碼應被置於讀者理所當然認為它應該所處的地方。大多時候,我們可以參考程式名稱去決定功能所該在的位置,而非放置在我們覺得方便的地方。
反之,當因為其他考量而決定函式、變數的位置時,若名稱無法簡明地表達出該功能的意義,那我們就該修正函式的名稱,或是增加符合該需求的新功能。
同時,既然精準、明確的表達能力,對於程式來說如此重要,我們就應該在各方面都確保程式具備這項能力。小心地避免跨行表達式、匈牙利命名法及魔術數字,來保障程式的意圖沒有被模糊。
這是一個相當普遍、古老的規範之一,但我仍然想特別提出來分享,因為裡面具有一些我認為不錯的概念補充。一般來說,在程式中直接使用數字及字串是件糟糕的想法,我們會被建議使用一個良好命名的常數來隱藏、代表原先的魔術數字。而雖然我們稱之為魔術數字,但其實不僅指數字,這我們稍後會說明。
例如,單看數字 86,400 我們可能很難明白它所代表的意涵,但當我們給它一個常數名稱 SECOND_PER_DAY 時,我們就能在閱讀程式時輕易地了解,這是代表一天之內的秒數。除了增加程式的可閱讀性、表述力,也同時避免我們因為重複輸入數值的疏忽而導致程式錯誤。尤其不幸的事,此種不起眼錯誤往往最難被找尋出來。
但有趣的是,其實我們不需要將每個數值轉換為常數,這在我之前接觸相關概念時並沒有人別提出。我們來看一點例子:
int dailyPay = hourlyRate * 8;
double circumference = radius * Math.PI * 2
在這兩種敘述裡,常數本身與程式碼共組時,已能夠明確地自我解釋並傳達其意義,這時我們是否仍需要特意分別命名為: WORK_HOURS_PER_DAY 及 TWO 呢?TWO 可能因為看起來過蠢而沒有爭議,但有些人也許會說,每天工時的法律或慣例可能會改變。然而從閱讀性來說,8 閱讀起來如此順暢、簡明,所以我們並無須特意改成一個 17 的字元的常數名稱。
但我前面也提說,使用良好命名的常數,除了關乎閱讀性外,避免數值出錯也是一個很重要的原因。所以縱然 3.1415926534 是相當具辨識度的數值,我們仍然會用 PI 來替代它,畢竟,應該沒什麼人會注意到我前面圓周率的值故意打錯。
除了數字,「魔術數字」也適用在任何無法自我描述其意義的值 (value) 上。例如:
assertEquals(7777, Employee.find("John Doe").employeeNumber());
在這個例子中,擁有了兩個魔術數字。一個是數字 7777,另一個則是字串 "John Doe",他們各自所代表的意義都不明。當我們探究其邏輯,發現 "John Doe" 的員工編號是 7777,而他也是資料庫中唯一的時薪員。所以我們將這個測試程式改為:
assertEquals(HOURLY_EMPLOYEE_ID, Employee.find(HOURLY_EMPLOYEE_NAME).employeeNumber());
如此一來,是不是就相當容易被閱讀了呢?