📖

API Design Guidelinesを読みながら命名規則についてまとめてみた!

2025/02/03に公開

もうすぐiOSエンジニアになるので、API Design Guidelinesをもう一度読み直しながら命名規則についてまとめてみました!

明瞭な命名の推進

曖昧さを避けるために、必要なすべての単語を含め命名する。

extension List {
  public mutating func remove(at position: Index) -> Element
}
employees.remove(at: x) // ⚠️employees.remove(x)にしない

上記のコードの命名は、x番目の要素を削除するというメソッドであるということが明瞭である。
employees.remove(x)の場合は、x番目の要素を削除するのか OR xの値と等しい要素を削除するのかがわからない。

不必要な単語は省略して命名する。

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

allViews.remove(cancelButton) // ⚠️removeElementにしない

basket.remove("りんご")

上記のコードで、例えば、basket配列からりんごという要素を削除するというように自然に読むことができます。

変数、引数、関連型の命名は役割に応じて命名する。

var greeting = "Hello" // ⚠️var stringにしない
protocol ViewController {
  associatedtype ContentView : View // ⚠️ViewType : Viewにしない
}
class ProductionLine {
  func restock(from supplier: WidgetFactory) // ⚠️from widgetFactoryにしない
}

Protocolを命名する。

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
}
protocol IteratorProtocol { ... }

プロトコル名が役割(名詞)になっているときは、接尾語に-Protocolをつける
追加情報:能力を説明するプロトコルは、接尾語に -able、-ible、または -ingを使用した名前にすべき((例 Equatable, ProgressReporting)

func addObserver(_ observer: NSObject, forKeyPath path: String)
grid.addObserver(self, forKeyPath: graphics)

UserDefaults.setValue(<#T##value: Any?##Any?#>, forKey: <#T##String#>)

メソッドの引数をforではなく、forKeyPathにすることで、呼び出し元を見ないでもkeyPathが入ることがわかる。

流暢な表現を目指す

文法的に正しい英語のフレーズを形成するメソッド名と関数名を優先する。

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”

英語の文法的に引数を使用するとメソッドの意味がわかりやすい。

意味の中心ではない引数ラベルの扱い

AudioUnit.instantiate(
  with: description,
  options: [.inProcess], completionHandler: stopProgressBar)

上記のoptionscompletionHandlerなど、関数呼び出しで主体にならない引数は英文法の流れが崩れても良い。

イニシャライザとファクトリメソッド

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

イニシャライザとファクトリメソッドの場合は、英文法に則るのではなく、引数が何なのかを表現する。

副作用の有無に応じた関数の命名

⚫︎副作用の無い関数は名詞で命名する。
⚫︎副作用の有る関数は命令形の動詞で命名する。

※副作用の有無で対になっている関数では下記のように例外がある。
⚫︎名前を動詞で自然に表現できる時
・副作用の無い関数は動詞の過去形または現在形(-edまたは-ing)で命名する。
例: z = x.sorted()z = x.appending(y)
・副作用の有る関数は変わらず、命令形の動詞で命名する。
例: sort()x.append(y)

⚫︎名前を名詞で自然に表現できる時
・副作用の無い関数は変わらず、名詞で命名する。
例: x = y.union(z)j = c.successor(i)
・副作用の有る関数は接頭辞form(form-)で命名する。
例: y.formUnion(z)c.formSuccessor(&i)

そもそも副作用とは、
プログラミングにおける式の評価による作用には以下の2つがある。
・主作用
・副作用(side effect)

副作用無し(主作用)
副作用の無いコードとは、
・同じ条件を与えれば必ず同じ結果になる
・他のいかなる機能の結果にも影響を与えない
主作用とは、引数を受け取り値を返す

func double(x: Int) -> Int {
  x * x
}
let ans =  double(x: 2)

上記のように引数を受け取り値を返すのは、主作用です。

副作用有り
副作用の有るコードとは
・同じ条件を与えても必ず同じ結果になるとは限らない。
・他の機能の結果に影響を与えてしまう。
例:状態の変更・APIリクエストなどのI/O実行

var sum: Int = 0
func add(x: Int, y: Int) -> Int {
  let result = x + y
  // 主作用以外に、スコープ外の変数の値を変更している。
  sum = result
  return result
}
print(sum) 
_ = add(x: 1, y: 4)
// addメソッド呼び出し前後で結果がかわってしまっている
print(sum) // 5

真偽値型のメソッドやプロパティー

内容変化を伴わない場合は、レシーバーの主張として読み取れるようにする

x.isEmpty // xは空
line1.intersects(line2) // line1はline2と交差する

メソッドでも真偽値(Bool)を戻り値に持つもの

let basket = ["りんご", "みかん"]
// ⚠️真偽値型のメソッドの場合は適当ではない
func containing(_ elemant: String) -> Bool {
    return basket.contains(elemant)

}
// ✅真偽値型のメソッドの場合、主張として読み取れるようにする
func contains(_ elemant: String) -> Bool {
    return basket.contains(elemant)

}
let ans = containing("りんご") // true

その他の型、プロパティ、変数、定数の名前は名詞にする

これまでのルールで決められていない型やプロパティは名詞にする

大文字と小文字の区別

型とプロトコルの名前にはUpperCamelCase。それ以外は、lowerCamelCaseを使用する。

※頭字語の扱いは例外
アメリカ英語で一般的にすべて大文字で表示される頭字語は一律に大文字にする必要がある。

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

protocol URLSessionProtocol {
    func data(for request: URLRequest) async throws -> (Data, Int)
}

同じメソッド名を使用することも可能

extension Shape {
  func contains(_ other: Point) -> Bool { ... }
  func contains(_ other: Shape) -> Bool { ... }
  func contains(_ other: LineSegment) -> Bool { ... }
}

extension Collection where Element : Equatable {
  func contains(_ sought: Element) -> Bool { ... }
}

基本的に同じ意味合いで使用するときは、複数メソッドで同じ名前を使用しても良い。

戻り値の型でのオーバーロード

extension Box {
  // ⚠️戻り値の型でのオーバーロードはしない
  func value() -> Int? { ... }
  func value() -> String? { ... }
}

同じメソッド名で戻り値だけが異なるオーバーロードは避ける。

引数の表現方法

ドキュメントになる引数名を選ぶ

ドキュメントを自然に読ませる名前にする

// ⚠️ドキュメントが不自然で文法的に不正確なもの
func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]
mutating func replaceRange(_ r: Range, with: [E])

// ✅ドキュメントが自然に読みやすいもの
func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]
mutating func replaceRange(_ subRange: Range, with newElements: [E])

