第三章 接口與API設(shè)計(jì)—第19條:使用清晰而協(xié)調(diào)的命名方式

類、方法及變量的命名是Objective-C編程的重要環(huán)節(jié)。新手通常會(huì)覺得這門語言很繁瑣,因?yàn)槠湔Z法結(jié)構(gòu)使得代碼讀起來和句子一樣。名稱中一般都帶有"in"、"for"、"with"等介詞,其他編程語言則很少使用這些它們認(rèn)為多余的字眼。以下面這段代碼為例:

NSString *text = @"The quick brown fox jumped over the lazy dog";
NSString *newText = [text stringByReplacingOccurrencesOfString:@"fox" withString:@"cat"];

此代碼用了比較啰嗦的方式來描述一個(gè)看上去如此簡(jiǎn)單的表達(dá)式。對(duì)于執(zhí)行替換操作的那個(gè)方法,其名字居然有48個(gè)字符長。不過這樣做的好處是,代碼讀起來像日常語言里的句子:
"將文本中出現(xiàn)的"fox"字符串替換為"cat",并返回替換后的新字符串。"(Take text and give me a new string by replacing the occurrences of the string 'fox' with the string 'cat')
這個(gè)句子準(zhǔn)確描述了開發(fā)者想做的事。在命名不像Objective-C這般復(fù)雜的語言中,類似的程序可能會(huì)寫成這樣:

string text = "The quick brown fox jumped over the lazy dog";
string newText = text.replace("fox", "cat");

這樣寫有個(gè)問題,就是text.replace()的兩個(gè)參數(shù)到底按何種順序解讀。是"fox"為"cat"所替換,還是"cat"為"fox"所替換呢?還有個(gè)疑問:replace函數(shù)是把所有出現(xiàn)的字符串都替換掉呢,還是只替換掉第一次找見的那個(gè)?其名稱沒能清除地表達(dá)出這兩個(gè)意思。而Objective-C的命名方式雖然長一點(diǎn),但是卻非常清晰。
讀者也會(huì)注意到,方法與變量名使用了"駝峰式大小寫命名法"(camel casing)--以小寫字母開頭,其后每個(gè)單詞首字母大寫。類名也用駝峰命名法,不過其首字母要大寫,而且前面通常還有兩三個(gè)前綴字母(參見第15條)。在編寫Objective-C代碼時(shí),大家一般都使用這種命名方式。如果你愿意,也可使用自己的風(fēng)格來命名,不過按照駝峰命名法寫出來的代碼更容易為其他Objective-C開發(fā)者所接受。

方法命名
你要是寫過C++或Java代碼的話,應(yīng)該會(huì)習(xí)慣那種較為簡(jiǎn)省的函數(shù)名,在那種命名方式下,若想知道每個(gè)參數(shù)的用途,就得查看函數(shù)原型。這會(huì)令代碼難于讀懂: 為了明白函數(shù)用法,你必須經(jīng)?;剡^頭來參照其原型。比方說,要寫一個(gè)表示矩形的類。用C++代碼可以這樣定義此類:

class Rectangle {
public:
    Rectangle(float width, float height);
    float getWidth();
    float getHeight();
private:
    float width;
    float height;
};

不熟悉C++也沒關(guān)系,你只要知道這個(gè)類包含名為width及height的兩個(gè)實(shí)例變量就好。若想創(chuàng)建該類的實(shí)例,只有一種辦法,就是以矩形尺寸為參數(shù),調(diào)用其"構(gòu)造器"(constructor),寬度與高度都有對(duì)應(yīng)的存取方法??梢杂孟旅孢@行代碼來創(chuàng)建該類的實(shí)例:

Rectangle *aRectangle = new Rectangle(5.0f, 10.0f);

回顧這行代碼時(shí),并不能一下子看出5.0f和10.0f表示什么。你可能覺得這兩個(gè)參數(shù)是矩形尺寸,可是到底寬度在先還是高度在先呢?要想確定這一點(diǎn),還得去查函數(shù)定義才行。
Objective-C語言就不會(huì)有這個(gè)問題了,因?yàn)槠浞椒梢云鸬酶L一些。熟悉C++的人可能會(huì)像下面這樣把Rectangle改寫為等價(jià)的Objective-C代碼:

#import <Foundation/Foundation.h>

@interface EOCRectangle : NSObject

@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;

- (id)initWithSize:(float)width :(float)height;

@end

