auto

先日 (と言っても結構前ですが)、 Apple から Swift の API におけるガイドライン が公開されました。当然ながら英語で書かれていたのですが、これはちゃんと理解しないといけないのでは、という気になりました。

なので、すでに翻訳されたものがあるのは知っていたのですが、自力で翻訳して読んでみることにしました。私は英語が得意ではないので、おそらく間違っているところもあると思います。怪しいと思ったら原文か翻訳されたものを読んでください。

また、気になった点だけ抜粋していますので、すべてを訳したわけではありません…

サンプルコードは、Swift API Design Guidelines のものをコメントだけ訳して掲載しています。

基本

  • メソッドやプロパティの用途を明確にしましょう。
  • 簡潔さよりも明確さを重視しましょう。
  • すべての定義にドキュメンテーションコメントを書きましょう。
    • Swift 風味の Markdown を使ってコメントを書きましょう。
    • コメントは要約から書きましょう。
    • 要約はとても大事です。
    • 単一の文章で表現しましょう。
    • メソッドや関数が何をするものか、何を返すのかを書きましょう。
    • subscript を実装するときは、それが何にアクセスするのかを書きましょう。
    • イニシャライザが何を生成するのかを書きましょう。
    • それ以外の定義については、エンティティの内容について書きましょう。
    • 必要に応じて、段落や箇条書きのコメントを書きましょう。段落を設けるときは空白行で区切りましょう。

命名

曖昧な表現にならないような名前にしましょう

NG

remove だと x に何を指定するか判別しづらいですね。

employees.remove(x)

OK

下の例だと、x に指定するのが index だということがすぐに判別できます。

extension List {
  public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x)

不要な単語を含めないようにしましょう

NG

下の例だと、removeElementElement と、引数で指定した cancelButton が同じ削除対象を示していて、読みづらくなっています。

public mutating func removeElement(_ member: Element) -> Element?

allViews.removeElement(cancelButton)

OK

下のようにすれば「remove するのが cancelButton」というように解釈しやすいです。

public mutating func remove(_ member: Element) -> Element?

allViews.remove(cancelButton) // clearer

変数名は型の名前をそのまま使うのではなく、役割に合ったものにしましょう

url とかもよくやりますね…。

NG

var string = "Hello"

protocol ViewController {
  associatedtype ViewType : View
}

class ProductionLine {
  func restock(from widgetFactory: WidgetFactory)
}

OK

var greeting = "Hello"

protocol ViewController {
  associatedtype ContentView : View
}

class ProductionLine {
  func restock(from supplier: WidgetFactory)
}

型の情報で意図が判断しづらい場合、メソッド名で補完しましょう

とくに NSObjectAnyAnyObject などのなんでも入る型や、IntString などの基本形は型でそれが何か特定しづらいので、対策が必要です。

NG

func add(_ observer: NSObject, for keyPath: String)

grid.add(self, for: graphics) // vague

OK

func addObserver(_ observer: NSObject, forKeyPath path: String)

grid.addObserver(self, forKeyPath: graphics) // clear

メソッド名は英語の文法に倣った形にしましょう

NG

x.insert(y, position: z)
x.subViews(color: y)
x.nounCapitalize()

OK

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"

ファクトリーメソッドの名前は make で始めましょう

makeIterator() のような感じですね。

初期化メソッドとファクトリーメソッドの第一引数を説明調にしてはいけません

例を見ると第一引数だけではない気もするのですが…

NG

let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)

OK

let foreground = Color(red: 32, green: 64, blue: 128)
let newPart = factory.makeWidget(gears: 42, spindles: 14)

メソッドの副作用に応じた名前にしましょう

副作用のないメソッドは名詞にしましょう

x.distance(to: y)i.successor() など。

副作用のあるメソッドは動詞にしましょう

print(x)x.sort()x.append など。

副作用のある / ない メソッドを両方実装する場合、メソッド名を動詞にするのが自然であれば、副作用のないメソッドに ed または ing を付けましょう

副作用あり 副作用なし
x.sort() z = x.sorted()
x.append() z = x.appending(y)

副作用のある / ない メソッドを両方実装する場合、メソッド名を名詞にするのが自然あれば、副作用のあるメソッドに form を付けましょう

副作用あり 副作用なし
x = y.union(z) y.formUnion(z)
j = c.successor(i) c.formSuccessor(&i)

Boolean のメソッドとプロパティは、操作しないものの場合、レシーバのアサーションとして読めるようにしましょう

訳がいまいちわかっていないのですが、文章として読めるようにしろ、ってことでしょうか。

x.isEmpty, line1.intersects(line2) など。

それが何かを示すプロトコルは、名詞で表しましょう

Collection など。

何かができることを示すプロトコルは、able, ible または ing を付けましょう

EquatableProgressReporting など。

専門用語を適切に使いましょう

一般的な言葉で適切なものがあるなら専門用語を使わないようにしましょう

英語のスキルがない私には関係のない話です…。

専門用語は確立された意味でのみ使用しましょう

専門用語は、明確な意味を持たせるために使用します。

