Swift专题首页投稿(暂停使用,暂停投稿)程序员

Swift API 设计指南(下)

2016-04-13  本文已影响586人  Sheepy

接上篇:Swift API 设计指南(上)

合理使用术语(Terminology)

你使用的所有缩写,必须可以很轻易的上网查到它的意思。

约定

通用约定

var utf8Bytes: [UTF8.CodeUnit];
var isRepresentableAsASCII = true;
var userSMTPServer: SecureSMTPServer;

其它首字母缩略词当作普通单词处理即可:

var radarDetector: RadarScanner;
var enjoysScubaDiving = true;
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 { ... }
}

最后,避免参数重载,因为这在类型推断时会产生歧义:

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? { ... }
}

参数

func move(from start: Point, to end: Point)
  /// Return an `Array` containing the elements of `self`
  /// that satisfy `predicate`.
  func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]

  /// Replace the given `subRange` of elements with `newElements`.
  mutating func replaceRange(_ subRange: Range, with newElements: [E])

而下面这些就使文档难以理解且不合语法:

  /// Return an `Array` containing the elements of `self`
  /// that satisfy `includedInResult`.
  func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]

  /// Replace the range of elements indicated by `r` with
  /// the contents of `with`.
  mutating func replaceRange(_ r: Range, with: [E])
let order = lastName.compare(
  royalFamilyName, options: [], range: nil, locale: nil)

可以更加简洁:

let order = lastName.compare(royalFamilyName)

一般来说,默认参数比方法族(method families)更可取,因为它减轻了 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
}

方法族中的每个方法都需要被分别注释和被使用者理解。决定使用哪个方法之前,使用者必须理解所有方法,并且偶尔会对它们之间的关系感到惊讶,譬如,foo(bar: nil)foo()并不总是同义的,从几乎相同的文档和注释中去区分这些微笑差异是十分乏味的。而一个有默认参数的方法将提供上等的编码体验。

参数标签

func move(from start: Point, to end: Point)
x.move(from: x, to: y) 
min(number1, number2)
zip(sequence1, sequence2)
extension String {
    // Convert `x` into its textual representation in the given radix
    init(_ x: BigInt, radix: Int = 10)   ← Note the initial underscore
}

  text = "The value is: "
  text += String(veryLargeNumber)
  text += " and in hexadecimal, it's"
  text += String(veryLargeNumber, radix: 16)

然而,在一个“narrowing”的转换中(译者注:范围变窄的转换,譬如 Int64 转 Int32),用一个标签来表明范围变窄是推荐的做法。

extension UInt32 {
    /// Creates an instance having the specified `value`.
    init(_ value: Int16)            ← Widening, so no label
    /// Creates an instance having the lowest 32 bits of `source`.
    init(truncating source: UInt64)
    /// Creates an instance having the nearest representable
    /// approximation of `valueToApproximate`.
    init(saturating valueToApproximate: UInt64)
}
x.removeBoxes(havingLength: 12)

头两个参数各自相当于某个抽象的一部分的情况,是个例外:

a.move(toX: b, y: c)
a.fade(fromRed: b, green: c, blue: d)

在这种情况下,为了保持抽象清晰,参数标签从介词后面开始。

a.moveTo(x: b, y: c)
a.fadeFrom(red: b, green: c, blue: d)
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

注意,短语必须表达正确的意思,这非常重要。如下的短语是符合语法的,但它们表达错误了。

view.dismiss(false)   Don't dismiss? Dismiss a Bool?
words.split(12)       Split the number 12?

注意,默认参数是可以被删除的,在这种情况下它们都不是短语的一部分,所以它们总是应该有标签。

特别说明

/// Ensure that we hold uniquely-referenced storage for at least
/// `requestedCapacity` elements.
///
/// If more storage is needed, `allocate` is called with
/// `byteCount` equal to the number of maximally-aligned
/// bytes to allocate.
///
/// - Returns:
///   - reallocated: `true` iff a new block of memory
///     was allocated.
///   - capacityChanged: `true` iff `capacity` was updated.
mutating func ensureUniqueStorage(
    minimumCapacity requestedCapacity: Int, 
    allocate: (byteCount: Int) -> UnsafePointer<Void>
) -> (reallocated: Bool, capacityChanged: Bool)

用在闭包中时,虽然从技术上来说它们是参数标签,但你应该把它们当做参数名来选择和解释(文档中)。闭包在方法体中被调用时跟调用方法时是一致的,方法签名从一个不包含第一个参数的方法名开始:

allocate(byteCount: newCount * elementSize)
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)
}

这些方法组成了一个语义族(semantic family),第一个参数的类型是明确的。但是当ElementAny时,一个单独的元素和一个元素集合的类型是一样的。

var values: [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 append<
      S : SequenceType where S.Generator.Element == Element
    >(contentsOf newElements: S)
}

注意新命名是怎样更好地匹配文档注释的。在这种情况下,写文档注释时实际上也在提醒 API 作者自己注意问题。

上一篇 下一篇

猜你喜欢

热点阅读