寫這個(gè)類的人顯然知道,在Objective-C語言中,和C++構(gòu)造器等效的東西是init-系列方法,于是將其命名為"initWithSize:"。這看上去很奇怪,你也許覺得語法有誤,第二個(gè)冒號(hào)前面怎么沒有字呢?實(shí)際上語法完全沒錯(cuò),之所以會(huì)覺得寫錯(cuò)了,是因?yàn)樗透膶懬暗腃++構(gòu)造器有著想同的問題: 使用此類的開發(fā)者還是不清楚每個(gè)變量的含義:

EOCRectangle *aRectangle = 
    [[EOCRectangle alloc] initWithSize:5.0f :10.0f];

下面這種命名方式就要好很多:

- (id)initWithWidth:(float)width andHeight:(float)height;

這么寫是很長,然而這次絕對(duì)不會(huì)混淆每個(gè)變量的含義了:

EOCRectangle *aRectangle = 
    [[EOCRectangle alloc] initWithWidth:5.0f andHeight:10.0f];

雖說使用長名字可令代碼更為易讀,但是Objective-C新手還是難于習(xí)慣這種詳盡的方法命名風(fēng)格。不要吝于使用長方法名。把方法名起的稍微長一點(diǎn),可以保證其能準(zhǔn)確傳達(dá)出方法所執(zhí)行的任務(wù)。然而方法名也不能長得太過分了,應(yīng)盡量言簡(jiǎn)意賅。
以EOCRectangle類為例。好的方法名應(yīng)該像這樣:

- (EOCRectangle*)unionRectangle:(EOCRectangle*)rectangle
- (float)area

而下面這種命名方式咋不好:

- (EOCRectangle*)union:(EOCRectangle*)rectangle // Unclear
- (float)calculateTheArea // Too verbose

