くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Swift API Design GuidelinesのConventionsについて

今回はAPIのデザインガイドラインのConventionsについてまとめようと思います

前回の命名についてのまとめは↓↓下記みてください!!

kumaskun.hatenablog.com

英語も個人的にわかりにくく計算量など聞きなれない用語も出てきていたのでまとめます

Conventionsは慣例や規則・規約と訳すのかなと思っているのですが、あまりしっくりきていません。いい訳があれば教えて欲しいです!!

計算量について

最初にO(1)ではないcomputed propertyには、計算量などの説明をコメント化するべきと記載されています

ここで、O(1)や計算量など馴染みのない言葉が出てきます。 英語を訳するのは難しくないかもしれないですが、O(1)や計算量を知らないと内容を理解するのは難しいと思います

まず、計算量とはあるアルゴリズムを実行するときに入力に対してどのくらいの時間またはメモリが必要かを表す指標です

時間の目安は時間計算量と呼ばれ、メモリの計算は空間計算量と呼ばれています。計算量とは時間計算量を表すことが多いです

そして、O(1)は計算量を表す指標です

O(1)は計算量が入力にかかわらず一定になることを表しています。例えば、配列のn番目にアクセスするなどです。 配列のサイズが変わったとしてもn番目にアクセスする際の時間は変わらないです

一方で、O(n)は計算量が入力に応じて比例します。例えば、配列の最初からn番目までの全てのデータにアクセスするなどです

プロパティは通常O(1)であることが一般的なので、O(1)ではない場合にはコメントを記載するべきです

その他の一般的な規約について

計算量の規約以外には下記のような規約が記載されています

  • Free functionsは特別な場合のみ使用して、基本的にはメソッドとプロパティを用いましょう

Free functionsとは構造体、クラス、列挙型等のメンバーではない関数のことです

Free functionsを使う場合は次の3パターンの場合に使用します

①明確なselfがない場合(クラス外で定義された可能性が高い関数の場合)

②関数が汎用的な場合(例:print(x))

③分野的に関数表記の方が自然な場合(例えば、sin(x)やmin(x)などすでに機能が提供されている場合)

  • 型とプロトコル名はUpperCamelCase(大文字から始まる)でその他はlowerCamelCase(小文字から始まる)

  • 基本的な部分で同じ意味を持つ場合、または異なるドメイン(クラスなど)で操作する場合、メソッドは名前を共有できます

例えば、次のように引数の型はそれぞれ異なりますが、基本的に同じことをしている場合にはメソッド名を同じにしても構いません

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

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

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

しかし、次のように処理の内容が異なっていたり、戻り値の型のみが異なるメソッドを複数定義するのは避けましょう

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

パラメータについて

大前提として、パラメータ名も処理内容を理解するドキュメントの役割を持つので、命名の際には意識するようにしましょう

そして、不要な情報を隠すという意味でデフォルト引数をうまく利用するようにしましょう。 デフォルト引数を利用することで処理内容を理解する負担が減ります(デフォルト引数の部分はあくまでもオプションでメインの部分は他の部分のため)

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

また、処理は同じなのに引数に応じていくつも定義すると読み手が理解するのに苦労するため、このような場合にもデフォルト引数は有効です

デフォルト引数を使う場合には後ろの方に配置するのが良いです。先に初期値のない引数を配置して、後からデフォルト引数を配置しましょう

引数ラベルについて

最後に引数ラベルについてです

まず、引数を区別する意味がない場合は、全てのラベルを省略しましょう。 例えば、min(number1, number2)zip(sequence1, sequence2)など引数を比較したり意味や順番などなく使用する場合などです

型的に大きくなる型変換で見られるべきイニシャライザの場合、第一引数は 変換元であり、引数ラベルはつけるべきではありません

extension String {
  //  xを基数(radix)を元に文字列に変換(BigInt → String) 
  init(_ x: BigInt, radix: Int = 10)
}

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

逆に型的に小さくなる型変換の場合、小さくなることを記述するラベルをつけることが推奨されます

extension UInt32 { 
  // 型的に大きくなる(Int16 → UInt32)のでラベルは不要 
  init(_ value: Int16)
  // 型的に小さくなる(UInt64 → UInt32)ので、切り捨てられる旨のラベルを推奨 
  init(truncating source: UInt64)
  /// 型的に小さくなる(UInt64 → UInt32)が近似値を取得するためsaturatingのラベルを追加する
  init(saturating valueToApproximate: UInt64)
}

※型変換はMonomorphismです。 すなわち、変換元の値の違いは全て変換後の値の違いとなります。 例えば、Int8からInt64への変換は値を保つ型変換で、全ての異なるInt8の値は異なるInt64の値に変換されます。 しかし、Int64はInt8で表すことができる値より多くの値とることができるため、逆方向の変換は値を保つことができません。

第1引数が前置詞句の一部を構成するときは、引数ラベルを付けましょう。 引数ラベルは通常、前置詞で始まるべきです(例:x.removeBoxes(havingLength: 12)) ここはあっているか微妙なところです・・havingLengthは前置詞句ではないような気が

そして、2つ(複数)の引数が一つの抽象概念(処理の内容)の一部を表している場合は例外です。 例えば次の例は良くない例です

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

toXやfromRedは他の引数にもかかります。toはXにもyにもかかる内容です。 その場合には次のようにメソッド名に前置詞を追加することでわかりやすくなります(前置詞の後に引数ラベルを追加する)

a.moveTo(x: b, y: c)
a.fadeFrom(red: b, green: c, blue: d)

それ以外の場合、第1引数が英文的なフレーズの一部を構成していない場合はラベルのままにしておくべきです。 そして、フレーズが正しい意味を伝えることが大切だということに注意してください。 次の例は英文法的には正しいかもしれませんが、間違った解釈される可能性があるのでよくない例です

view.dismiss(false)
words.split(12)

falseや12が意味するものが間違った解釈をされる可能性があるので、次のようにラベルを表示させるのが望ましいです

view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

今回のドキュメント

www.swift.org

www.appypie.com