Copyright (c) 1999 Scott Ambler, Ambysoft, Inc.
「Java 程式碼撰寫準則」是在 Scott Ambler, Ambysoft Inc., www.ambysoft.com 的授權下提供。它們經過重新格式化,以包含在 Rational Unified Process 中。
1
簡介
1.1 第一個及最後一個準則
2 程式碼撰寫標準
2.1
命名慣例
2.2
記載註解慣例
2.2.1 Java 註解的類型
2.2.2 javadoc 的快速概觀
3 成員函數的標準
3.1
成員函數命名
3.1.1
存取元成員函數命名
3.1.1.1 getter
3.1.1.2 setter
3.2
建構子命名
3.3
成員函數有效範圍
3.4 記載成員函數
3.4.1 成員函數標頭
3.4.2 內部文件
3.5
撰寫簡潔程式碼的方法
3.5.1 記載程式碼
3.5.2 將程式碼分段落或縮排
3.5.3 在程式碼中使用空白字元
3.5.4 遵循 30 秒規則
3.5.5 撰寫簡短、單一指令行
3.5.6 指定作業的次序
5 區域變數的標準
5.1
區域變數命名
5.1.1
串流命名
5.1.2 迴圈計數器命名
5.1.3
異常狀況物件命名
5.2
宣告及註解區域變數
5.2.1 關於宣告的一般註解
6 成員函數的參數標準
6.1
參數命名
6.2
參數註解
6.2.1 參數類型的使用介面
7 類別、介面、套件和編譯單元的標準
7.1 類別的標準
7.1.1 類別命名
7.1.2 記載類別
7.1.3 類別宣告
7.1.4 將 public 和 protected 介面減至最少
7.1.4.1 先定義 public 介面
7.2 介面的標準
7.2.1 介面命名
7.2.1.1 替代方案
7.2.2 介面註解
7.3 套件的標準
7.3.1 套件命名
7.3.2 套件註解
7.4 編譯單元的標準
7.4.1 編譯單元命名
7.4.2 編譯單元註解
10 成功的典範
10.1 有效地利用這些標準
10.2 導致撰寫成功程式碼的其他因素
11 摘要
11.1 Java 命名慣例
11.2 Java 記載慣例
11.2.1 Java 註解類型
11.2.2 註解的內容
11.3 Java 程式碼撰寫慣例(一般事項)
12 參考資料
13 名詞解釋
本文件說明用於撰寫穩定的 Java 程式碼的大量標準、慣例和準則。它們係依據健全且經驗證的軟體工程原則,這些原則會導致易於瞭解、維護及加強的程式碼。此外,在遵循了這些程式碼撰寫標準之後,身為 Java 開發人員的您的產能應該會顯著增加。經驗顯示,若是從一開始就費點心思撰寫高品質的程式碼,則在開發流程期間修改它時會輕省很多。最後,遵循一個共同的程式碼撰寫標準集會帶來更佳的一致性,使開發人員小組的產能顯著提升。
利用常識。 當您找不到規則或準則時、當規則顯然不適用時、當其他作法都不管用時:請善用常識,以及檢查基本原則。這個規則超越其他的所有規則。常識乃是必要的。
Java 的程式碼撰寫標準非常重要,因為它們會讓您的程式碼以及您團隊隊員的程式碼更為一致。更佳的一致性會使得程式碼較易於瞭解,這也就表示它較易於開發及維護。這可以降低您所建立的應用程式的整體成本。
您務必要記住您的 Java 程式碼會留存很久;留存到您進行其他專案以後。開發期間的一個很重要的目標,是確定您可以將您的工作轉移給另一位開發人員,或是另一個小組的開發人員,讓他們可以不必耗費過多的工夫瞭解您的程式碼,就可以維護及加強您的工作。難以瞭解的程式碼會冒著被捨棄及重寫的風險。
我們在整個標準中都會討論到命名慣例,因此讓我們用幾個基本概念來設定階段:
- 使用精確說明變數、欄位和類別的完整英文;例如,使用像是 firstName、grandTotal 或 CorporateCustomer 等名稱。諸如 x1、y1 或 fn 等這些名稱雖然很短,很容易鍵入,但是它們並沒有提供它們代表什麼的任何指示,結果是程式碼難以瞭解、維護及加強。
- 使用適用於網域的專有名詞。 如果您的使用者以客戶的身份參照他們的用戶端,則請使用 Customer 一詞代表類別,不要使用 Client。許多開發人員犯了在產業或領域中已經有絕佳的術語時,還創造概念的通用術語的錯誤。
- 使用混合大小寫來使名稱易於辨認。 一般請使用小寫字母,但是類別名稱和介面名稱的第一個字母以及任何非起始字的第一個字母,請使用大寫。[KAN97]
- 謹慎使用縮寫,但是如果您這麼做,則花點心思使用它們。 這表示您應維護一份標準簡短格式(縮寫)的清單、 應明智地選擇它們,並且使用它們時應保持一致。比方說,如果您要使用 "number" 一字的簡短格式,則選擇 nbr、no 或 num 其中一個、 記載您選擇哪一個(哪一個並不是真的那麼重要),而且只使用那一個。
- 避免長名稱(少於 15 個字元是個好主意)。雖然類別名稱 PhysicalOrVirtualProductOrService 看來似乎是一個不錯的類別名稱,但是這個名稱就是太長了,您應考慮將它重新命名為較短的名稱,也許像是 Offering。
- 避免只有在大小寫上類似或有差異的名稱。例如,變數名稱 persistentObject 和 persistentObjects 不應一起使用,anSqlDatabase 和 anSQLDatabase 也是如此。
- 避免前導或尾端的底線。 具有前導或尾端底線的名稱通常保留作為系統用途,不應用於任何使用者建立的名稱。更重要的是,底線相當令人困擾且難以鍵入,因此請盡可能避免使用它們。
我們也會討論記載慣例,因此讓我們先來探討一些基本觀念:
- 註解應該要使程式碼更為明確。記載程式碼的原因是要使您本身、您的同事,以及任何跟在您後面的其他開發人員能更加瞭解它。
- 如果您的程式不值得記載,那麼它可能也沒多大用處。 [NAG95]
- 避免過度裝飾;也就是說,不要使用像是一幅廣告的註解。 在 1960 及 1970 年代,COBOL 程式設計師養成了在他們的內部註解周圍畫方框(通常是用星號)的習慣。沒錯,它是為他們在藝術上的渴望提供了一個發洩的管道,但是坦白講,它浪費了大半的時間,只為最終產品添加了一點價值。您應該寫的是乾淨俐落而不是漂亮的程式碼。此外,由於許多用來顯示及列印程式碼的字型是按照比例,有些則不然,因此您怎麼樣都沒辦法將方框適當排齊。
- 註解要簡潔有力。 有一些最好的註解都是簡單的點格式附註。不必長篇大論;只要提供足夠的資訊,讓別人能夠瞭解您的程式碼就行了。
- 先記載,才寫程式碼。 記載程式碼的最好方法是:先寫註解,然後才寫程式碼。這讓您有機會在寫程式碼之前先想一想它將如何運作,而且也能夠確保寫好註解。此外,您至少應在撰寫程式碼時寫它的註解。由於註解能使程式碼更易於瞭解,因此您在開發它的同時,就能夠充分利用這個事實的優點。如果您打算花點時間撰記載,則應該至少要有點收穫。[AMB98]
- 說明為什麼要寫這段,而不只是說明在做什麼。 例如,以下的範例 1 中的程式碼顯示金額超過 $1,000 元(含)的訂單有 5% 的折扣。為什麼會這麼做?是否有什麼商業規則指出大型訂單可以享有折扣?是否有針對大型訂單的限時特惠,還是它是一個永久性的方案?是因為原創程式設計師過於慷慨嗎?只有在某處(在程式碼本身或外部文件中)加上註解才知分曉。
範例 1:
if ( grandTotal >= 1000.00)
{
grandTotal = grandTotal * 0.95;
}
Java 的註解分成三種:
- 文件註解,其開頭為 /**,結尾為 */
- C-style 註解,其開頭為 /*,結尾為 */
- 單行註解,其開頭為 //,然後一直寫到程式碼行結束
下表是每一類註解的建議用法摘要,以及數個範例。
註解類型 用法 範例 文件 在介面、類別、成員函數和欄位的宣告正前面使用文件註解來加以說明。javadoc 處理文件註解(請參閱以下)來建立類別的外部說明文件。 /**
客戶:客戶是我們向其販售服務和產品的任何人或組織。
@author S.W. Ambler
*/C style 使用 C-style 註解來註銷已不再適用、但是您想要保留以防您的使用者改變心意,或是因為您要在除錯時暫時關閉它的程式碼行。 /*
B.Gustafsson 在 1999 年 6 月 4 日註銷了這段程式碼,因為它被前一段程式碼所取代。如果它兩年後仍然不適用,請加以刪除。
. . . (程式碼)
*/單行 在成員函數內使用單行註解來說明商業邏輯、幾段程式碼,以及暫存變數的宣告。 // 對所有超過 $1000 的發票套用
// 5% 的折扣,這是肇因於 1995 年 2 月開始
// 的超優惠
// 活動。重要的是您的組織應設立一個應如何使用 C-style 註解和單行註解的標準,然後遵循那個標準不變。使用一種類型來寫商業邏輯的註解,並使用另一種類型來註銷舊程式碼。商業邏輯請用單行註解,因為您可以將註解放在與程式碼同一行上(這稱為列入)。註銷舊程式碼請用 C-style 註解,因為它可讓您一次註銷數行。由於 C-style 看起來非常類似文件註解,為避免混淆,請不要在別的地方使用它們。
請注意行尾註解-[MCO93] 強烈質疑使用行內註解,這又稱為行尾註解。 McConnell 指出註解必須與程式碼的右邊對齊,這樣它們才不會干擾到程式碼的視覺結構。其結果是,它們很容易會難以格式化,而且「如果您用了很多註解,就要花很多時間對齊它們」。這種時間並不是花在進一步瞭解程式碼;它是單單用在按空格鍵或 Tab 鍵這些冗長乏味的作業上。它還指出行尾註解也很難維護,因為當行上的程式碼擴增時,它會將行尾註解擠掉,而如果您要將它們對齊,則其餘的也得這麼做。
Sun 的 Java Development Kit (JDK) 中包含了一個稱為 javadoc 的程式,其處理 Java 程式檔,並以 HTML 檔案的格式,為您的 Java 程式產生外部文件。 Javadoc 支援有限數目的標示;標示註解開頭的保留字。請參閱 JDK javadoc 文件以取得進一步的詳細資料。
標示 使用對象 用途 @author name 類別、 介面 指出某給定程式碼片段的作者。應使用每位作者一個標示。 @deprecated 類別、 成員函數 指出類別的 API 已被棄用,因此不應該再使用。 @exception name description 成員函數 說明成員函數擲出的異常狀況。您應對每個異常狀況使用一個標示,並賦予異常狀況的完整類別名稱。 @param name description 成員函數 用來說明傳遞給成員函數的參數,其中包括其類型或類別以及其用法。請對每個參數使用一個標示。 @return description 成員函數 說明成員函數的傳回值(如果有的話)。您應指出傳回值的類型或類別,以及可能的用法。 @since 類別、成員函數 指出這個項目存在了多久;這是指自 JDK 1.1 之後。 @see ClassName 類別、介面、成員函數、欄位 在文件中產生一個連接指定類別的超本文鏈銡。您可以(而且可能應該)使用完整的類別名稱。 @see ClassName#member functionName 類別、介面、成員函數、欄位 在文件中產生一個連接指定成員函數的超本文鏈銡。您可以(而且可能應該)使用完整的類別名稱。 @version text 類別、介面 指出給定的程式碼片段之版本資訊。 您記載程式碼撰的方式,對於您自己的產能以及每一位稍後維護及加強它的其他人之產能而言,都有重大的影響。提早在開發流程中記載程式碼的情況下,您會變得較具產能,因為它逼得您在將您的邏輯確認到程式碼之前,得先對它有個通盤的考量。此外,當您再看到您在幾天或幾週前所寫的程式碼時,您可以輕易地判定您當時在寫它時的想法,因為已經記載。
請記住,您現在寫的程式碼幾年過後可能還在使用中,而且維護及加強它的很可能另有他人。您務必要盡可能讓您的程式碼「簡潔」而且容易瞭解,因為這些因素會使得它較容易維護及加強。
成員函數應該用混合大小寫的完整英文來命名,任何非起始單字的第一個字母大寫。成員函數的第一個字是堅定、主動語氣的動詞也是常見的作法。
範例:
openAccount()
printMailingLabel()
save()
delete()
這個慣例造就了往往只要從它的名稱就可以看出其用途的成員函數。雖然這個慣例會因為它經常會導致較長的名稱,而往往使得開發人員得另外多鍵入一點,但是由程式碼的可瞭解性增加而得到的彌補卻是更多。
我們將在下一章中更詳細討論存取元,這是取得及設定欄位值(欄位或內容)的成員函數。不過,以下會彙總存取元的命名慣例。
3.1.1.1 getter
getter 是傳回欄位值的成員函數。您應在欄位名稱前面冠上 "get" 一字,除非它是一個 Boolean 欄位,那麼就要在欄位名稱前面冠上 "is" 而不是 "get"。
範例:
getFirstName()
getAccountNumber()
isPersistent()
isAtEnd()
遵循下列命名慣例時,您可以使成員函數傳回物件的欄位非常明確,而若是 Boolean getter,則會使它傳回 true 或 false 非常明確。這項標準的另一個優點是,它遵循 Beans Development Kit (BDK) 針對 getter 成員函數所用的命名慣例。[DES97] 主要的缺點是 "get" 是多餘的,需要額外的鍵入。
getter 的替代命名慣例:has 和 can
一個可行的替代方案(依據適當的英文使用慣例)是對 Boolean getter 使用字首 "has" 或 "can" 而非 "is"。例如,像是 hasDependents() 和 canPrint() 的 getter 名稱會在您閱讀程式碼時可以瞭解到很多。這種方法的問題是,BDK 不會挑選這種命名策略(仍然)。您可以將這些成員函數重新命名為 isBurdenedWithDependents() 和 isPrintable()。
3.1.1.2 setter
setter(又稱為 mutator)是修改欄位值的成員函數。無論欄位類型為何,您都應在欄位名稱前面冠上 "set" 一字。
範例:
setFirstName(String aName)
setAccountNumber(int anAccountNumber)
setReasonableGoals(Vector newGoals)
setPersistent(boolean isPersistent)
setAtEnd(boolean isAtEnd)
遵循這個命名慣例時,您可以使成員函數設定物件的欄位值非常明確。這項標準的另一個優點是,它遵循 BDK 針對 setter 成員函數所用的命名慣例。[DES97] 主要的缺點是 "set" 是多餘的,需要額外的鍵入。
建構子是成員函數,其會在物件最先被建立時執行任何必要的起始設定。建構子一律被賦予與它們的類別相同的名稱。例如,類別 Customer 的建構子將會是 Customer()。請注意,所用的大小寫相同。
範例:
Customer()
SavingsAccount()
PersistenceBroker()
這個命名慣例是由 Sun Microsystems 所設定,必須嚴格遵守。
以其中類別之間的耦合減至最少的一個好的設計而言,在設定成員函數的有效範圍時,簡略的一般規則是盡可能加以限制。如果成員函數不一定得是 public,就使它為 protected,而若是不一定得是 protected,就使它為 private。
有效範圍 說明 適當用法 public public 成員函數可以被任何其他的物件或類別中的任何其他成員函數所呼叫。 當成員函數必須可以被該成員函數在其中定義之類別階層外的物件和類別所存取時。 protected protected 成員函數可以被它在其中定義之類別中的任何成員函數、 該類別的任何子類別,或是同一個套件中的任何類別所呼叫。 當成員函數提供的行為是在類別階層或套件內部(而非外部)被需要時。 private private 成員函數只能被它在其中定義之類別(但是不包括子類別)中的其他成員函數所呼叫。 當成員函數提供類別特有的行為時。private 成員函數經常是重構的結果,其又稱為重組,是類別內的其他成員函數封裝一個特定行為的行為。 預設值 依預設(未指定有效範圍),函數只能被它在其中定義的類別,或是同一個套件中的任何類別中的其他成員函數所呼叫。 當成員函數提供的行為是在同一個套件內(而非外面)的類別(而非子類別)所需要時。
您記載成員函數的方式,往往會是它是否可以瞭解的決定性因素,因而也是它是否可以維護及延伸的決定性因素。
每個 Java 成員函數都應在程式碼頂端包含某種稱為成員函數文件的標頭,記載所有對於瞭解它至關重大的資訊。這項資訊包含(但不限於)下列各項:
- 成員函數做什麼以及它那麼做的原因。當記載成員函數做什麼時,您會讓他人較容易判斷他們是否可以重複使用您的程式碼。記載此函數的功能,可使他人更容易瞭解您的程式碼。這也會讓他人較易於判斷是否應實際對一段程式碼進行新的變更(或許新變更的原因與程式碼為何寫在首位的原因相衝突)。
- 什麼參數必須傳遞給成員函數。您也必須指出什麼參數必須傳遞給成員函數(如果有的話),以及將會如何使用它們。這項資訊是必要的,以便讓其他程式設計師知道什麼資訊要傳遞給成員函數。這項作業使用 2.2.2 節 javadoc 的快速概觀中討論的 javadoc @param 標示。
- 成員函數傳回什麼。您必須載明成員函數傳回什麼(如果有的話),使其他程式設計師能夠適當地使用傳回值或物件。這項作業使用 2.2.2 節 javadoc 的快速概觀中討論的 javadoc @return 標示。
- 已知的錯誤。 應載明成員函數的任何尚未解決的問題,讓其他開發人員能夠瞭解該成員函數的弱點和難處。如果某給定的錯誤適用於類別內的多個成員函數,則應針對該類別加以載明。
- 成員函數擲出的任何異常狀況。 您應載明成員函數所擲出的所有異常狀況,讓其他程式設計師能夠知道他們的程式碼需要捕捉到什麼。這項作業使用 2.2.2 節 javadoc 的快速概觀中討論的 javadoc @exception 標示。
- 有效範圍決定。 如果您覺得您針對成員函數所選擇的有效範圍將會遭到其他開發人員所質疑(或許您在還沒有其他物件呼叫成員函數之前,就使成員函數成為 public),則您應將您的決定記載下來。這將有助於使其他開發人員明瞭您的想法,讓他們不會浪費時間在憂慮您為何做出令人疑慮的事情。
- 成員函數變更物件的方式。如果成員函數變更某物件,例如銀行帳戶的 withdraw() 成員函數修改帳戶餘額,則必須指出這種情況。這項資訊是必要的,以便讓其他 Java 程式設計師知道成員函數呼叫對於目標物件到底會有何影響。
- 避免使用含有諸如以下資訊的標頭:作者、電話號碼、建立和修改日期以及單元的位置(或檔名),因為這項資訊很快就過時。將擁有權的版權聲明放在裝置的結尾。例如,讀者並不想捲動對於瞭解程式毫無用處的兩頁或三頁的文字,也不想捲動未載有任何程式資訊的文字,例如版權聲明。請避免使用垂直列或是封閉的頁框或方框,它們只會使畫面更加雜亂,而且難以保持一致。使用配置管理工具來保存裝置的歷程。
- 如何呼叫成員函數的範例(如果適用的話)。判斷一段程式碼如何運作的其中一個最容易的方法,就是看它的範例。請考慮包含一或兩個關於如何呼叫成員函數的範例。
- 適用的前置條件和後置條件。前置條件是一項限制,在其下成員函數將會適當運作,而後置條件是一個內容或確認,在成員函數執行完成之後將會為真。[MEY88] 前置條件和後置條件用許多方式說明您在撰寫成員函數時所做的假設 [AMB98]。它確切定義成員函數之使用程度的界限。
- 所有並行問題。 對於許多開發人員而言,並行是一個全新的且複雜的概念,但是對於富經驗的並行程式設計師而言,它充其量只是一個既舊又繁雜的主題。最後的結果是,如果您使用 Java 的並行程式設計特性,則您必須巨細靡遺地註解它。[LEA97] 建議當某類別包含已同步及未同步的成員函數時,您必須註解成員函數所根據的執行環境,特別是當它需要未限定的存取,以便其他開發人員可以安全使用您的成員函數時。當實作 Runnable 介面之類別的 setter(更新欄位的成員函數)未同步化時,則您應載明您的原因。最後,如果您置換或超載成員函數,並變更其同步化,則也應載明原因。
- 只有在註解有助於釐清程式碼時才應該寫它。您不應該針對每一個成員函數記載上述的所有因素,因為並不是所有的因素都適用於每個成員函數。不過,您倒是應該針對您所寫的每一個成員函數,記載它們其中的數個因素。
除了成員函數文件之外,您還需在成員函數內包含註解來說明您的工作。目標是要使您的成員函數較易於瞭解、維護及加強。
有兩種類型的註解應用來記載程式碼的內容:C-style 註解 ( /* and */ ) 和單行註解 ( // )。如先前所討論的,您應慎重考慮選擇一種註解,用於註解程式碼的商業邏輯,另一種用於註銷不需要的程式碼。建議您對商業邏輯用單行註解,因為全註解行及行內註解都可以用這種註解,接在程式碼行結尾。使用 C-style 註解來註銷不需要的程式碼行,因為它使得只用一個註解就註銷數行較為容易。此外,由於 C-style 註解看來非常像文件註解,因此使用它們可能會令人混淆,這會讓人無法理解您的程式碼。因此,請小心使用它們。
在內部,您應一律記載下列各項:
- 控制結果。 說明每一個控制結構,例如比較陳述式和迴圈。您不應該非得閱讀控制結構中的所有程式碼才能判定它做什麼;相反的,您只要查看它前面的一或兩行註解就可以了。
- 程式碼做什麼以及原因。 您固然可以查看一下程式碼,然後想出它做什麼,但是對於不明確的程式碼,則就很難判斷那種做法的原因了。例如,您可以查看一行程式碼,然後輕易地判定訂單的總額套用了 5% 的折扣。這非常輕而易舉。不容易的是要想出套用該折扣的原因。很明顯的,有某種商業規則會指示套用折扣,因此您的程式碼中至少應參照該商業規則,讓其他開發人員能夠瞭解您的程式碼為何會那麼做的原因。
- 區域變數。 雖然我們會在第 5 節中進一步詳述這個項目,但是每一個定義於成員函數的區域變數都應該在它自己的程式碼行上宣告,而且通常應該有一個行內註解,說明它的用法。
- 困難或複雜的程式碼。 如果您發現您無法加以重寫,或是沒有時間,則您必須巨細靡遺地註解成員函數中的任何複雜的程式碼。一個簡略的一般規則是,如果您的程式碼不明確,則您就必須記載它。
- 處理順序。 如果您的程式碼中有必須按定義的順序執行的陳述式,則您應確定有記載這個因素 [AMB98]。最糟糕的是對一段程式碼進行簡單的修改,只是為了看到它已無法再運作,然後花了幾個小時找問題,只是為了看到您把某些事搞得不正常。
- 記載左大括號。您會經常發現您有控制結構在別的控制結構內,而那些控制結構又在另外的控制結構內。雖然您應避免撰寫像這樣的程式碼,但是有時您會發現用這種方式寫程式碼是比較好的。問題是它會變得令人困惑,不知道右大括弧 } 字元屬於哪一個控制結構。好消息是某些程式碼編輯器支援一項特性,它會在您選取左大括弧時自動強調顯示相對應的右大括弧;壞消息是並不是每一個編輯器都支援這項特性。我發現用行內註解(如 //end if、//end for、//end switch)標示右大括號,會使您的程式碼較易於瞭解。
本節涵蓋幾種方法,可協助將專業開發人員與駭客寫碼者區別。這些方法有:
- 記載程式碼。
- 將程式碼分段落或縮排。
- 使用空白字元。
- 遵循 30 秒規則。
- 撰寫簡短、單一指令行。
- 指定作業的次序。
請記住,如果您的程式碼不值得記載,則它也不值得保存。[NAG95] 當您適當地套用本文件中所提議的記載標準和準則時,可以大幅提升程式碼的品質。
有一個增進成員函數之可讀性的方法是將它分段落,或者,換句話說,就是在程式碼建構的範圍內縮排程式碼。大括號({ 和 } 字元)內的所有程式碼形成一個建構。基本概念是,一個建構內的程式碼應統一縮排一個單位。
Java 慣例似乎是左大括弧要放在建構擁有者後面的行上,同時右大括弧應縮排一層。[LAF97] 所指出的要點是,您的組織選擇一種縮排樣式,然後遵守它。請使用您的 Java 開發環境對它產生的程式碼所用的相同縮排樣式。
在 Java 程式碼中加入幾個空白行(稱為空白字元)後,可以將它分成比較小的、 易於領會的區段,有助於使它更容易判讀得多。[VIS96] 建議使用一個空白行來分隔程式碼的邏輯群組(如控制結構),而使用兩個空白行來分隔成員函數定義。如果沒有空白字元,它就很難判讀及瞭解。
其他程式設計師應能夠在看了您的成員函數之後,在 30 秒內充分瞭解它做什麼、它那麼做的原因,以及它做的方式。如果沒有辦法做到這一點,表示您的程式碼難以維護,應加以改進。三十秒,就是這麼多。有一個簡略的好規則是,如果某成員函數超過一個畫面,則它可能就太長了。
您的程式碼應一行做一件事。如果是回到打孔卡片的時代,則嘗試在一行程式碼上盡可能獲得更多的功能,乃是合理的。每當您試圖在一行程式碼上做超過一件以上的事情時,您就使它更難以瞭解。為什麼要這麼做呢?我們希望使我們的程式碼較易於瞭解,讓它能夠較易於維護及加強。正如同成員函數應只做一件事一樣,一行程式碼也應只做一件事。
此外,您寫的程式碼應保持可以在畫面看得到 [VIS96]。您不應該得將編輯視窗向右捲動才能閱讀整個程式碼行,其中包括使用行內註解的程式碼在內。
增進程式碼之可讀性的一個真正容易的方法,是使用括弧(又稱為「圓括弧」)來指定您 Java 程式碼 [NAG95] 和 [AMB98] 中之作業的精確順序。如果您得知道作業的順序,某語言才能瞭解您的程式碼,則表示某一方面問題很嚴重。這泰半是邏輯比較上的問題,在這些邏輯比較中,您將數個其他的比較 AND 及 OR 在一起。請注意,如果您用的是如先前所建議的簡短、單一指令行,則這實在不應該會變成一個問題。
這裡所用的欄位一詞指的是 BDK 呼叫內容 [DES97] 的欄位。欄位是說明物件或類別的一段資料。欄位可能是基本資料類型,像是字串或浮點數,也可能是物件,如客戶或銀行帳戶。
您應使用完整的英文來為您的 [GOS96] 和 [AMB98] 欄位命名,從而使欄位代表的意義顯而易見。本身為集合的欄位(如陣列或向量)應賦予它複數的名稱,以指出它們代表多個值。
範例:
firstName
zipCode
unitPrice
discountRate
orderItems
您應使用完整的英文來作為元件的名稱(介面小組件),字尾用小組件的類型。這可讓您能夠輕易地識別元件的用途,還有其類型,可讓您更容易尋找清單中的每一個元件。許多的視覺化程式設計環境在一個 Applet 或應用程式中提供所有元件的清單,如果每一個元件的名稱都是 button1、button2 等等,則可能會令人非常困惑。
範例:
okButton
customerList
fileMenu
newFileMenuItem
4.1.1.1 元件命名的替代方案:匈牙利表示法
「匈牙利表示法」[MCO93] 係依據使用下列方法來為欄位命名的原則:xEeeeeeEeeeee,其中 x 表示元件類型,EeeeeEeeeee 是完整的英文。
範例:
pbOk
lbCustomer
mFile
miNewFile
主要的優點是這是 C++ 程式碼共同的業界標準,因此已經有很多人遵循它。開發人員從變數的名稱就可以很快地判定出它的類型以及使用方式。主要的缺點就是字首表示法變成
4.1.1.2 元件命名的替代方案:字尾-匈牙利表示法
基本上,這是另外兩個替代方案的組合,它會產生像是 okPb、customerLb、fileM 和 newFileMi 等名稱。主要的優點是元件的名稱會指出小組件的類型,而且同類型的小組件不會在按字母排序的清單中分組在一起。主要的缺點就是您仍然沒有使用全英文說明,這使得標準更難以記住,因為它已偏離基準。
4.1.1.3 設定元件名稱標準
無論您選擇哪一個慣例,您都要建立一個「正式」小組件名稱清單。例如,在為按鈕命名時,您是要使用 Button、PushButton、b 或 pb 呢?請建立一份清單,並讓您組織中的每一位 Java 開發人員都可以使用它
在 Java 中,常數(不變的值)通常都當作類別的 static final 欄位來實作。經認可的使用慣例是採用全部大寫的全英文字,字與字之間用底線 [GOS96]。
範例:
MINIMUM_BALANCE
MAX_VALUE
DEFAULT_START_DATE
這個慣例的主要優點是,它可以協助您區別常數和變數。我們稍後會在本文件中看到:您可以藉由不定義常數來大幅增加程式碼的彈性和可維護性;而應定義傳回常數值的 getter 成員函數。
應賦予集合(如陣列或向量)一個複數化的名稱,代表陣列所儲存的物件類型。此名稱應為完整的英文,其中所有非起始字的第一個字母都大寫。
範例:
customers
orderItems
aliases
這個慣例的主要優點是,它可以協助區別代表多個值的欄位(集合)和那些代表單一值的欄位(非集合)。
當欄位宣告為 protected 時,子類別中的成員函數有可能直接存取它們,會有效地在類別階層內增加耦合。這使得您的類別較難以維護及加強,因此,應加以避免。欄位不可直接存取: 而應使用存取元成員函數(請參閱以下)。
有效範圍 說明 適當用法 public public 欄位可以被任何其他的物件或類別中的任何其他成員函數所存取。 請勿使欄位成為 public。 protected protected 欄位可以被它在其中宣告之類別中的任何成員函數,或是定義於該類別之子類別中的任何成員函數所存取。 請勿使欄位成為 protected。 private private 欄位只能被它在其中宣告之類別(但是不包括子類別)中的成員函數所存取。 所有的欄位都應為 private,並且應由 getter 和 setter 成員函數(存取元)存取。 針對不是永久性的欄位(它不會儲存至永久儲存體),您應將它們標示為 static 或 transient [DES97]。這使得它們符合 BDK 的使用慣例。
名稱隱藏指的是將區域變數、引數或欄位命名為與更大範圍的另一個區域變數、引數或欄位的名稱相同(或類似)的作法。比方說,如果您有一個名稱為 firstName 的欄位,則請勿建立名稱為 firstName 的區域變數或參數,或是任何接近的名稱,像是 firstNames 或 firstName。這會使您的程式碼難以瞭解,並且易於出現錯誤,因為其他開發人員或是您自己在修改時,會誤解您的程式碼,同時會造成難以偵測的錯誤。
每個欄位都應該記載得夠充分,以便讓其他開發人員能夠瞭解它。若要有效,您必須載明:
- 它的說明。 您必須說明欄位,讓他人知道如何使用它。
- 所有適用的不變量。 欄位的不變量是它一律為真的條件。例如,關於欄位 dayOfMonth 的不變量可能是它的值介於 1 到 31 之間(很明顯的,當依據月和年來限制欄位的值時,這個不變量會導致的複雜度會高出很多)。記載對於欄位值的限制,有助於定義重要的商業規則,而能較易於瞭解您程式碼的運作方式。
- 範例。 針對有複雜的企業規則與它們相關聯的欄位,您應提供數個範例值,以使它們較容易瞭解。範例經常像是一幅好的圖片: 勝過千言萬語。
- 並行問題。 對於許多開發人員而言,並行是一個全新的且複雜的概念;實際上,對於富經驗的並行程式設計師而言,它充其量只是一個既舊又繁雜的主題。最後的結果是,如果您使用 Java 的並行程式設計特性,則您必須巨細靡遺地註解它們。
- 有效範圍決定。 如果您將欄位宣告為 private 以外的任何欄位,則您應載明您這麼做的原因。先前已在 4.2 節、欄位有效範圍中討論過欄位的有效範圍,而且在接下來的 4.4 節、使用存取元成員函數中會涵蓋使用存取元成員函數來支援封裝。底線是您最好的確有不將變數宣告為 private 的好理由。
除了命名慣例之外,適當使用存取元成員函數可以達到欄位的可維護性,此函數是提供更新欄位或存取其值之功能的成員函數。存取元成員函數有兩種樣式:setter(又稱為 mutator)和 getter。setter 修改變數的值,而 getter 為您取得它。
雖然以前使用存取元成員函數會增加程式碼的額外負荷,但是現在 Java 編譯器已針對它們的使用加以最佳化,因此不會再有這種情形。存取元可以協助隱藏類別的實作詳細資料。在最多有兩個從中存取變數的控制點(一個 setter 及一個 getter)的情況下,您就能夠藉由將必須在該處進行變更的點減至最少,來增加類別的可維護性。9.3 節、最佳化 Java 程式碼中會討論 Java 程式碼的最佳化。
您的組織可以強制實施的其中一個最重要的標準是使用存取元。某些開發人員並不想使用存取元成員函數,因為他們不願意多敲幾個必要的按鍵;例如,以 getter 而言,您必須在欄位名稱的上方鍵入 "get" 和 "()"。底線是從使用存取元所增加的可維護性和延伸性,超過調整它們的用法。
存取元是唯一可以存取欄位的地方。適當使用存取元成員函數的主要概念是:唯一被容許直接使用欄位的成員函數是存取元成員函數本身。沒錯,是可以直接存取欄位在其中定義之類別的成員函數內的 private 欄位,但是您不會想這麼做,因為這會增加類別內的耦合。
「好的程式設計尋求將部分程式與不必要的、無意的或不要的外界影響隔離。存取修飾元(存取元)為語言提供用來控制這類接觸的一個明確又可以檢查的方法。」[KAN97]
存取元成員函數以下列方式增進類別的可維護性:
- 更新欄位。您有每一個欄位的單一更新點,使修改及測試更為簡單。換句話說,您的欄位被封裝起來。
- 取得欄位的值。您對於欄位的存取方式及存取者具有完全的控制權。
- 取得常數的值及類別的名稱。如果將常數和類別名稱的值封裝在 getter 成員函數中,當那些值/名稱改變時,您只要在 getter 中變更該值即可,不必變更其中有使用到該常數/名稱的每一行程式碼。
- 起始設定欄位。使用延遲初始可以確保欄位一定會起始設定,並且它們只有在被需要時才會起始設定。
- 減少子類別與其子類別之間的耦合。當子類別存取僅透過它們相對應的存取元成員函數繼承欄位時,就能夠變更超類別中的欄位實作而不影響到它的任何子類別,因而有效地減少它們之間的耦合。存取元可以降低「脆弱的基本類別」(超類別中的變更在其整個子類別中波動)的風險。
- 封裝欄位變更。如果企業規則是有關一或多個欄位變更,則您可以修改存取元來提供變更之前的相同功能,讓您能較易於回應新的企業規則。
- 簡化並行問題。 [LEA97] 指出當您依據該欄位的值進行等待時,setter 成員函數提供一個併入 notifyAll 的地方。這使得移至並行解決方案容易得多。
- 名稱隱藏變成比較不成問題。 雖然您應避免名稱隱藏、賦予相同的名稱給區域變數和欄位,但是使用存取元來固定存取欄位表示您可以任意賦予名稱給區域變數。您不必顧慮著要隱藏欄位名稱,因為您無論怎麼樣都不會直接存取它們。
4.4.1.1 何時不要使用存取元
當執行時間是最重要的考量時,是您唯一不應使用存取元的時候。不過,您應用程式內增加的耦合調整這個動作的情況非常少見。
您應賦予 "get" + 欄位名稱這個名稱給 getter 成員函數,除非該欄位代表 Boolean(true 或 false),則應賦予 "is" + 欄位名稱這個名稱給 getter。setter 成員函數則應賦予它 "set" + 欄位名稱這個名稱,無論其欄位類型為何([GOS96] 和 [DES97])。請注意,欄位名稱一律為大小寫混合,其中所有單字的第一個字母大寫。這個命名慣例在 JDK 內被一致採用,並且是進行 Bean 開發的必要慣例。
範例:
欄位 類型 getter 名稱 setter 名稱 firstName string getFirstName() setFirstName() address Addressobject getAddress() setAddress() persistent boolean isPersistent() setPersistent() customerNo int getCustomerNo() setCustomerNo() orderItems Array ofOrderItemobjects getOrderItems() setOrderItems()
存取元的用途不僅僅只是取得及設定實例欄位的值。本節討論如何利用存取元來執行下列作業,以增加程式碼的彈性:
- 起始設定欄位的值
- 存取常數值
- 存取集合
- 同時存取數個欄位
4.4.3.1 延遲初始
變數必須在被存取之前起始設定。兩個學校要起始設定: 在建立物件時起始設定所有的變數(傳統的方法),或是在它第一次使用時起始設定。
第一個方法使用在最先建立物件時被呼叫的特殊成員函數,稱為建構子。這雖然可行,但是它經常被證明容易發生錯誤。在加入新的變數時,您可能很容易就忘了更新建構子。
有一種替代方法稱為延遲初始,其中欄位是由它們的 getter 成員函數起始設定,如以下所示。 請注意 setter 成員函數在 getter 成員函數內的使用方式。注意成員函數檢查分行號碼是否為零;如果是,則它會將它設為適當的預設值。
/** 回答分行號碼,
此號碼是全部帳號最左邊的四個數字。
帳號的格式為 BBBBAAAAAA。
*/
protected int getBranchNumber()
{
if ( branchNumber == 0)
{
// 預設分行號碼為 1000,
// 這是位於 Bedrock 市中心的主要分行。
setBranchNumber(1000);
}
return branchNumber;
}
對於其他物件實際儲存在資料庫中的欄位而言,使用延遲初始相當普遍。例如,當您建立新的庫存項目時,您不必從資料庫中提取您設為預設值的庫存項目類型。而是在它第一次被存取時使用延遲初始來設定這個值,這樣您只要在需要庫存項目類型的物件時,從資料庫中加以讀取即可。
這種方法對於其欄位不是被定期存取的物件而言相當有利。為何要從持續性儲存庫中擷取一些您不打算要使用的資料,而帶來額外的負荷呢?
每當在 getter 成員函數中使用延遲初始時,您都應該載明使用的預設值是什麼以及使用的原因,如同我們在上述範例中所看到的。當您這麼做時,可以釐清在程式碼中使用欄位的方式,增進了它的可維護性和延伸性。
4.4.3.2 常數的存取元
常見的 Java 慣用句是將常數值當作 static final 欄位來實作。這種方法對於保證會穩定的「常數」非常合理。例如,類別 Boolean 實作兩個稱為 TRUE 和 FALSE 的 static final 欄位,其代表該類別的兩個實例。對於其值可能永遠不變的 DAYS_IN_A_WEEK 常數而言,它也會是非常合理。
然而,許多所謂的商業「常數」在經過一段時間後會改變,原因是商業規則改變。請考量下列範例:Archon Bank of Cardassia (ABC) 向來堅持帳戶的餘額最少要有 $500 才能賺取利息。為實作這個案例,我們可以將一個名為 MINIMUM_BALANCE 的靜態欄位加入類別 Account,這個欄位將在成員函數中用來計算利息。雖然這會行得通,但是它缺乏彈性。如果商業規則改變,而且不同類型的帳戶有不同的最低餘額,也許儲蓄存款帳戶為 $500,但是支票帳戶只要 $200,會發生什麼事呢?如果商業規則變成第一年的最低餘額為 $500、第二年為 $400、第三年為 $300,依此類推,則會發生什麼事呢?或許此規則在夏天會變成 $500,但是在冬天卻只要 $250?或許將來會需要實作這全部規則的組合。
這裡所做的結論是將常數當作欄位來實作較缺乏彈性。解決方案是將常數當作 getter 成員函數來實作。在上述我們的範例中,靜態(類別)成員函數 getMinimumBalance() 的彈性遠超過靜態欄位 MINIMUM_BALANCE,因為我們可以在這個成員函數中實作不同的商業規則,並且針對不同類型的帳戶適當加以繼承。
/** 取得帳號的值。帳號的格式如下:BBBBAAAAAA,其中 BBBB 是分行號碼,AAAAAA 是分行帳號。*/public long getAccountNumber(){return ( ( getBranchNumber() * 100000 ) + getBranchAccountNumber() );}/**設定帳號。帳號的格式如下:BBBBAAAAAA,其中 BBBB 是分行號碼,AAAAAA 是分行帳號。*/public void setAccountNumber(int newNumber){setBranchAccountNumber( newNumber % 1000000 );setBranchNumber( newNumber / 1000000 );}
常數 getter 的另一個優點是它們有助於增加程式碼的一致性。請考量以上所示的程式碼:它的運作不正常。帳號是分行號碼接上分行帳號。測試程式碼後,我們發現 setter 成員函數 setAccountNumber() 並沒有適當更新分行帳號;它取最左邊的三個數字,而不是四個數字。那是因為我們是用 1,000,000 而非 100,000 來擷取欄位 branchAccountNumber。如果我們對這個值使用單一來源(如以下所示的常數 getter getAccountNumberDivisor()),則我們的程式碼會較為一致並且能夠運作。
/**傳回將完整帳號內的分行帳號與分行號碼分隔所需的除數。完整帳號的格式為 BBBBAAAAAA。*/public int getAccountNumberDivisor(){return ( (long) 1000000);}/**取得帳號的值。帳號的格式如下:BBBBAAAAAA,其中 BBBB 是分行號碼,AAAAAA 是分行帳號。*/public long getAccountNumber(){return ( ( getBranchNumber() * getAccountNumberDivisor() ) + getBranchAccountNumber() );}/**設定帳號。帳號的格式如下:BBBBAAAAAA,其中 BBBB 是分行號碼,AAAAAA 是分行帳號。*/public void setAccountNumber(int newNumber){setBranchAccountNumber( newNumber % getAccountNumberDivisor() );setBranchNumber( newNumber / getAccountNumberDivisor() );}藉由使用常數的存取元,我們可以減少錯誤的機會,而在同時,增加了系統的可維護性。當帳號的佈置變更,而且我們知道它最終會變更時,則機會在於我們的程式碼將會較易於變更,因為我們已隱藏並集中化建置或均分帳號所需的資訊。
4.4.3.3 集合的存取元
存取元的主要用途是將存取權封裝到欄位,以減少程式碼內的耦合。毫無疑問的,對比單一值欄位複雜的集合(如陣列和向量)所必須實作的不只是標準的 getter 和 setter 成員函數而已。特別是由於您可以新增至集合及從中移除,因此必須包含存取元成員函數才能這麼做。請針對本身為集合的欄位,適當新增下列的存取元成員函數:
成員函數類型 命名慣例 範例 集合的 getter getCollection() getOrderItems()集合的 setter setCollection() setOrderItems()將物件插入集合中 insertObject() insertOrderItem()從集合中刪除物件 deleteObject() deleteOrderItem()建立及加入新物件至集合 newObject() newOrderItem()這種方法的優點是集合經完整封裝,可讓您稍後將它替換為另一個結構,像是鏈結清單或 B-tree。
4.4.3.4 同時存取數個欄位
存取元成員函數的其中一個效力是,它們讓您能有效地施行商業規則。例如,考量形狀的類別階層。形狀的每一個子類別透過使用「xPosition 和 yPosition」來得知其位置,並且可以藉由呼叫成員函數 move(Float xMovement, Float yMovement),在二維平面上的畫面上移動。就我們的目的而言,一次沿著一個軸移動形狀並不合理;而是會同時沿著 x 和 y 軸移動(將值 0.0 當作 move() 成員函數的任一參數傳遞是可以接受的)。這意味著 move() 成員函數應為 public,但是成員函數 setXPosition() 和 setYPosition() 則應都是 private,這樣 move() 成員函數才能適當加以呼叫。
一個替代的實作方式將會是採用同時更新兩個欄位的 setter 成員函數,如以下所示。成員函數 setXPosition() 和 setYPosition() 仍然會是 private,這樣它們才不會被外部的類別或子類別直接呼叫(您應加入一些如以下所示的說明文件,指出不應直接呼叫它們)。
/** 設定形狀的位置 */protected void setPosition(Float x, Float y){setXPosition(x);setYPosition(y);}/** 設定 x 位置。重要事項:請呼叫 setPosition(),而不是呼叫這個成員函數。*/private void setXPosition(Float x){xPosition = x;}/** 設定形狀的 y 位置重要事項:請呼叫 setPosition(),而不是呼叫這個成員函數。*/private void setYPosition(Float y){yPosition = y;}
請永遠盡力使存取元為 protected,以便只有子類別可以存取欄位。您應只在「外部類別」需要存取欄位時,才應使相對應的 getter 或 setter 為 public。請注意,getter 成員函數為 public,而 setter 為 protected,是相當常見的。
有時您必須使 setter 為 private,以確保某些不變量被保留起來。例如,Order 類別可能有一個代表 OrderItem 實例集合的欄位,以及稱為 orderTotal 的第二個欄位,這是整份訂單的總計。orderTotal 是一個便利的欄位,它是所訂購項目的總計或所有的小計。唯一必須更新 orderTotal 值的成員函數,是那些操作訂單項目集合的成員函數。如果假設那些成員函數全部都在 Order 中實作,則您應使 setOrderTotal() 為 private,即使 getOrderTotal() 較有可能是 public。
您應指定有效值給靜態欄位(又稱為類別欄位),因為您不能假設類別的實例會在靜態欄位被存取之前建立。
區域變數是定義於一個建構範圍(經常是成員函數)內的物件或資料項目。區域變數的範圍是它在其中定義的建構。區域變數的重要程式碼撰寫標準集中在:
- 命名慣例
- 宣告和說明文件慣例
一般而言,區域變數的命名是遵循對欄位所用的相同慣例;換句話說,是使用完整的英文,其中任何非起始字的第一個字母為大寫。
不過,為了方便起見,有數個特定類型的區域變數放寬使用這個命名慣例:
- 串流
- 迴圈計數器
- 異常狀況物件
當有一個輸入和(或)輸出串流在成員函數內被開啟、使用然後關閉時,常見的慣例是分別使用 In 和 Out 作為這些串流的名稱 [GOS96]。針對用於輸入及輸出的串流,其含意為使用名稱 inOut。
這個命名慣例的常見替代方案(雖然與 Sun 的建議衝突)是分別使用名稱 inputStream、outputStream 和 ioStream,而非 In、Out 和 inOut。
由於對區域變數使用迴圈計數器非常普遍,也由於它在 C/C++ 中被接受,因此在 Java 程式設計中,可接受 i、j 或 k 用於迴圈計數器 [GOS96]。如果您對迴圈計數器使用這些名稱,使用它們時請保持一致。
一個常見的替代方案是使用像是 loopCounter 或就是 counter 的名稱,但是這種方法的問題是,您經常會在需要多個計數器的成員函數中發現像是 counter1 和 counter2 等名稱。底線是 i、j、k 如同計數器般運作;它們可以很快鍵入,而且它們通常會被接受。
由於異常狀況處理在 Java 程式碼撰寫中也非常普遍,因此使用字母 e 作為同屬異常狀況被視為可以接受 [GOS96]。
在 Java 中,有數個關於區域變數之宣告和註解的慣例。這些慣例有:
- 每行程式碼宣告一個區域變數。 這與每行程式碼一個陳述式一致,而能夠以行內註解說明每一個變數。
- 以行內註解說明區域變數。 行內註解是一種樣式,其中以 // 代表的單行註解緊跟在同一行程式碼上的指令後面(這稱為行尾註解)。您應說明所用的區域變數、 使用它的適當地方,以及使用它的原因。這可以使您的程式碼較易於瞭解。
- 區域變數只用於一件事 每當您為了多個原因而使用一個區域變數時,您都會有效地降低其內聚性,使它難以瞭解。您也會由於前面程式碼中的區域變數先前的值的非預期副作用,而增加了將錯誤引入程式碼的機會。重複使用區域變數固然會因為需要配置的記憶體較少而較有效率,但是重複使用區域變數會降低程式碼的可維護性,並使它較為脆弱。為了不必配置較多的記憶體以節省一點點記憶體而造成這種情況,通常是不值得的。
不熟悉您程式碼的人可能很難找到在程式碼行之間(例如,在 if 陳述式的範圍內)宣告的區域變數。
一個替代方案是就在最先使用區域變數前面宣告它們,而不是在程式碼頂端宣告它們。由於您的成員函數還是應該要簡短(請參閱 3.5.5、撰寫簡短、單一指令行,因此要跑到程式碼頂端判斷區域變數的內容究竟是什麼,應該就並不全然那麼糟糕了。
成員函數之參數和引數的重要標準著重在它們的命名及註解方式。參數一詞用來參照成員函數引數。
參數的命名應遵循針對區域變數所用的相同慣例。如同區域變數一樣,名稱隱藏是一個課題。
範例:
customer
inventoryItem
photonTorpedo
e
一個取自 Smalltalk 的可行替代方案是使用區域變數的命名慣例,但是在名稱前面另外加上一個 "a" 或 "an"。 加上 "a" 或 "an" 幫助使參數移出區域變數和欄位,並避免名稱隱藏問題。這是偏好的方法。
範例:
aCustomer
anInventoryItem
aPhotonTorpedo
anInputStream
anException
成員函數的參數使用 javadoc @param 標示說明於成員函數的標頭說明文件中。您應說明下列各項:
- 它應用於什麼。 您必須說明參數用於什麼,以便其他開發人員瞭解將會如何使用該參數的完整背景。
- 任何限制或前置條件。如果成員函數無法接受參數全部範圍的值,則該成員函數的呼叫者必須知道。也許成員函數僅接受正數或五個字元以下的字串。
- 範例。 如果某參數應該是什麼並不是完全明顯,則您應在說明文件中提供一或多個範例。
如果適當的話,請對參數的類型指定介面(如 Runnable),而不是指定類別(如 Object)。優點是這種方法可能會較為明確(Runnable 比 Object 明顯),或者也可能是一個較好的支援多型性方式,視狀況而定。請指定參數支援特定的介面(這意味著它只需在多種形態上符合您的需求即可),而不是堅持它為某特定類別階層中的類別之實例。
本章著重於類別、介面、套件及編譯單元的標準和準則。類別是一個範本,可從中實例化(建立)物件。類別包含欄位和成員函數的宣告。介面是共同簽章的定義(包括成員函數和欄位),這是實作介面的類別所必須支援的。套件是指相關類別的集合。最後,編譯單元是指程式碼檔案,類別和介面在其中宣告。由於 Java 容許編譯單元儲存在資料庫中,因此個別的編譯單元可能與實體程式碼檔案沒有直接的關係。
類別的重要標準係依據下列各項:
- 命名慣例
- 說明文件慣例
- 宣告慣例
- public 和 protected 介面
標準的 Java 慣例使用完整的英文,其開頭為大寫的第一個字母,其餘的名稱則使用混合大小寫([GOS96] 和 [AMB98])。
類別名稱的格式必須是單數。
範例:
Customer
Employee
Order
OrderItem
FileStream
String
下列資訊應出現在類別定義正前面的文件註解中:
- 類別的用途。 開發人員必須知道類別的一般用途,以便他們能夠判定它是否符合他們的需求。養成記下任何可以瞭解類別之要點的習慣;例如,它是型樣的一部分嗎?或是使用它時是否有任何令人關注的限制呢 [AMB98]?
- 已知的錯誤。如果某類別有任何尚未解決的問題,則應將它們記載下來,讓其他開發人員能夠瞭解該類別的弱點和難處。此外,也必須載明不修正錯誤的原因。請注意,如果錯誤是單一成員函數所特有,則它應是與成員函數有直接的關聯。
- 類別的開發或維護歷程。 常見的作法是包含歷程表格,其中列出日期、作者,以及對類別所做變更的摘要。這可讓維護程式設計師洞察過去對類別所做的任何修正,並載明誰對類別做了什麼修正。
- 載明適用的不變量。 不變量是一組確認集,這是關於在所有的「穩定」時間都必須為真的實例或類別,其中的穩定時間被定義為在物件或類別上呼叫成員函數之前,以及緊接在成員函數被呼叫之後的期間 [MEY88]。藉由載明別的不變量,您可以提供其他開發人員關於可以如何使用類別的有用洞察力。
- 並行策略。任何實作介面 Runnable 的類別都應完整說明其並行策略。對於許多程式設計師而言,並行程式設計都是一個嶄新的複雜課題,因此您需要多投入一點時間來確定人們可以瞭解您的作品。載明您的並行策略以及您棄其他策略而選擇該策略的原因,非常重要。常見的並行策略 [LEA97] 包括下列各項:
- 已同步化的物件
- 阻礙的物件
- 被監視的物件
- 已版本化的物件
- 並行原則控制器
- 接收器
一種讓您的類別較易於瞭解的方法,是以一致的方式宣告它們。Java 中常見的方法是按下列順序宣告類別:
- public member functions
- public fields
- protected member functions
- protected fields
- private member functions
- private fields
[LAF97] 指出應先列出建構子和 finalize(),想必是因為這些是另一位開發人員將會最先查看以瞭解如何使用類別的成員函數。此外,由於我們訂有將所有欄位宣告為 private 的標準,因此宣告順序實際上會如下:
建構子
finalize()public 成員函數
protected 成員函數
private 成員函數
private 欄位
在成員函數的每一個分組內,通常會按字母順序列示它們。許多開發人員選擇先列出每一個分組內的 static 成員函數,接著列出 instance 成員函數;然後在這兩個子分組中的每一個分組內,按字母順序列示成員函數。這兩種方法都是有效的;您只要選擇一種方法,然後遵守它即可。
物件導向設計的其中一個基本概念是將類別的 public 介面減至最少。這麼做的原因有數個:
- 簡化學習。 要學習如何使用類別,您只要瞭解其 public 介面即可。public 介面越小,類別越容易學習。
- 減少耦合。 每當一個類別的實例傳送訊息至另一個類別的實例或直接傳送至類別本身時,兩個類別就變成耦合。將 public 介面減至最少意味著將耦合的機會減至最小。
- 較佳的彈性。 這與耦合有直接的關係。每當您要變更成員函數在您 public 介面中的實作方式時(也許您想要修改成員函數傳回的內容),則您有可能必須修改任何呼叫該成員函數的程式碼。public 介面越小,封裝就更為強化;因而您的彈性也就越大。
很明顯的,將 public 介面減至最少相當值得,但是將 protected 介面也減至最少是否值得,就往往不是那麼明確了。其基本觀念是:站在子類別的觀點,其所有超類別的 protected 介面實際上都是 public。子類別可以呼叫 protected 介面中的任何成員函數。因此,您要最小化類別的 protected 介面的原因,與要最小化 public 介面的原因是一樣的。
7.1.4.1 先定義 public 介面
有經驗的開發人員大部分都會在開始撰寫類別的 public 介面的程式碼之前先加以定義。
- 首先,如果您不知道類別將執行什麼服務或行為,那麼您還有許多設計工作要做。
- 第二,它可讓您很快地接取出類別,這樣依賴它的其他開發人員至少可以處理該 Stub,直到開發出「真實的」類別。
- 第三,這種方法提供您起始架構,可以據此建置您的類別。
介面的重要標準係依據下列各項:
- 命名慣例
- 說明文件慣例
Java 慣例是使用混合大小寫來為介面命名,其中每一個字的第一個字母大寫。對於介面的名稱而言,偏好的 Java 慣例是使用敘述性的形容詞(如 Runnable 或 Cloneable),不過敘述性的名詞(如 Singleton 或 DataInput)也相當普遍 [GOS96]。
7.2.1.1 替代方案
在介面名稱前冠上字母 "I"。 如 [COA97] 所指出的,在介面名稱前面附加字母 "I" 會導致像是 ISingleton 或 IRunnable 等名稱。這種方法可以幫助區分介面名稱與類別和套件名稱。基於以下很簡單的事實,讓我喜歡這種可能的命名慣例:它可以使您的類別圖(有時又稱為物件模型)較容易判讀。主要的缺點是現有的介面(如 Runnable)並不是用這種方法來命名。這種介面命名慣例在 Microsoft 的 COM/DCOM 架構中也相當普遍。
下列資訊應出現在介面定義正前面的文件註解中:
- 敘述用途。 其他開發人員必須先瞭解介面所蘊涵的概念,然後才能使用它。換句話說,他們必須知道它的用途。您是否需要定義介面的一個很好的測試是,您是否能夠輕易地說出其用途。如果您說不太出來,則很可能您不需要介面作為開頭。由於介面的概念對於 Java 是全新的,因此人們還沒有太多適當使用它們的經驗,同時他們很可能會因為它們是全新的而過度使用它們。
- 介面該用的及不該用的使用方式。 開發人員必須知道應該如何使用介面,以及不應該如何使用介面 [COA97]。
由於成員函數的簽章定義於介面,因此對於每一個成員函數簽章而言,您都必須遵循第 3 章中所討論的成員函數說明文件慣例。
套件的重要標準係依據下列各項:
- 命名慣例
- 說明文件慣例
有數個規則與套件的命名相關聯。這些規則有(按順序):
- 識別字用句點分隔。 為了使套件名稱更容易判讀,Sun 建議套件名稱中的識別字用句點分隔。例如,套件名稱 java.awt 由 java 和 awt 兩個識別字所組成。
- Sun 出品的標準 Java 分送套件的開頭為識別字 "java"。 Sun 保留了這項權利,使得無論 Java 開發環境的供應商是誰,標準 Java 套件都是以一致的方式命名。
- 區域套件名稱的開頭是並非全大寫的識別字。 區域套件是在您組織內部使用,不會分送至其他的組織。舉例而言,這些套件名稱包括 persistence.mapping.relational 和 interface.screens。
- 廣域套件名稱的開頭為專為您組織保留的網際網路網域名稱。 將分送至多個組織的套件應包含分送方組織的網域名稱,其中最上層網域類型大寫。為分送先前的套件,它們會命名為 com.rational.www.persistence.mapping.relational 和 com.rational.www.interface.screens.0
您應維護一或多份外部文件,說明您的組織所開發之套件的用途。您應載明每一個套件的下列事項:
- 套件的基本原理。 其他開發人員需要通曉套件的全部內容,以便他們能夠決定是否要使用它,而如果它是共用套件,則可以決定是否要予以加強或延伸。
- 套件中的類別。 包含套件中的類別和介面清單,以及每一個類別和介面的簡要線上說明,以便其他開發人員知悉套件的內容。
要訣:使用套件的名稱建立 HTML 檔案,並將它放在套件的適當目錄中。這個檔案應後置 .html。
編譯單元的標準和準則係依據下列各項:
- 命名慣例
- 使用慣例註解
您應賦予編譯單元(在本案例中是一個程式碼檔案)的名稱應為在其內宣告的主要類別或介面之名稱。請使用套件或類別的相同名稱(使用相同的大小寫)作為檔名。該檔名應後置副檔名 .java。
範例:
Customer.java
Singleton.java
SavingsAccount.java
雖然您應盡力讓每個檔案僅宣告一個類別或介面,但是有時在同一個檔案中定義數個類別(或介面)並不為過。一個簡略的一般規則是:如果類別 B 唯一的用途是封裝只有類別 A 需要的功能,則類別 B 出現在與類別 A 相同的程式碼檔案中乃是合理的。結果是,下列註解慣例適用於程式碼檔案,並不專用於類別:
- 針對具有數個類別的檔案,列出每一個類別。 如果檔案包含多個類別,您應提供類別的清單以及各個類別的簡要說明。
- 檔名和(或)識別資訊。 檔案的名稱應在檔案頂端指明。優點是如果印出程式碼,您知道該程式碼的程式檔是什麼。
- 版權資訊。 您應指出所有適用於檔案的版權資訊。通常會指出版權的年份,以及持有版權的個人或組織的名稱。請注意,程式碼作者並不一定是版權的持有者。
一般原理是只對錯誤使用異常狀況:邏輯和程式設計錯誤、配置錯誤、 資料毀損、資源耗盡等等。一般規則是系統在正常狀況以及沒有超載或硬體故障的情況下,不應引發任何異常狀況。
- 使用異常狀況來處理邏輯和程式設計錯誤、配置錯誤、資料毀損,以及資源耗盡。
儘早由適當的記載機制報告異常狀況,包括引發這些異常狀況所在的點。
- 將從給定的抽象化所匯出的異常狀況數減至最少。
在大型系統中,處理每一個層次的大量異常狀況會使程式碼難以判讀及維護。異常狀況處理程序有時會阻礙正常處理程序的進展。
有好幾個方法可以將異常狀況數減至最少:
- 僅匯出幾個異常狀況,但是提供「診斷」基本元素,可讓您查詢錯誤摘要或不良物件,以取得關於所發生問題之本質的更詳細資訊。
- 將「異常狀況」狀態新增至物件,並提供基本元素以明確檢查物件的有效性。
- 請勿使用異常狀況來代表頻繁、預期的事件。
在使用異常狀況來代表不是明確錯誤的狀況上,有好幾個不便之處:
- 它令人困惑。
- 它通常會迫使控制流程中出現一些較難以瞭解及維護的分裂。
- 它使程式碼的除錯較為麻煩,因為大部分的來源層次除錯程式依預設會標出所有的異常狀況。
例如,請勿使用異常狀況作為函數所傳回的某種形式的額外值(像是搜尋中的 Value_Not_Found);使用帶有 "out" 參數的程序,或採用表示 Not_Found 的特殊值,或是在記錄中將傳回的類型壓縮為區別元件 Not_Found。
- 請勿使用異常狀況來實作控制結構。
這是上一個規則的特殊情況:異常狀況不應用來作為一種形式的 "goto" 陳述式。
- 請確定狀態碼具有適當的值。
當使用子程式所傳回的狀態碼作為 "out" 參數時, 請務必確定有一個值指定給 "out" 參數, 方法為使這個陳述式成為子程式主體中的第一個可執行陳述式。有系統地使所有的狀態依預設成功或失敗。考量離開子程式的所有可能的出口,包括異常狀況處理常式。
- 在本端環境執行安全檢查;不要期待您的用戶端這麼做。
如果子程式在沒有被提供適當的輸入時可能會產生錯誤輸出,請在子程式中安裝程式碼,以受控制的方式來偵測及報告無效的輸入。請勿依賴註解來告知用戶端傳遞適當的值。註解幾乎遲早一定會被忽略,因此若是未偵測到無效的參數,則會導致難以除錯的錯誤。
本章說明數個重要的標準和準則,這些標準和準則普遍到足以需要它們專屬的篇章。
您從外部來源所購買或重複使用的任何 Java 類別庫或套件都應經認證為 100% 純 Java [SUN97]。藉由施行這項標準,將保證您重複使用的類別庫或套件會在您選擇要在其上部署它的所有平台上運作。您可以從各種不同的來源(專攻 Java 類別庫的協力廠商開發公司,或是您組織內的另一個部門或專案小組)取得 Java 類別、套件或 Applet。
import 陳述式容許使用萬用字元來指出類別的名稱。例如,陳述式
import java.awt.*;會帶入套件 java.awt 中的所有類別。但是實際上並不是全然如此。真正發生的是,您從 java.awt 套件中使用的每個類別都會在您的程式碼被編譯時被帶入其中,您不使用的類別則不會被帶入。這聽起來雖然好像是不錯的特性,但是它降低了您程式碼的可讀性。一個比較好的方法是完全限定您的程式碼使用的類別名稱 [LAF97]; [VIS96]。以下範例中顯示一個比較好的匯入類別方法:
import java.awt.Colorimport java.awt.Buttonimport java.awt.Container
最佳化程式碼是程式設計師最後(而不是最先)應該要考量的其中一件事。將最佳化留到最後,是因為您應只要最佳化需要最佳化的程式碼。程式碼的一小部分導致極大半的處理時間極為常見,而這就是您應加以最佳化的程式碼。經驗不足的程式設計師所造成的典型錯誤,是試圖最佳化他們所有的程式碼,就連已執行得夠快的程式碼也不放過。
- 不要浪費時間在最佳化無關緊要的程式碼上!
最佳化程式碼時應該尋找什麼呢?[KOE97] 指出最重要的因素是大量輸入時固定的額外負荷和效能。原因很簡單: 固定的額外負荷主宰了小量輸入的執行時期速度,而演算法則主宰大量輸入的執行時期速度。 Koenig 的簡略規則是,對於小量和大量輸入運作良好的程式,對於中量的輸入也很可能是如此。
必須撰寫在數個硬體平台和(或)作業系統上運作之軟體的開發人員必須瞭解不同平台中的一些特些。似乎可能會耗用特定時間量的作業(例如記憶體和緩衝區的處理方式),經常會在不同的平台上出現重大的差異。常見的是,您需要視不同的平台而有不同的程式碼最佳化方式。
在最佳化程式碼時要注意的另一個問題是使用者的優先順序,因為人員對於特別的延遲會很敏感,而這要看前後情況而言。例如,您的使用者很可能比較會樂於看到畫面馬上呈現,然後花八秒載入資料,而不是在費了五秒載入資料後,才跑出畫面來。換句話說,只要立即給他們一些回應,大部分的使用者都願意稍微等久一點,這是在最佳化程式碼時所必須具備的重要概念。
- 您不必一定得讓您的程式碼跑快一點,以便在您使用者的觀點上最佳化它。
雖然最佳化可能攸關應用程式的成敗,但是別忘了程式碼能否適當運作,卻遠比它來得重要。請牢記,跑不快的有效軟體永遠勝過跑得快的無效軟體。
物件導向測試是一項緊要的主題,但是卻被物件開發社群所全盤忽略。 實際上,無論您選擇在其中採用什麼語言,您自己或他人總會有人得測試您所寫的軟體。 測試控制工具是用來測試應用程式的成員函數集合,其中有一部分是內嵌於類別本身(稱為內建測試),一部分則是內嵌於專用的測試類別。
- 在所有的測試成員函數名稱前冠上 "test"。 這可讓您在程式碼中快速尋找所有的測試成員函數。在測試成員函數名稱前冠上 "test" 的優點是,它可讓您在編譯程式碼的正式作業版本之前,能夠輕易地從中除去測試成員函數。
- 為所有的成員函數測試成員函數命名時保持一致。方法測試是驗證單一成員函數是否如所定義般執行的動作。為所有的成員函數測試成員函數命名時,都應遵循格式 "testMemberFunctionNameForTestName"。例如,用來測試 withdrawFunds() 的測試控制工具成員函數將會包括 testWithdrawFundsForInsufficientFunds() testWithdrawFundsForSmallWithdrawal()。 如果您有一系列針對 withdrawFunds() 的測試,可以選擇撰寫一個呼叫它們全部的成員函數,稱為 testWithdrawFunds()。
- 為所有的類別測試成員函數命名時保持一致。類別測試是驗證單一類別是否如所定義般執行的動作。為所有的類別測試成員函數命名時,都應遵循格式 "testSelfForTestName"。例如,用來測試 Account 類別的測試控制工具成員函數 testSelfForSimultaneousAccess() 和 testSelfForReporting()。
- 建立用於呼叫類別測試的單一點。 開發一個稱為 testSelf() 的靜態成員函數,呼叫所有的類別測試和方法測試成員函數。
- 註解測試控制工具成員函數。 註解您的測試控制工具成員函數。註解中應包含測試的說明以及預期的測試結果。
擁有標準文件並不會自動使您比開發人員更有產能。要能夠成功,您必須選擇要成為較有產能,這表示您必須有效地套用這些標準。
下列的忠告將會幫助您更有效地使用本文件中所述的 Java 程式碼撰寫標準和準則。
- 瞭解標準。 花點時間來瞭解每一項標準和準則能夠帶來較佳產能的原因。例如,不要只是因為這些準則告訴您要在各個區域變數的所在行上加以宣告,您就這麼做。您會這麼做是因為您瞭解它會讓人更加瞭解您的程式碼。
- 信任它們。 瞭解每一項標準只是一個起頭,您還必須信任它們。遵循標準不應該是您有時間才做的事情;它應該是因為您相信這是寫程式碼的最好方式而一定會採行的事。
- 寫程式碼時就遵循它們,而不是事後。 有記載的程式碼在您寫它及寫好之後比較易於瞭解。無論在開發還是維護期間,命名保持一致的成員函數和欄位比較容易使用。無論在開發還是維護期間,簡潔的程式碼都比較容易使用。底線是遵循標準會增加您開發時的產能,並且會使您的程式碼較易於維護(因而也會使維護開發人員更具產能)。如果您從一開始就撰寫簡潔的程式碼,則您在建立它時會受益良多。
- 使它們成為品質保證流程的一部分。 程式碼檢驗的一部分應為確定程式碼遵循您的組織所採用的標準。使用標準作為基礎,在此基礎上將您的開發人員訓練及教導得變成更具產能。
- 程式是為了人,而不是為了機器而寫。 您開發工作的首要目標應該是讓其他人容易瞭解您的程式碼。如果其他人都不明究理,則它稱不上是什麼好程式。請使用命名慣例。記載程式碼。將它分段落。
- 先設計,再撰寫。 您遇到過您程式所依附的部分程式碼必須更改的情況嗎?也許是有一個新的參數要傳遞到成員函數,也或許是一個類別必須分成數個類別。您得額外多費多少功夫才能確定您的程式碼能與已修改程式碼的重新配置版本一起運作呢?您恐怕難有好心情吧?您有沒有自問為何有人原先在寫程式碼時沒有先停下來仔細考量,也就不必發生這種情況,還是他們應該先設計好呢?您一定有這麼做。如果您在實際開始寫程式碼之前,先花點時間想好您要如何撰寫程式碼,那過撰寫它所花的時間可能會少一點。此外,您只要事先好好考量,就很可能會降低將來更改程式碼的影響。
- 以小量的步驟開發。 以小量的步驟開發(先寫幾個成員函數、測試它們,然後再多寫幾個成員函數)經常會遠比一次寫了一大串程式碼,然後才試著修正它來得有效率。測試及修正 10 行程式碼遠比測試及修正 100 行程式碼來得容易;事實上,要說以每次增加 10 行來撰寫、測試及修正 100 行程式碼所花的時間,還不到撰寫一整塊執行相同工作的 100 行程式碼的一半,可說是毫無疑問的。
原因很簡單。每當您在測試程式碼時發現錯誤時,您幾乎總會在您剛寫好的新程式碼中發現該錯誤(當然假設其餘的程式碼在一開始是相當穩定的)。在一小段程式碼中抓錯誤的速度,要遠比在一大段程式碼中抓錯誤來得快。以小量的增量步驟開發時,可以縮減尋找錯誤所花的平均時間,轉而減少整體的開發時間。
- 使程式碼保持簡潔有力。 在技術上要撰寫複雜的程式碼並不困難,但是如果其他人無法理解,則它就談不上是什麼好程式。當某人甚或是您第一次被要求修改一段複雜的程式碼來修正錯誤或加強它時,重寫程式碼的機率很高。事實上,您甚至可能得重寫別人的程式碼,為的是它太難以理解了。當您重寫原開發人員的程式碼時,您對他們有何感想呢?您覺得當事人是天才還是白癡?寫出後來得重寫的程式碼實在不是什麼光彩的事,所以還是請遵循 KISS 規則:保持簡單,不要複雜。
- 學習一般型樣、反型樣和慣用句。 有相當多的分析、設計和流程型樣,以及程式設計慣用語可用於指引您增加您的開發產能。
本章彙總了一些準則,在這裡提供您方便使用,而這些準則編排成數個單頁的 Java 程式碼撰寫標準(按主題收集)摘要。這些主題有:
- Java 命名慣例
- Java 說明文件慣例
- Java 程式碼撰寫慣例
在我們彙總說明本白皮書中所述的其餘標準和準則之前,我想重申一下首要的指示:
當您違反標準而行時,請記載下來。 您可以違反所有的標準,但是除了這個。如果您這麼做,則必須載明違反標準的原因、違反標準的可能含意,以及標準可以套用到這種情況之前可能/必須發生的任何狀況。
除了以下討論的幾個例外之外,在為事項命名時,您應一律使用完整的英文。此外,您通常應使用小寫字母,但是類別名稱和介面名稱的第一個字母以及任何非起始字的第一個字母應為大寫。
一般概念:
- 使用完整的英文。
- 使用適用於網域的專有名詞。
- 使用混合大小寫來使名稱易於辨認。
- 謹慎使用簡短格式,但是如果您這麼做,則花點心思使用它們。
- 避免長名稱(少於 15 個字元是個好主意)。
- 避免只有在大小寫上類似或有差異的名稱。
- 避免使用底線。
項目 命名慣例 範例 引數/
參數使用傳遞的值/物件的全英文說明,可能在名稱前冠上 "a" 或 "an"。很重要的事是選擇一種方法,然後遵守它。 customer、account、 - 或 -aCustomer、anAccount欄位/
欄位/
內容使用欄位的全英文說明,其中第一個字母小寫,而任何非起始字的第一個字母大寫。 firstName、lastName、 warpSpeedBoolean getter 成員函數 所有的 Boolean getter 前面都必須冠上 "is" 一字。如果您遵循上述的 Boolean 欄位命名標準,則就賦予它欄位的名稱。 isPersistent()、isString()、 isCharacter()類別 使用全英文說明,其中所有單字的第一個字母大寫。 Customer、SavingsAccount編譯單元檔案 使用類別或介面的名稱,而若是除了主要類別之外,檔案中還有多個類別,則結尾請用 ".java" 來指出它是'程式碼檔案。 Customer.java、SavingsAccount.java、Singleton.java元件/
小組件使用全英文說明,說明對其使用的元件,結尾接上元件的類型。 okButton、customerList、fileMenu建構子 使用類別的名稱。 Customer()、SavingsAccount()解構子 Java 並沒有解構子,但是會在對物件進行記憶體回收之前呼叫 finalize() 成員函數。 finalize()異常狀況 一般接受使用字母 "e" 來代表異常狀況。 e最終 static 欄位(常數) 全部使用大寫字母,字與字之間用底線分隔。有一個較好的方法使用最終 static getter 成員函數,因為它們大幅增加彈性。 MIN_BALANCE、DEFAULT_DATEGetter 成員函數 在所存取的欄位名稱前面冠上 "get"。 getFirstName()、getLastName()、getWarpSpeeed()介面 使用全英文說明,說明介面所包含的概念,其中所有單字的第一個字母大寫。習慣上會在名稱前面冠上 "able"、"ible" 或 "er",但這並非必要。 Runnable、Contactable、Prompter、Singleton區域變數 使用全英文說明,其中第一個字母小寫,但是請勿隱藏現有的欄位/欄位。比方說,如果您有一個名為 "firstName" 的欄位,則請勿讓區域變數稱為 "firstName"。 grandTotal、customer、newAccount迴圈計數器 一般而言,接受使用字母 i、j 或 k,或是名稱 counter。 i、j、k、counter套件 使用全英文說明,其中使用混合大小寫,而每一個字的第一個字母大寫,其他的則全為小寫。如果是廣域套件,請將網際網路網域的名稱反轉,然後接上套件名稱。 java.awt、com.ambysoft.www.persistence.mapping成員函數 使用成員函數所執行內容的全英文說明,可能的話以作用中的動詞作為開頭,第一個字母小寫。 openFile()、addAccount()setter 成員函數 在所存取的欄位名稱前面冠上 "set"。 setFirstName()、setLastName()、setWarpSpeed()
關於記載的一個很好的簡略遵循規則是,如果您從未看過該程式碼,則問問您自己: 「您需要什麼資訊才能在合理的時間量內有效地瞭解該程式碼?」。
一般概念:
- 加上註解可以使程式碼更為明確。
- 如果您的程式不值得記載,那麼它可能也沒多大用處。
- 避免過度裝飾;也就是說,不要使用像是一幅廣告的註解。
- 註解要簡潔有力。
- 先記載,才寫程式碼。
- 註解為什麼要寫這段,而不只是說明在做什麼。
下表說明三種類型的 Java 註解,以及它們的建議用法。
註解類型 用法 範例 文件 在介面、類別、成員函數和欄位的宣告正前面使用文件註解來加以說明。javadoc 處理文件註解(請參閱以下)來建立類別的外部說明文件。 /**
客戶:客戶是我們向其販售服務和產品的任何人或組織。
@author S.W. Ambler
*/C style 使用 C-style 註解來註銷已不再適用、但是您想要保留以防您的使用者改變心意,或是因為您要在除錯時暫時關閉它的程式碼行。 /*
B.Gustafsson 在 1999 年 6 月 4 日註銷了這段程式碼,因為它被前一段程式碼所取代。如果它兩年後仍然不適用,請加以刪除。
. . . (程式碼)
*/單行 在成員函數內使用單行註解來說明商業邏輯、幾段程式碼,以及暫存變數的宣告。 // 對所有超過 $1000 的發票套用
// 5% 的折扣,這是由在 1995 年 2 月開始的 Sarek
// 超優惠活動
// 所定義。
下表彙總關於您撰寫的部分 Java 程式碼所要註解的內容。
項目 要註解的內容 引數/
參數參數的類型 它應用於什麼
任何限制或前置條件
範例
欄位/
欄位/內容它的說明 載明所有適用的不變量
範例
並行問題
有效範圍決定
類別 類別的用途 已知錯誤
類別的開發和維護歷程
載明適用的不變量
並行策略
編譯單元 類別中所定義的每一個類別或介面,其中包括簡要說明 檔名和(或)識別資訊
版權資訊
getter 成員函數 載明使用延遲初始的原因(如果適用的話) 介面 用途 它該用的及不該用的使用方式
區域變數 它的用法或用途 成員函數:說明文件 成員函數做什麼以及它那麼做的原因 什麼參數必須傳遞給成員函數
成員函數傳回的值
已知錯誤
成員函數擲出的任何異常狀況
有效範圍決定
成員函數變更物件的方式
包含任何程式碼變更的歷程
如何呼叫成員函數的範例(如果適用的話)
適用的前置條件和後置條件
成員函數:內部註解 控制結構 程式碼做什麼以及原因
區域變數
困難或複雜的程式碼
處理順序
套件 套件的基本原理 套件中的類別
有許多對於 Java 程式碼的可維護性和可增強性非常重要的使用慣例和標準。在 99.9% 的時間中,為人(您共事的開發人員)撰寫程式要比為機器撰寫程式來得重要。讓他人可以理解您的程式碼是最為重要的。
使用慣例目標 使用慣例 存取元成員函數 考慮對資料庫中的欄位使用延遲初始 使用存取元以取得及修改所有欄位
使用存取元以取得「常數」
如果是集合,請新增成員函數來插入及移除項目
盡可能使存取元為 protected,而不是 public
欄位 欄位應一律宣告為 private 請勿直接存取欄位,而應使用存取元成員函數
請勿使用最後 static 欄位(常數),而應使用存取元成員函數
請勿隱藏名稱
一律起始設定 static 欄位
類別 將 public 和 protected 介面減至最少 在開始撰寫類別的程式碼之前,先定義其 public 介面
按下列順序宣告類別的欄位和成員函數:
- 建構子
- finalize()
- public 成員函數
- protected 成員函數
- private 成員函數
- private 欄位
區域變數 請勿隱藏名稱 每行程式碼宣告一個區域變數
以行內註解說明區域變數
在使用區域變數之前才加以宣告
區域變數只用於一件事
成員函數 記載程式碼 準備程式碼
在控制結構前面一行及成員函數宣告前面兩行,使用空白字元
成員函數應在三十秒內就可以讓人瞭解
撰寫簡短、單一指令行
盡可能限制成員函數的有效範圍
指定作業的次序
參照碼 參考資訊 [AMB98] Ambler, S.W. (1998). Building Object Applications That Work: Your Step-By-Step Handbook for Developing Robust Systems with Object Technology. New York: SIGS Books/Cambridge University Press. [COA97] Coad, P. and Mayfield, M. (1997). Java Design: Building Better Apps & Applets. Upper Saddle River, NJ: Prentice Hall Inc. [DES97] DeSoto, A. (1997). Using the Beans Development Kit 1.0 February 1997: A Tutorial. Sun Microsystems. [GOS96] Gosling, J., Joy, B., Steele, G. (1996). The Java Language Specification. Reading, MA: Addison Wesley Longman Inc. [GRA97] Grand, M. (1997). Java Language Reference. Sebastopol, CA: O. Reilly & Associates, Inc. [KAN97] Kanerva, J. (1997). The Java FAQ. Reading, MA: Addison Wesley Longman Inc. [KOE97] Koenig, A. (1997). The Importance--and Hazards--of Performance Measurement. New York: SIGS Publications, Journal of Object-Oriented Programming, January, 1997, 9(8), pp. 58-60. [LAF97] Laffra, C. (1997). Advanced Java: Idioms, Pitfalls, Styles and Programming Tips. Upper Saddle River, NJ: Prentice Hall Inc. [LEA97] Lea, D. (1997). Concurrent Programming in Java: Design Principles and Patterns. Reading, MA: Addison Wesley Longman Inc. [MCO93] McConnell, S. (1993). Code Complete: A Practical Handbook of Software Construction. Redmond, WA: Microsoft Press. [MEY88] Meyer, B. (1988). Object-Oriented Software Construction. Upper Saddle River, NJ: Prentice Hall Inc. [NAG95] Nagler, J. (1995). Coding Style and Good Computing Practices. http://wizard.ucr.edu/~nagler/coding_style.html [SUN96] Sun Microsystems (1996). javadoc - The Java API Documentation Generator. Sun Microsystems. [SUN97] Sun Microsystems (1997). 100% Pure Java Cookbook for Java Developers: Rules and Hints for Maximizing the Portability of Java Programs. Sun Microsystems. [VIS96] Vision 2000 CCS Package and Application Team (1996). Coding Standards for C, C++, and Java. http://v2ma09.gsfc.nasa.gov/coding_standards.html
100% 純:實際上,來自 Sun 的「認可標章」,指出 Java Applet、應用程式或套件將在任何支援 Java VM 的平台上執行。
存取元:一個成員函數,其修改或傳回欄位的值。另稱為存取修飾元。請參閱 Getter 和 Setter。
分析型樣:一種建模型樣,其說明商業或網域問題的解決方案。
反型樣:一種解決一般問題的方法,其一段時間內經證明為錯誤或極缺乏效率。
引數:請參閱參數。
BDK:Beans Development Kit
建構:用(大)括號括住的零或多個陳述式的集合。
大括弧 { 和 } 字元(又分別稱為左大括弧和右大括弧),用來定義建構的開始和結束。
類別:一個定義或範本,可從中建立物件的實例。
類別測試:確定類別及其實例(物件)如定義般執行的動作。
CMVC:配置管理和版本控制
編譯單元:可在其中宣告類別和介面的程式碼檔案,分為磁碟上的實體檔案或是儲存在資料庫中的「虛擬」檔案。
元件:一個介面小組件,例如清單、按鈕或視窗。
常數 getter:一個傳回「常數」值的 getter 成員函數,必要的話會將依序它寫在程式中或加以計算。
建構子:一個成員函數,其會在物件被建立時執行任何必要的起始設定。
Containment:一個物件包含其他的物件,它與這些物件協同作業來執行其行為。這可藉由使用內部類別 (JDK 1.1+) 或聚集物件內的其他類別之實例 (JDK 1.0+) 來達成。
CPU:中央處理單元
C-style 註解:採用自 C/C++ 語言的一種 Java 註解格式 /* & */,其可用來建立多行註解。通常在測試期間用來「註銷」不需要的或不要的程式碼行。
設計型樣:一種建模型樣,其說明設計問題的解決方案。
解構子:一個 C++ 類別成員函數,用來從記憶體中移除不再需要的物件。由於 Java 管理其自己的記憶體,因此並不需要這種類型的成員函數。不過,Java 支援一種在概念上類似的成員函數,稱為 finalize()。
文件註解:一種 Java 註解格式 /** & */,可以由 javadoc 處理來提供類別檔案的外部文件。介面、類別、成員函數和欄位的主要文件應與文件註解一起撰寫。
欄位:一個說明類別或類別之實例的變數,它是一種文字資料類型或另一個物件。實例欄位說明物件(實例),static 欄位則說明類別。欄位又稱為欄位、欄位變數或內容。
finalize():一個成員函數,在記憶體回收期間,它會在物件從記憶體中移除之前自動被呼叫。這個成員函數的用途是執行任何必要的清理,例如關閉開啟的檔案。
記憶體回收:記憶體的自動化管理,其中會從記憶體中自動移除不再被參照的物件。
getter:一種存取元成員函數類型,其傳回欄位的值。getter 可用來回答常數的值,其將常數當作 static 欄位來實作經常比較是比較好的,因為這是較富彈性的方法。
HTML:超文字標記語言,這是建立網頁的業界標準格式。
縮排:請參閱段落化。
行內註解:使用一行註解來寫一行程式碼的註解,其中註解緊接在程式碼後面,與程式碼在同一行上。通常這會用單行註解,不過也可以利用 C-style 註解。
介面:共同簽章的定義(包括成員函數和欄位),這是實作介面的類別所必須支援的。介面藉由組合提升多型性。
I/O:輸入/輸出
不變量:一組確認集,這是關於在所有的「穩定」時間都必須為真的實例或類別,例如在物件或類別上呼叫成員函數之前及之後的期間。
Java:一種業界標準的物件導向開發語言,非常適合用於開發網際網路應用程式以及必須在各式各樣運算平台上運作的應用程式。
javadoc:包含在 JDK 中的公用程式,其處理 Java 原始程式碼檔案,並產生採 HTML 格式的外部文件,依據程式檔中的文件註解說明原始程式碼檔案的內容。
JDK:Java Development Kit
延遲初始:一種方法,其中欄位在它第一次被需要時,會在其相對應的 getter 成員函數中起始設定。當欄位不是普遍地被需要,並且它需要大量的記憶體來儲存或是必須從永久儲存體加以讀入時,會使用延遲初始。
區域變數:定義於一個建構範圍(經常是成員函數)內的變數。區域變數的範圍是它在其中定義的建構。
成員函數:一段執行碼,與類別或類別的實例相關聯。您可以將成員函數視為一個函數的物件導向等式。
成員函數簽章:請參閱簽章。
方法測試:確定成員函數(成員函數)如定義般執行的動作。
名稱隱藏:這指的是對欄位、變數或引數使用與更高範圍之欄位、變數或引數相同的(或至少是類似的)名稱之作法。濫用名稱隱藏最常見的是將區域變數命名為與實例欄位相同。您應避免使用名稱隱藏,因為它會使您的更難以瞭解並且易於出現錯誤。
超載:我們說成員函數被超載是指當它在同一個類別(或是子類別)中被定義多次時;唯一的差異是每一個定義的簽章。
置換:我們說成員函數被置換是指當它在子類別中被重新定義,同時其簽章與原始定義的簽章相同時。
套件:相關類別的集合。
段落化:一種在程式碼建構的範圍內將程式碼縮排一個單位(通常是一個水平欄標)的方法,以將它與該程式碼建構外的程式碼區隔。段落化有助於增加程式碼的可讀性。
參數:一個傳遞給成員函數的引數;參數可能是已定義的類型,如字串、int 或物件。
後置條件:一個內容或確認,在成員函數執行完成之後將會為真。
前置條件:成員函數在其下將會適當運作的限制。
內容:請參閱欄位。
Setter:一個存取元成員函數,其設定欄位的值。
簽章:參數類型(如果有的話)與它們必須傳遞給成員函數之順序的組合。這又稱為成員函數簽章。
單行註解:採用自 C/C++ 語言的一種 Java 註解格式 //,通常用於商業邏輯的內部成員函數說明文件。
標示:一個用於標示文件註解之指定區段的慣例;javadoc 會加以處理來產生有專業樣子的註解。標示的範例包括 @see 和 @author。
測試控制工具:用於測試程式碼的成員函數集合。
UML:統一建模語言,它是業界標準的建模表示法。
有效範圍:一種用來指出類別、成員函數或欄位之封裝層次的方法。可以使用 public、protected 和 private 等關鍵字來定義有效範圍。
空白字元:加入至程式碼以增加可讀性的空白行、空格和欄標。
小組件:請參閱元件。