本文翻譯自蘋果官方文檔:Swift API Design Guidelines,如有錯(cuò)漏,歡迎指出。
基本準(zhǔn)則
在調(diào)用處表意足夠明確是你最重要的目的。像方法和屬性這樣的實(shí)體(Entities)只聲明一次,但卻會(huì)被重復(fù)調(diào)用,所以你需要設(shè)計(jì)好你的 API 讓它們可以被明確和簡潔的調(diào)用。當(dāng)我們評價(jià)某個(gè)設(shè)計(jì)的時(shí)候,往往需要查看它的使用場景以確保它在實(shí)際環(huán)境中足夠明確,而不僅僅是看一眼它的聲明。
** 明確比簡潔更重要。**雖然 Swift 代碼可以寫得非常簡潔,但是通過減少字符數(shù)使得代碼盡可能簡短卻從不是我們的目標(biāo)。在 Swift 中,簡潔只是強(qiáng)類型系統(tǒng)和其它可以減少樣板代碼的特性所帶來的一個(gè)副作用(side-effect)。
為每個(gè)聲明編寫文檔注釋。寫文檔時(shí)的感悟會(huì)對你的設(shè)計(jì)產(chǎn)生重大影響,所以不要擱置它。
命名
促使能被明確調(diào)用
-
包含所有需要的單詞,以避免人們在閱讀調(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ù)類型信息對于消除歧義是必要的,但一般來說,用一個(gè)表明參數(shù)角色(role)而不是類型的詞,會(huì)更好一些。詳情請參看下一條。
基于變量、參數(shù)、關(guān)聯(lián)類型的角色來對它們進(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ù)在這里好像不對頭,索性就不翻了,大家根據(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ù)和方法的副作用對它們命名
- 沒有副作用的方法讀起來應(yīng)該是一個(gè)名詞詞組,譬如:
x.distance(to: y),i.successor()。 - 有副作用的方法讀起來應(yīng)該是一個(gè)命令式的動(dòng)詞短語,譬如:
print(x),x.sort(),x.append(y)。 - 使用 “ed/ing” 規(guī)則對一個(gè)可變方法(mutating method)的不可變版本命名。
一般來說,可變方法都會(huì)有一個(gè)對應(yīng)的不可變版本,該方法會(huì)返回一個(gè)和接受值相同或者相似類型的值。- 傾向于用過去分詞對不可變版本命名(一般是加 “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)在分詞對不可變版本命名,也就是加上 “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)該用名詞。