鍵.png)
使用這些基本 REST API 最佳實踐構(gòu)建出色的 API
這個例子中,remove后Element沒有使表達更清晰,因此是冗余的。如下API的表述就更好:
public?mutating?func?remove(member:?Element)?->?Element?
allViews.remove(cancelButton)?//?clearer
少數(shù)情況下,為了避免產(chǎn)生歧義,重復(fù)信息也是有必要的;但一般情況下,用一個詞語而不是一個類型來描述參數(shù)的作用會更好。有關(guān)詳細(xì)信息,請參閱下一項。
特別是當(dāng)參數(shù)的類型是NSObject、Any、AnyObject或者是一個基本類型如Int或者String時,類型信息便不能充分表達參數(shù)的使用目的。如下面的例子,聲明是明確的,但在調(diào)用的時候是有些含糊不清的:
func?add(observer:?NSObject,?forkeyPath:?String)
grid.add(self,?for:?graphics)?//?vague
為了使表述更清晰,就需要在每一個弱類型參數(shù)前加一個名詞來描述它的作用:
func?addObserver(_?observer:?NSObject,?forKeyPath?path:?String)
rid.addObserver(self,?forKeyPath:?graphics)?//?clear
x.reverse(), x.sort(), x.append(y).
x.distanceTo(y), i.successor().
let?firstAndLast?=?fullName.split()?//?acceptable
當(dāng)一個動態(tài)方法是用動詞來表述時,那么可以用對該動詞的過去時或者進行時形式(如”ed/ing”形式),來對該動態(tài)方法對應(yīng)的靜態(tài)方法進行命名。如:
x.sort()和x.append(y)和靜態(tài)形式則為:x.sorted()和x.appending(y)。
通常情況下,一個動態(tài)方法都會有一個不同形式的靜態(tài)方法,并且該靜態(tài)方法的返回值與動態(tài)方法的返回值的類型相似或者相同。
對靜態(tài)方法進行命名時,優(yōu)先使用動詞的過去時態(tài)(一般為“ed”形式)
///?Reverses?self
?in-place.
mutating?func?reverse()
///?Returns?a?reversed?copy?of?self
.
func?reversed()?->?Self
...
x.reverse()
let?y?=?x.reversed()
當(dāng)動詞后接直接賓語時,用過去時態(tài)就不符合語法規(guī)則。此時,應(yīng)用動名的進行時態(tài)(一般為“ing”形式)來對靜態(tài)方法進行命名。
///?Strips?all?the?newlines?from?\self\
mutating?func?stripNewlines()
///?Returns?a?copy?of?\self\
?with?all?the?newlines?stripped.
func?strippingNewlines()?->?String
...
s.stripNewlines()
let?oneLine?=?t.strippingNewlines()
善用專業(yè)術(shù)語
專業(yè)術(shù)語:在特定領(lǐng)域或?qū)I(yè),有明確的,特殊含義的詞或短語
當(dāng)常用詞不能準(zhǔn)確表達其含義,或者使用常用詞會導(dǎo)致其含義模糊不清時,才能使用專業(yè)術(shù)語。因此,應(yīng)該根據(jù)所能接受的含義嚴(yán)格使用API專業(yè)術(shù)語。
1.不要獨樹一幟:以為創(chuàng)造出了一種新的含義,實際上只會讓對這個術(shù)語了如指掌的專家感到奇怪甚至憤怒。
2.不要誤導(dǎo)新手:每個學(xué)習(xí)此新術(shù)語的人,都會到網(wǎng)上查詢術(shù)語的意思,以便進行理解,他們肯能看到的是其傳統(tǒng)意思。
使用的任何縮寫都應(yīng)該是能在網(wǎng)上搜到其含義的
在連續(xù)數(shù)據(jù)結(jié)構(gòu)進行命名時,雖然初學(xué)者可能更容易理解List,但使用Array比使用List要好。因為數(shù)組是現(xiàn)代計算的基礎(chǔ),所以每個程序員都知道或者將會知道array代表什么。使用大部分程序員都熟知的術(shù)語,這樣也便于他們在網(wǎng)絡(luò)進行查詢和提問時能夠更快得到反饋。
在某些特地編程領(lǐng)域里面,例如數(shù)學(xué)運算,一個廣泛的先例術(shù)語:sin(x),顯然比下面解釋其含義的描述語句更好: ? ??
verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)
注意,在這種情況下,相比專業(yè)述語,先例詞更應(yīng)該謹(jǐn)慎縮寫:雖然完整的單詞是“sine”,但幾十年來,編程人員一直用的都是“sin(x)”,而數(shù)學(xué)家們甚至使用了好幾個世紀(jì)。
1.沒有明顯的self關(guān)鍵字:
min(x, y, z)
2.函數(shù)為一個通用的泛型:
print(x)
3.函數(shù)的語法是已存在域符號的一部分:
sin(x)
例如,下面這種表述是正確的,因為方法在本質(zhì)上都是處理相同的事情:
extension?Shape?{
??///?Returns?true
?iff?other
?is?within?the?area?of?self
.
??func?contains(other:?Point)?->?Bool?{?...?}
??
??///?Returns?true
?iff?other
?is?entirely?within?the?area?of?self
.
??func?contains(other:?Shape)?->?Bool?{?...?}
??
??///?Returns?true
?iff?other
?is?within?the?area?of?self
.
??func?contains(other:?LineSegment)?->?Bool?{?...?}
}
這是因為幾何類型和集合類型是處于不同的域里面,在同一個程序里面,這樣表述也是沒有問題的:
extension?Collection?where?Element?:?Equatable?{
??///?Returns?true
?iff?self
?contains?an?element?equal?to
??///?sought
.
??func?contains(sought:?Element)?->?Bool?{?...?}
}
但是,下面的這些index方法具有不同的語義,就必須要使用不同的名稱來命名:
extension?Database?{
??///?Rebuilds?the?database's?search?index
??func?index()?{?...?}
??
??///?Returns?the?n
th?row?in?the?given?table.
??func?index(n:?Int,?inTable:?TableID)?->?TableRow?{?...?}
}
最后,不要使用返回值類型重載函數(shù),這樣使用會導(dǎo)致Swift在類型推到的時候,產(chǎn)生歧義:
extension?Box?{
??///?Returns?the?Int
?stored?in?self
,?if?any,?and
??///?nil
?otherwise.
??func?value()?->?Int??{?...?}
??
??///?Returns?the?String
?stored?in?self
,?if?any,?and
??///?nil
?otherwise.
??func?value()?->?String??{?...?}
}
默認(rèn)參數(shù)通過隱藏一些不相關(guān)信息來提高可讀性。例如:
let?order?=?lastName.compare(
??royalFamilyName,?options:?[],?range:?nil,?locale:?nil)
更簡單的寫法:
let?order?=?lastName.compare(royalFamilyName)
默認(rèn)參數(shù)通常優(yōu)于方法簇,因為默認(rèn)參數(shù)的使用為學(xué)習(xí)API的人減少認(rèn)知上的負(fù)擔(dān)。
extension?String?{
??///?*...description...*
??public?func?compare(
?????other:?String,?options:?CompareOptions?=?[],
?????range:?Range??=?nil,?locale:?Locale??=?nil
??)?->?Ordering
}
上面這種表述可能有點復(fù)雜,但是它比下面更簡潔明了:
extension?String?{
??///?*...description?1...*
??public?func?compare(other:?String)?->?Ordering
??///?*...description?2...*
??public?func?compare(other:?String,?options:?CompareOptions)?->?Ordering
??///?*...description?3...*
??public?func?compare(
?????other:?String,?options:?CompareOptions,?range:?Range)?->?Ordering
??///?*...description?4...*
??public?func?compare(
?????other:?String,?options:?StringCompareOptions,
?????range:?Range,?locale:?Locale)?->?Ordering
}
方法簇中每一個成員,都需要用戶單獨編寫和理解。如果使用它們,用戶需要去了解每一個方法,有時候還要理清楚它們之間的關(guān)聯(lián)。例如,fooWithBar(nil)和foo()方法并不總是同義的—從一個幾乎完全相同的定義中去尋找它們之間細(xì)微的差別,是一個很繁瑣的過程。從很多優(yōu)秀編程人員的經(jīng)驗得知,應(yīng)該使用一個帶有默認(rèn)參數(shù)的單一方法,而不是方法簇。
換言之:
1.方法或者函數(shù)的第一個參數(shù)不需要參數(shù)標(biāo)簽
2.方法或者函數(shù)的其他參數(shù)都必須要參數(shù)標(biāo)簽
3.所有參數(shù)的初始化模塊也需要參數(shù)標(biāo)簽
對應(yīng)上面所說,如果每個參數(shù)都是像下面這種定義方式,那么所有參數(shù)也需要參數(shù)標(biāo)簽:
identifier: Type
只有少數(shù)幾個例外情況:
1.對于無損“full-width”(即占用空間小的類型向占用空間大的類型轉(zhuǎn)換)類型轉(zhuǎn)換的構(gòu)造器方法而言,第一個參數(shù)應(yīng)該是待轉(zhuǎn)換的類型,并且這個參數(shù)不應(yīng)該寫有外部參數(shù)標(biāo)簽。
extension?String?{
??//?Convert?x
?into?its?textual?representation?in?the?given?radix
??init(_?x:?BigInt,?radix:?Int?=?10)?//?Note?the?initial?separate?underscore
}
text?=?"The?value?is:?"
text?+=?String(veryLargeNumber)
text?+=?"?and?in?hexadecimal,?it's"
text?+=?String(veryLargeNumber,?radix:?16)
對于有損“narrowing”(即占用空間大的類型向占用空間小的類型轉(zhuǎn)換)類型轉(zhuǎn)換,推薦使用外部參數(shù)標(biāo)簽來描述這個特定類型:
extension?UInt32?{
??init(_?value:?Int16)????????????//?widening,?so?no?label
??init(truncating?bits:?UInt64)
??init(saturating?value:?UInt64)
}
3.當(dāng)所有的參數(shù)都是相同的類型,不能有效區(qū)分的時候,也不應(yīng)該使用參數(shù)標(biāo)簽。例如幾個常見的例子:min(number1,number2)、zip(sequence1,sequence2)。
4.當(dāng)?shù)谝粋€參數(shù)不可用時,這時候需要一個明顯的參數(shù)標(biāo)簽。
extension?Document?{
??func?close(completionHandler?completion:?((Bool)?->?Void)??=?nil)
}
doc1.close()
doc2.close(completionHandler:?app.quit)
正如你所看到的上面這個例子,方法可以都正確調(diào)用,不管參數(shù)是否被顯式的傳入。如果我們將參數(shù)的描述缺省,調(diào)用的時候可能有錯誤暗示:參數(shù)是語句的直接賓語:
extension?Document?{
??func?close(completion:?((Bool)?->?Void)??=?nil)
}
doc.close(app.quit)?//?Closing?the?quit?function?
? ? 如果你把參數(shù)的描述加到函數(shù)名上面,當(dāng)函數(shù)默認(rèn)被調(diào)用時,可能就會產(chǎn)生歧義:
extension?Document?{
??func?closeWithCompletionHandler(completion:?((Bool)?->?Void)??=?nil)
}
doc.closeWithCompletionHandler()???//?What?completion?handler?
在重載集合中,需要特別注意無約束多態(tài)類型(如Any、AnyObjecty以及無約束泛型參數(shù))來避免歧義。
例如,我們看下面這個重載:
struct?Array?{
??///?Inserts?newElement
?at?self.endIndex
.
??public?mutating?func?append(newElement:?Element)
??
??///?Inserts?the?contents?of?newElements
,?in?order,?at
??///?self.endIndex
.
??public?mutating?func?append<
????S?:?SequenceType?where?S.Generator.Element?==?Element
??>(newElements:?S)
}
? ? 這些方法構(gòu)成了一個語義族,在第一個方法中參數(shù)類型有很大的不同,然而當(dāng)Element為Any類型時,一個單個元素?fù)碛泻鸵幌盗性叵嗤念愋停?/p>
varvalues:?[Any]?=?[1,?"a"]
values.append([2,?3,?4])?//?[1,?"a",?[2,?3,?4]]?or?[1,?"a",?2,?3,?4]?
想要去除歧義,第二個重載方法名應(yīng)該更加明確:
struct?Array?{
??///?Inserts?newElement
?at?self.endIndex
.
??public?mutating?func?append(newElement:?Element)
??
??///?Inserts?the?contents?of?newElements
,?in?order,?at
??///?self.endIndex
.
??public?mutating?func?appendContentsOf<
????S?:?SequenceType?where?S.Generator.Element?==?Element
??>(newElements:?S)
}
注意,這個新方法的名稱能更好的匹配文檔的注釋。在這個例子中,編寫文檔的注釋實際上可以讓編寫API的設(shè)計者更多關(guān)注于問題的本質(zhì)。
我們使用的Markdown編輯器能夠便捷的給我們生成一個如下整齊的列表:
編寫一篇很棒的總結(jié),相對來說比使用關(guān)鍵字,更為重要。
如果方法的簽名和方法頭注釋行,已經(jīng)描述了參數(shù)或者返回值類型信息,那么你沒有必要去為它們編寫一個注釋性的文檔來描述它們的用法或作用,如下:
///?Append?newContent
?to?this?stream.
mutating?func?write(newContent:?String)
本文章轉(zhuǎn)載微信公眾號@Cocoa開發(fā)者社區(qū)