引数のデフォルトパラメータを活用

// ⚠️デフォルト引数を使用していない
let order = lastName.compare(
  royalFamilyName, options: [], range: nil, locale: nil)

// ✅デフォルト引数を使用する
let order = lastName.compare(royalFamilyName)
extension String {
  /// ...description...
  public func compare(
     _ other: String, options: CompareOptions = [],
     range: Range? = nil, locale: Locale? = nil
  ) -> Ordering
}

デフォルト引数は、無関係な情報を非表示にすることで読みやすさを向上させる。

引数ラベルを区別する必要のない場合は省略

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

変換イニシャライザの引数

・値を保全した型変換を行うときは、最初の引数ラベルを省略する
・値の再解釈を伴う型変換は、最初のラベルで変更方法を説明する

extension UInt32 {
  init(_ value: Int16)         
  init(truncating source: UInt64)
  init(saturating valueToApproximate: UInt64)
}

let a: Int = -1
let b: String = String(a) // -1
let c: UInt = UInt(clamping: a) // 0

前置詞句を構成する最初の引数

// ⚠️複数の引数を持つ場合、引数ラベルに前置詞を入れない
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)

前置詞句を構成しない最初の引数

・文法的な句を構成をするときは、ラベル名を省略する
x.addSubview(y)
・文法的な句を構成をしないときは、引数ラベルで説明する

// ⚠️引数ラベル省略して意味がわからない
view.dismiss(false)   Don't dismiss? Dismiss a Bool?
words.split(12)       Split the number 12?

// ✅引数ラベルで説明する
view.dismiss(animated: false)
let text = words.split(maxSplits: 12)
let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)

それ以外は引数ラベルをつける

引数のデフォルトパラメータを活用しない・前置詞句を構成しないときは常にラベルをつける。

特記事項

API定義内におけるタプルの要素

・タプルにラベルをつけることで、可読性が高まる。

mutating func ensureUniqueStorage(
  minimumCapacity requestedCapacity: Int,
  allocate: (_ byteCount: Int) -> UnsafePointer<Void>
) -> (reallocated: Bool, capacityChanged: Bool)

API定義内におけるクロージャーの引数

・クロージャーには引数ラベルつけられないので_でようとを説明する。
・名前はドキュメントコメントで役立つ名前にする

mutating func ensureUniqueStorage(
  minimumCapacity requestedCapacity: Int,
  allocate: (_ byteCount: Int) -> UnsafePointer<Void>
) -> (reallocated: Bool, capacityChanged: Bool)

制約のないポリモーフィズムに注意を払う

struct Array {
  public mutating func append(_ newElement: Element)
  public mutating func append(_ newElements: S)
    where S.Generator.Element == Element
}
var values: [Any] = [1, "a"]
// ⚠️valuesの型が配列のAnyであったとき上記の2つのメソッドどちらを使用しているかわからない。
values.append([2, 3, 4]) // ⚠️[1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?

struct Array {
  public mutating func append(_ newElement: Element)
// ✅contentsOfを引数ラベルにつける
  public mutating func append(contentsOf newElements: S)
    where S.Generator.Element == Element
}

valuesの型が配列のAnyであったとき上記の2つのメソッドどちらを使用しているかわからないので、曖昧さをなくすために、2 番目のオーバーロードにもっと明確な名前を付ける。

参考文献

https://www.swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage
https://youtu.be/RSK1LEtxzuE?si=n4aXXKXxijyzavT6
https://dev.classmethod.jp/articles/protocol-naming-style/
https://zenn.dev/ueshun/articles/fcf1e27eca3cf5

Discussion