清晰的方法名從左至右讀起來好似一段文章。并不是說非得按照那些命名規(guī)則來給方法起名,不過這樣做可以令代碼變得更好維護(hù),而且也能使其他人更易讀懂。
NSString這類就展示了一套良好的命名習(xí)慣。下面列出幾個(gè)方法及其命名緣由:

  • +string
    工廠方法(factory method),用于創(chuàng)建新的空字符串。方法名清晰地描述了返回值的類型。
  • +stringWithString
    工廠方法,根據(jù)某字符串創(chuàng)建出與之內(nèi)容相同的新字符串。與創(chuàng)建空字符串所用的那個(gè)工廠方法一樣,方法名的第一個(gè)單詞也指明了返回類型。
  • +localizedStringWithFormat:
    工廠方法,根據(jù)特定格式創(chuàng)建出新的"本地化字符串"(localized string)。返回值類型是方法名的第二個(gè)單詞(string),因?yàn)槠淝懊孢€有個(gè)修飾語(localized)用來描述其邏輯含義。此方法的返回值依然是"字符串"(string),只不過是一種經(jīng)過本地化處理的特殊字符串。
  • -lowercaseString
    把字符串中的大寫字母都轉(zhuǎn)為小寫。該方法不會(huì)像修改接收此消息的字符串本身,而是要新創(chuàng)建一個(gè)字符串,此做法也符合方法名中應(yīng)該包含返回值類型這一規(guī)范,然而描述返回值類型的單詞(string)前面還有個(gè)定語(lowercase)。
  • -intValue
    將字符串解析為整數(shù)。由于返回值是int,所以方法名以這個(gè)詞開頭。通常情況下我們不會(huì)像這樣來簡(jiǎn)寫返回值的類型,比如string不簡(jiǎn)寫為str,然而由于"Integer"(整數(shù))一詞的簡(jiǎn)稱"int"本身就是返回值的類型名稱,所以此處這么做是合理的。為了把方法名湊足兩個(gè)詞,所以加了后綴"Value"。只有一個(gè)詞的名字通常用來表示屬性。由于int不是字符串對(duì)象的屬性,所以要加Value以限定其含義。
  • -length
    獲取字符串長度(也就是其字符個(gè)數(shù))。這個(gè)方法只有一個(gè)詞,因?yàn)閷?shí)際上length也是字符串的一個(gè)屬性。這個(gè)屬性可能不是由實(shí)例變量來實(shí)現(xiàn)的,然而即便如此,它也依然是字符串中的屬性。此方法若是命名為stringLength就不好了。string一詞多余,因?yàn)樵摲椒ǖ慕邮照呖隙ㄊ莻€(gè)字符串。
  • -lengthOfBytesUsingEncoding
    若字符串是以給定的編碼格式(ASCII、UTF8、UTF6
    等)來編碼的,則返回其字節(jié)數(shù)組的長度。此方法與length相似,所以其命名原因也和剛才說的一樣。此外,該方法還需一個(gè)參數(shù)。該參數(shù)緊跟著方法名中描述其類型的那個(gè)名詞(encoding)。
  • -getCharacters:range:
    獲取字符串中給定范圍內(nèi)的字符。其他語言里的獲取方法也許會(huì)以get開頭,但Objective-C中一般不這么做,然而此處例外,該方法用get作其前綴。原因在于,調(diào)用此方法時(shí),要在其首個(gè)參數(shù)中傳入數(shù)組,而該方法所獲取的字符正是要放到這個(gè)數(shù)組里面。此方法的完整簽名為:
  • -(void)getCharacters:(unichar *)buffer range:(NSRange)aRange
    首個(gè)參數(shù)buffer應(yīng)該指向一個(gè)足夠大的數(shù)組,以便容納所請(qǐng)求范圍內(nèi)的那些字符。此方法要通過其參數(shù)來返回(這種參數(shù)通常稱為"輸出參數(shù)'(out-parameter)), 而不通過返回值來返回,從內(nèi)存管理的角度看,這樣做更好。所有內(nèi)存管理事宜均由方法調(diào)用者處理,而不是先在此方法中創(chuàng)建一個(gè)數(shù)組,然后再又調(diào)用者釋放。第二個(gè)參數(shù)前有個(gè)描述其類型的名詞(range),如果還有其他參數(shù),也應(yīng)該在方法名中提到其類型。有時(shí)參數(shù)名前面還會(huì)加介詞,例如,此方法可以命名為"getCharacters:inRange:"。當(dāng)需要特別強(qiáng)調(diào)眾參數(shù)中的某一個(gè)時(shí),通常會(huì)這樣命名。
  • -hasPrefix:
    判斷本字符串是否以另一個(gè)字符串開頭。由于返回值是Boolean類型,所以為了讀起來像個(gè)句子,這種方法的名稱中通常都包括has("是否有")一詞。例如:
[@"Effective Objective-C" hasPrefix:@"Effective"] == YES

要是把方法名直接寫成"prefix",讀起來就不這么順了。反之,若將其叫成"isPrefixWith:"則聽上去冗長而別扭。

  • -isEqualToString:
    判斷兩字符串是否相等。其返回值和"hasPrefix:"一樣,都是Boolean型,為了便于述說,方法名用is開頭。還有個(gè)地方也會(huì)用到is這個(gè)前綴詞,那就是Boolean型的屬性。比方說,有個(gè)屬性叫做enabled,則其兩個(gè)存取方法應(yīng)該分別起名為"setEnabled:"與isEnabled。

給方法命名時(shí)的注意事項(xiàng)可總結(jié)成下面幾條規(guī)則。

  • 如果方法的返回值是新創(chuàng)建的,那么方法名的首個(gè)詞應(yīng)是返回值的類型,除非前面還有修飾語,例如localizedString。屬性的存取方法不遵循這種命名方式,因?yàn)橐话阏J(rèn)為這些方法不會(huì)創(chuàng)建新對(duì)象,即便有時(shí)返回內(nèi)部對(duì)象的一份拷貝,我們也認(rèn)為那相當(dāng)于原有的對(duì)象。這些存取方法應(yīng)該按照其所對(duì)應(yīng)的屬性來命名。
  • 應(yīng)該把表示參數(shù)類型的名詞放在參數(shù)前面。
  • 如果方法要在當(dāng)前對(duì)象上執(zhí)行操作,那么就應(yīng)該包含動(dòng)詞;若執(zhí)行操作時(shí)還需要參數(shù),則應(yīng)該在動(dòng)詞后面加上一個(gè)或多個(gè)動(dòng)詞。
  • 不要使用str這種簡(jiǎn)稱,應(yīng)該用string這樣的全稱。
  • Boolean屬性應(yīng)加is前綴。如果某方法返回非屬性的Boolean值,那么應(yīng)該根據(jù)其功能,選用has或is當(dāng)前綴。
  • 將get這個(gè)前綴留給那些借由"輸出參數(shù)"來保存返回值的方法,比如說,把返回值填充到"C語言式數(shù)組"(C-style array)里的那種方法就可以使用這個(gè)詞做前綴。