勝手に用語を短縮しない

逆に一般的な略語 (Web で簡単に検索できるようなもの) は使用してもよい。

(プログラミング界、コンピュータ界、数学界などで) 決まった用語があればそれを優先して使用する

例えば、List のほうが初心者にはわかりやすくても、コンピュータ界では配列は Array が一般的なので、Array を使います。

また、verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)sine(x) よりも sin(x) を使用します。

慣例

一般的な慣例

O(1) でない Computed Property には、どれぐらい複雑なのかをドキュメント (コメント) に書きましょう

Computed Property は複雑なものではないというのが一般的な認識なので、そうでない場合はコメントに記しましょう。

以下を除いては、関数よりもメソッドやプロパティにするほうが望ましい

  • self が明らかでない場合 (min(x, y, z) など)
  • グローバルに使用するもの (print(x) など)
  • 一般的に確立された関数 (sin(x) など)

大文字、小文字のルールに従いましょう

型とプロトコルは UpperCamelCase、それ以外は lowerCamelCase にします。

私は Objective-C では略語を使うときは変数でも URLJSON のように大文字にしていましたが、Swift ではダメなようです…。

同じ意味を持つメソッドは同じ名前にしてもよい

OK
extension Shape {
  /// Returns `true` if `other` is within the area of `self`.
  func contains(_ other: Point) -> Bool { ... }

  /// Returns `true` if `other` is entirely within the area of `self`.
  func contains(_ other: Shape) -> Bool { ... }

  /// Returns `true` if `other` is within the area of `self`.
  func contains(_ other: LineSegment) -> Bool { ... }
}

ただし、意味の異なるメソッドは別名にすべきです。

NG

上の index() はデータベースの検索インデックスを作り直すメソッド。下の 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? { ... }
}

引数

引数名は、メソッドや関数がドキュメントのようになるように命名しましょう

OK
/// `述語` を満たす `self` の要素を含む配列を返す?
func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]

/// `subRange` で指定された要素を `newElements` に置き換える
mutating func replaceRange(_ subRange: Range, with newElements: [E])
NG
/// `includedInResult` を満たす `self` の要素を含む配列を返す?
func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]

/// `r` で指定された要素を `with` に置き換える
mutating func replaceRange(_ r: Range, with: [E])

引数にデフォルト値をセットして読みやすくしましょう

下の例では、options 以下の引数にデフォルト値をセットしています。

extension String {
  /// ...description...
  public func compare(_ 
    other: String, 
    options: CompareOptions = [],
    range: Range? = nil, 
    locale: Locale? = nil
    ) -> Ordering
}

こうすることで、デフォルト値でよい場合は引数の指定が少なくなり、見やすくなります。

let order = lastName.compare(royalFamilyName)

引数の並びは「デフォルト値を持たない引数」「デフォルト値を持つ引数」の順にします

デフォルト値を持たない引数のほうが基本的には重要なので、おのずと前に来るはず (ということです)。

引数ラベル

引数を区別できない場合はラベルを省略します

例: min(number1, number2), zip(sequence1, sequence2)

キャストするイニシャライザは第 1 引数を省略します

例: Int64(someUInt32)

第 1 引数が前置詞句の一部となる場合は、引数ラベルを与えます

例: x.removeBoxes(havingLength: 12)

ただし、2 つ (複数の?) 引数が同じ前置詞句に関連する場合は、メソッドや関数名に前置詞句を付けます。(訳に自信なし)

NG
// `x` と `y` に `move` するので、`to` が `x` だけについていると誤解を招く。
a.move(toX: b, y: c)
a.fade(fromRed: b, green: c, blue: d)
OK
a.moveTo(x: b, y: c)
a.fadeFrom(red: b, green: c, blue: d)

一方で、最初の引数が文法的なフレーズの一部となる場合、引数ラベルを省略します

訳に自信なし

例: x.addSubView(y)

このガイドラインは、最初の引数が文法的なフレーズでない場合は、引数ラベルを付けるべきだということを意味しています。

正しい意味を伝えることが重要だということを覚えておいてください。以下は文法的ではあるけれども、間違った表現です。

view.dismiss(false)   // false の意味が伝わらない。dismiss しないということか
words.split(12)       // 12 の意味がわからない

特例

クロージャのパラメータやタプルにはラベルを付けましょう

ラベルを付けることによって、コメントでそれが何かを説明しやすくなります。 クロージャのラベルは引数ラベルですが、引数名のように扱うべきです。

AnyAnyObject、制約のないジェネリクスを使用する場合、曖昧な意味にならないように気をつけましょう

以下の例の場合、ElementAny であれば IntString のような単一の要素も、[Int][String] のような配列も対象になります。

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(_ newElements: S)
where S.Generator.Element == Element
}

そのため、以下のような場合 append した結果が [1, "a", [2, 3, 4]]なのか [1, "a", 2, 3, 4] なのかはっきりしません。

var values: [Any] = [1, "a"]
values.append([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(contentsOf newElements: S)
where S.Generator.Element == Element
}