本文翻譯自蘋果官方文檔:Swift API Design Guidelines肴颊,如有錯(cuò)漏渣磷,歡迎指出。
基本準(zhǔn)則
在調(diào)用處表意足夠明確是你最重要的目的竟宋。像方法和屬性這樣的實(shí)體(Entities)只聲明一次,但卻會(huì)被重復(fù)調(diào)用丘侠,所以你需要設(shè)計(jì)好你的 API 讓它們可以被明確和簡(jiǎn)潔的調(diào)用。當(dāng)我們?cè)u(píng)價(jià)某個(gè)設(shè)計(jì)的時(shí)候打肝,往往需要查看它的使用場(chǎng)景以確保它在實(shí)際環(huán)境中足夠明確挪捕,而不僅僅是看一眼它的聲明。
** 明確比簡(jiǎn)潔更重要断医。**雖然 Swift 代碼可以寫得非常簡(jiǎn)潔奏纪,但是通過減少字符數(shù)使得代碼盡可能簡(jiǎn)短卻從不是我們的目標(biāo)。在 Swift 中序调,簡(jiǎn)潔只是強(qiáng)類型系統(tǒng)和其它可以減少樣板代碼的特性所帶來的一個(gè)副作用(side-effect)。
為每個(gè)聲明編寫文檔注釋荣挨。寫文檔時(shí)的感悟會(huì)對(duì)你的設(shè)計(jì)產(chǎn)生重大影響朴摊,所以不要擱置它。
命名
促使能被明確調(diào)用
-
包含所有需要的單詞口锭,以避免人們?cè)陂喿x調(diào)用處的代碼時(shí)感到困惑介杆。
譬如,有一個(gè)方法春哨,要在集合(collection)中移除指定位置的元素。
推薦:
extension List {
public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)
如果我們刪掉方法簽名中的at
椰拒,那就給人一種該方法是搜索并刪除集合中等于x
的元素的感覺,而不是用x
來指示元素在集合中的位置褒脯,并把該位置的元素刪除缆毁。
不推薦:
employees.remove(x) // unclear: are we removing x?
-
刪除不需要的單詞。名字中的每個(gè)單詞都應(yīng)該在調(diào)用處傳達(dá)出重點(diǎn)信息脊框。
更多的單詞或許能澄清意圖和消除歧義浇雹,但是那些讀者已經(jīng)知道的冗余信息都可以刪掉,尤其是那些僅僅重復(fù)了類型信息的單詞箫爷。
不推薦:
public mutating func removeElement(member: Element) -> Element?
allViews.removeElement(cancelButton)
上述情況下聂儒,Element
在調(diào)用處沒有提供任何要點(diǎn)信息,如下 API 會(huì)更好窜护。
推薦:
public mutating func remove(member: Element) -> Element?
allViews.remove(cancelButton) // clearer
個(gè)別情況下非春,重復(fù)類型信息對(duì)于消除歧義是必要的,但一般來說护侮,用一個(gè)表明參數(shù)角色(role)而不是類型的詞储耐,會(huì)更好一些。詳情請(qǐng)參看下一條什湘。
基于變量、參數(shù)得哆、關(guān)聯(lián)類型的角色來對(duì)它們進(jìn)行命名哟旗,而不是基于它們的類型栋操。
不推薦:
var string = "Hello"
protocol ViewController {
associatedtype ViewType : View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
像這樣重申一遍類型名并不能最大程度提升明確性和表現(xiàn)力乐设。相反,我們應(yīng)該盡量選用那些表明實(shí)體角色的名字蠕啄。
推薦:
var greeting = "Hello"
protocol ViewController {
associatedtype ContentView : View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}
如果某個(gè)關(guān)聯(lián)類型和它的協(xié)議聯(lián)系非常緊密戈锻,以至于它的協(xié)議名就是它的角色名,那就給關(guān)聯(lián)類型的名字加上Type
避免沖突:
protocol Sequence {
associatedtype IteratorType : Iterator
}
-
為弱類型信息的參數(shù)添加補(bǔ)充信息以表明參數(shù)的角色
當(dāng)參數(shù)類型是NSObject哈街、Any拒迅、 AnyObject
或者像Int、String
這樣的基本類型的時(shí)候作箍,調(diào)用處的類型信息和上下文環(huán)境可能不能完全表明函數(shù)的意圖前硫。如下這個(gè)例子,它的聲明可能是明確的屹电,但在調(diào)用的地方就顯得意圖不明了。
不推薦:
func add(observer: NSObject, for keyPath: String)
grid.add(self, for: graphics) // vague
為了恢復(fù)明確性牧愁,在每個(gè)弱類型參數(shù)前加一個(gè)名詞用來描述它的角色外莲。
推薦:
func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics) // clear
為能被流暢調(diào)用而努力
-
盡量使方法或函數(shù)名在調(diào)用的時(shí)候符合英語語法規(guī)范。
推薦:
x.insert(y, at: z) “x, insert y at z”
x.subViews(havingColor: y) “x's subviews having color y”
x.capitalizingNouns() “x, capitalizing nouns”
不推薦:
x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()
為了流暢性办龄,可以把調(diào)用時(shí)非重點(diǎn)的參數(shù)放到第一或者第二個(gè)參數(shù)之后淋昭。
AudioUnit.instantiate(
with: description,
options: [.inProcess], completionHandler: stopProgressBar)
工廠方法的命名以
make
開頭,譬如:x.makeIterator()
英融。構(gòu)造方法和工廠方法在調(diào)用時(shí)應(yīng)該從一個(gè)不包含 first argument(譯者注:翻譯成第一個(gè)參數(shù)在這里好像不對(duì)頭,索性就不翻了驶悟,大家根據(jù)下面的例子應(yīng)該可以理解它的意思)的短語開始痕鳍,譬如:
x.makeWidget(cogCount: 47)
。
舉個(gè)例子笼呆,如下這些調(diào)用的短語都不包含 first argument。
推薦:
let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)
而下面這段代碼的 API 作者企圖用 first argument 創(chuàng)建符合英語語法的順暢 API:
不推薦:
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
事實(shí)上汗茄,本指南包含了參數(shù)標(biāo)簽(argument labels铭若,譯者注:應(yīng)該和外部參數(shù)名一個(gè)意思吧)這樣的的主題,意味著第一個(gè)參數(shù)都應(yīng)該包含一個(gè)標(biāo)簽瞳腌,除非該方法完全只是用來做類型轉(zhuǎn)換的环鲤。
推薦:
let rgbForeground = RGBColor(cmykForeground)
-
基于函數(shù)和方法的副作用對(duì)它們命名
- 沒有副作用的方法讀起來應(yīng)該是一個(gè)名詞詞組憎兽,譬如:
x.distance(to: y)
,i.successor()
。 - 有副作用的方法讀起來應(yīng)該是一個(gè)命令式的動(dòng)詞短語西剥,譬如:
print(x)
,x.sort()
,x.append(y)
亿汞。 - 使用 “ed/ing” 規(guī)則對(duì)一個(gè)可變方法(mutating method)的不可變版本命名。
一般來說疗我,可變方法都會(huì)有一個(gè)對(duì)應(yīng)的不可變版本,該方法會(huì)返回一個(gè)和接受值相同或者相似類型的值旧找。- 傾向于用過去分詞對(duì)不可變版本命名(一般是加 “ed”):
- 沒有副作用的方法讀起來應(yīng)該是一個(gè)名詞詞組憎兽,譬如:
/// Reverses `self` in-place.
mutating func reverse()
/// Returns a reversed copy of `self`.
func reversed() -> Self
...
x.reverse()
let y = x.reversed()
- 當(dāng)動(dòng)詞后面跟了個(gè)名詞的時(shí)候麦牺,用過去分詞就不符合語法規(guī)范了鞭缭,這時(shí)候可以用動(dòng)詞的現(xiàn)在分詞對(duì)不可變版本命名魏颓,也就是加上 “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()
調(diào)用返回值為布爾型的不可變方法和屬性的時(shí)候讀起來應(yīng)該是調(diào)用者的斷言(assertions)甸饱,譬如:
x.isEmpty
,line1.intersects(line2)
。用來描述是什么的協(xié)議讀起來應(yīng)該是個(gè)名詞柜候。**(譬如:
Collection
)。用來描述能做什么的協(xié)議應(yīng)該加上 able鹦肿、ible 或者 ing 進(jìn)行命名**(譬如:
Equatable
,ProgressReporting
)辅柴。其它類型、屬性涣旨、變量和約束的命名都應(yīng)該用名詞股冗。