
使用這些基本 REST API 最佳實踐構建出色的 API
這個例子中,remove后Element沒有使表達更清晰,因此是冗余的。如下API的表述就更好:
public?mutating?func?remove(member:?Element)?->?Element?
allViews.remove(cancelButton)?//?clearer
少數情況下,為了避免產生歧義,重復信息也是有必要的;但一般情況下,用一個詞語而不是一個類型來描述參數的作用會更好。有關詳細信息,請參閱下一項。
特別是當參數的類型是NSObject、Any、AnyObject或者是一個基本類型如Int或者String時,類型信息便不能充分表達參數的使用目的。如下面的例子,聲明是明確的,但在調用的時候是有些含糊不清的:
func?add(observer:?NSObject,?forkeyPath:?String)
grid.add(self,?for:?graphics)?//?vague
為了使表述更清晰,就需要在每一個弱類型參數前加一個名詞來描述它的作用:
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
當一個動態方法是用動詞來表述時,那么可以用對該動詞的過去時或者進行時形式(如”ed/ing”形式),來對該動態方法對應的靜態方法進行命名。如:
x.sort()和x.append(y)和靜態形式則為:x.sorted()和x.appending(y)。
通常情況下,一個動態方法都會有一個不同形式的靜態方法,并且該靜態方法的返回值與動態方法的返回值的類型相似或者相同。
對靜態方法進行命名時,優先使用動詞的過去時態(一般為“ed”形式)
///?Reverses?self
?in-place.
mutating?func?reverse()
///?Returns?a?reversed?copy?of?self
.
func?reversed()?->?Self
...
x.reverse()
let?y?=?x.reversed()
當動詞后接直接賓語時,用過去時態就不符合語法規則。此時,應用動名的進行時態(一般為“ing”形式)來對靜態方法進行命名。
///?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()
善用專業術語
專業術語:在特定領域或專業,有明確的,特殊含義的詞或短語
當常用詞不能準確表達其含義,或者使用常用詞會導致其含義模糊不清時,才能使用專業術語。因此,應該根據所能接受的含義嚴格使用API專業術語。
1.不要獨樹一幟:以為創造出了一種新的含義,實際上只會讓對這個術語了如指掌的專家感到奇怪甚至憤怒。
2.不要誤導新手:每個學習此新術語的人,都會到網上查詢術語的意思,以便進行理解,他們肯能看到的是其傳統意思。
使用的任何縮寫都應該是能在網上搜到其含義的
在連續數據結構進行命名時,雖然初學者可能更容易理解List,但使用Array比使用List要好。因為數組是現代計算的基礎,所以每個程序員都知道或者將會知道array代表什么。使用大部分程序員都熟知的術語,這樣也便于他們在網絡進行查詢和提問時能夠更快得到反饋。
在某些特地編程領域里面,例如數學運算,一個廣泛的先例術語:sin(x),顯然比下面解釋其含義的描述語句更好: ? ??
verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)
注意,在這種情況下,相比專業述語,先例詞更應該謹慎縮寫:雖然完整的單詞是“sine”,但幾十年來,編程人員一直用的都是“sin(x)”,而數學家們甚至使用了好幾個世紀。
1.沒有明顯的self關鍵字:
min(x, y, z)
2.函數為一個通用的泛型:
print(x)
3.函數的語法是已存在域符號的一部分:
sin(x)
例如,下面這種表述是正確的,因為方法在本質上都是處理相同的事情:
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?{?...?}
}
最后,不要使用返回值類型重載函數,這樣使用會導致Swift在類型推到的時候,產生歧義:
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??{?...?}
}
默認參數通過隱藏一些不相關信息來提高可讀性。例如:
let?order?=?lastName.compare(
??royalFamilyName,?options:?[],?range:?nil,?locale:?nil)
更簡單的寫法:
let?order?=?lastName.compare(royalFamilyName)
默認參數通常優于方法簇,因為默認參數的使用為學習API的人減少認知上的負擔。
extension?String?{
??///?*...description...*
??public?func?compare(
?????other:?String,?options:?CompareOptions?=?[],
?????range:?Range??=?nil,?locale:?Locale??=?nil
??)?->?Ordering
}
上面這種表述可能有點復雜,但是它比下面更簡潔明了:
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
}
方法簇中每一個成員,都需要用戶單獨編寫和理解。如果使用它們,用戶需要去了解每一個方法,有時候還要理清楚它們之間的關聯。例如,fooWithBar(nil)和foo()方法并不總是同義的—從一個幾乎完全相同的定義中去尋找它們之間細微的差別,是一個很繁瑣的過程。從很多優秀編程人員的經驗得知,應該使用一個帶有默認參數的單一方法,而不是方法簇。
換言之:
1.方法或者函數的第一個參數不需要參數標簽
2.方法或者函數的其他參數都必須要參數標簽
3.所有參數的初始化模塊也需要參數標簽
對應上面所說,如果每個參數都是像下面這種定義方式,那么所有參數也需要參數標簽:
identifier: Type
只有少數幾個例外情況:
1.對于無損“full-width”(即占用空間小的類型向占用空間大的類型轉換)類型轉換的構造器方法而言,第一個參數應該是待轉換的類型,并且這個參數不應該寫有外部參數標簽。
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”(即占用空間大的類型向占用空間小的類型轉換)類型轉換,推薦使用外部參數標簽來描述這個特定類型:
extension?UInt32?{
??init(_?value:?Int16)????????????//?widening,?so?no?label
??init(truncating?bits:?UInt64)
??init(saturating?value:?UInt64)
}
3.當所有的參數都是相同的類型,不能有效區分的時候,也不應該使用參數標簽。例如幾個常見的例子:min(number1,number2)、zip(sequence1,sequence2)。
4.當第一個參數不可用時,這時候需要一個明顯的參數標簽。
extension?Document?{
??func?close(completionHandler?completion:?((Bool)?->?Void)??=?nil)
}
doc1.close()
doc2.close(completionHandler:?app.quit)
正如你所看到的上面這個例子,方法可以都正確調用,不管參數是否被顯式的傳入。如果我們將參數的描述缺省,調用的時候可能有錯誤暗示:參數是語句的直接賓語:
extension?Document?{
??func?close(completion:?((Bool)?->?Void)??=?nil)
}
doc.close(app.quit)?//?Closing?the?quit?function?
? ? 如果你把參數的描述加到函數名上面,當函數默認被調用時,可能就會產生歧義:
extension?Document?{
??func?closeWithCompletionHandler(completion:?((Bool)?->?Void)??=?nil)
}
doc.closeWithCompletionHandler()???//?What?completion?handler?
在重載集合中,需要特別注意無約束多態類型(如Any、AnyObjecty以及無約束泛型參數)來避免歧義。
例如,我們看下面這個重載:
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)
}
? ? 這些方法構成了一個語義族,在第一個方法中參數類型有很大的不同,然而當Element為Any類型時,一個單個元素擁有和一系列元素相同的類型:
varvalues:?[Any]?=?[1,?"a"]
values.append([2,?3,?4])?//?[1,?"a",?[2,?3,?4]]?or?[1,?"a",?2,?3,?4]?
想要去除歧義,第二個重載方法名應該更加明確:
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的設計者更多關注于問題的本質。
我們使用的Markdown編輯器能夠便捷的給我們生成一個如下整齊的列表:
編寫一篇很棒的總結,相對來說比使用關鍵字,更為重要。
如果方法的簽名和方法頭注釋行,已經描述了參數或者返回值類型信息,那么你沒有必要去為它們編寫一個注釋性的文檔來描述它們的用法或作用,如下:
///?Append?newContent
?to?this?stream.
mutating?func?write(newContent:?String)
本文章轉載微信公眾號@Cocoa開發者社區