類與協(xié)議的命名
應(yīng)該為類與協(xié)議的名稱加上前綴,以避免命名空間沖突(參見第15條),而且應(yīng)該像給方法起名時(shí)那樣把詞句組織好,使其從左至右讀起來較為通順。例如,在NSArray的子類中,有一個(gè)用于表示可變數(shù)組的類,叫做NSMutableArray,mutable這個(gè)詞放在array前面,用以表明這是一種特殊的array(數(shù)組)。
下面以iOS的UI庫UIKit為例,演示類與協(xié)議的命名慣例:

  • UIView(類)
    所有"視圖"(View)均繼承于此類。視圖是構(gòu)造用戶界面的基本單元,它們負(fù)責(zé)繪制按鈕、文本框(text field)、表格等控件。這個(gè)類的名字無須解釋即可自明題意(self-explanatory)、開頭的兩個(gè)字母"UI"是UIKit框架的通用前綴。
  • UIViewController(類)
    視圖類(UIView)負(fù)責(zé)繪制視圖,然而卻不負(fù)責(zé)指定視圖里面應(yīng)該顯示的內(nèi)容。這項(xiàng)工作由本類,也就是"視圖控制器"(view controller)來完成。其名稱從左至右讀起來很順。
  • UITableView(類)
    這是一種特殊類型的視圖,可以顯示表格中的一系列條目。所以,它在超類(UIView)名稱中的View一詞前面加了Table這個(gè)修飾詞,用以和其他類型的視圖相區(qū)隔。在超類名稱前加修飾語是一種常用的命名慣例。本類也可以叫做UITableView,不過這個(gè)名字無法完整傳達(dá)出"視圖"這個(gè)概念。開發(fā)者必須查看接口聲明方能確定這一點(diǎn)。比方說,想創(chuàng)建一個(gè)專門用來顯示圖像的表格視圖,那么就可以將這個(gè)繼承自UITableView的子類命名為EOCImageTableView。不過這時(shí)要加上自己的前綴EOC,而不是沿用超類的前綴UI(UIKit框架中的類以UI為前綴)。這么做的原因在于,你不應(yīng)該把自己的類放到其他框架的命名空間里面,那些框架以后也許會(huì)新建同名的類。
  • UITableViewController(類)
    正如UITableView是一種特殊的view(視圖)一樣,UITableViewController也是一種特殊的view controller(視圖控制器),它專門用于控制表格視圖。因此,其命名方式與UITableView類似。
  • UITableViewDelegate(協(xié)議)
    此協(xié)議定義了表格視圖與其他對(duì)象之間的通信接口,命名時(shí),把定義"委托接口"(delegate interface)的那個(gè)類名(UITableView)放在前面,后面加上Delegate一詞,這樣讀起來順口。(第23條詳述了"委托模式"(Delegate pattern)。)

說了這么多,其中最重要的一點(diǎn)就是,命名方式應(yīng)該協(xié)調(diào)一致。而且,如果要從其他框架中繼承子類,那么務(wù)必遵循其命名慣例。比方說,要從UIView類中繼承自定義的類,那么類名末尾的詞必須是View。同理,若要?jiǎng)?chuàng)建自定義的委托協(xié)議,則其名稱中應(yīng)該包含委托發(fā)起方的名稱,后面再跟上Delegate一詞。如果能堅(jiān)持這種命名習(xí)慣,那么在稍后回顧自己的代碼或他人使用你所寫的代碼時(shí),很容易就能理解其含義。

要點(diǎn)

  • 起名時(shí)應(yīng)遵從標(biāo)準(zhǔn)的Objective-C命名規(guī)范,這樣創(chuàng)建出來的接口更容易為開發(fā)者所理解。
  • 方法名要言簡(jiǎn)意賅,從左至右讀起來要像個(gè)日常用語中的句子才好。
  • 方法名里不要使用縮略后的類型名稱。
  • 給方法起名時(shí)的第一要?jiǎng)?wù)就是確保其風(fēng)格與你自己的代碼或所要集成的框架相符。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,192評(píng)論 3 119
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,631評(píng)論 1 32
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,215評(píng)論 8 265
  • 一個(gè)很突然的想法,總覺得有很多故事想說,關(guān)于我的,關(guān)于別人的。但是又不想在朋友圈扣扣空間那樣的地方去寫了,感覺……...
    小小小葉紫閱讀 117評(píng)論 0 0
  • 現(xiàn)在已經(jīng)第七集了寫劇評(píng)也不知道會(huì)不會(huì)太遲了【攤手.jpg】但是這集卻是到現(xiàn)在為止我覺得最有味道的一集,同時(shí)也是最...
    BuffaloLau閱讀 1,491評(píng)論 0 0

友情鏈接更多精彩內(nèi)容