Swift 4.2 日本語化計画 : Swift 4.2
API 設計ガイドライン
訳注:詳細を全て展開した状態で翻訳します。
目次
基礎
- 使用時の透明性 が最も重要な目標です。メソッドやプロパティなどの実体は一度だけ宣言されますが、繰り返し 使用されます。これらの用途を明確かつ簡潔にするため API を設計します。設計を評価するとき、宣言を読むことはほとんど十分ではありません。常に使用法を調べて、文脈内で明確に見えるようにしてください。
- 明確さは、簡潔さよりも重要です。Swift のコードはコンパクトにすることができますが、可能な限り少ない文字数で最小限のコードを有効にすることが 目的ではありません。それが発生する Swift コードの簡潔性は、強力な型システムの副作用であり、自然に定型文を減らす機能です。
- すべての宣言について 文書コメントを書く。文書を書くことによって得られる理解は、あなたの設計に大きな影響を与える可能性があるので、やめないで下さい。
- Swift の Markdown の方言 を使用してください。
- 実体が宣言されていることを説明する 要約から始めます。多くの場合、API はその宣言とその要約から完全に理解することができます。
- 要約に焦点を当てる; これは最も重要な部分です。優れた文書のコメントの多くは、素晴らしい要約以外の何物でもない事だけで構成されています。
- 可能であれば、1 つの文の断片を使用し、ピリオドで終了します。完全な文章を使用しないでください。
- 関数またはメソッドが 何をして何を返す のかを記述し、null 効果と Void を返すのを省略します:
- サブスクリプトが アクセスする 内容を記述する:
- イニシャライザが 作成する 内容を説明する:
- 他のすべての宣言については、宣言された実体が何であるかを記述します。
- 必要に応じて、1つ以上の段落と箇条書き項目を 続行します。 段落は空白行で区切られ、完全な文章が使用されます。
- 適切な場合はいつでも、要約以外の情報を追加するには、認識された 記号のドキュメントマークアップ 要素を使用します。
- 認識された箇条書き項目を知り、記号コマンド構文 で使用して下さい。Xcode のような人気のある開発ツールは、以下のキーワードで始まる箇条書き項目に特別な処理を行います。
/// Returns a "view" of `self` containing the same elements in /// reverse order. func reversed() -> ReverseCollection
/// Inserts `newHead` at the beginning of `self`. mutating func prepend(_ newHead: Int) /// Returns a `List` containing `head` followed by the elements /// of `self`. func prepending(_ head: Element) -> List /// Removes and returns the first element of `self` if non-empty; /// returns `nil` otherwise. mutating func popFirst() -> Element?
注:上記の popFirst のようなまれなケースでは、要約はセミコロンで区切られた複数の文の断片で構成されています。
/// Accesses the `index`th element. subscript(index: Int) -> Element { get set }
/// Creates an instance containing `n` repetitions of `x`. init(count n: Int, repeatedElement x: Element)
/// A collection that supports equally efficient insertion/removal /// at any position. struct List { /// The element at the beginning of `self`, or `nil` if self is /// empty. var first: Element? ...
/// Writes the textual representation of each ← Summary /// element of `items` to the standard output. /// ← Blank line /// The textual representation for each item `x` ← Additional discussion /// is generated by the expression `String(x)`. /// /// - Parameter separator: text to be printed ⎫ /// between items. ⎟ /// - Parameter terminator: text to be printed ⎬ Parameters section /// at the end. ⎟ /// ⎭ /// - Note: To print without a trailing ⎫ /// newline, pass `terminator: ""` ⎟ /// ⎬ Symbol commands /// - SeeAlso: `CustomDebugStringConvertible`, ⎟ /// `CustomStringConvertible`, `debugPrint`. ⎭ public func print( _ items: Any..., separator: String = " ", terminator: String = "\n")
ネーミング
明確な使用の促進
- 名前が使用されているコードを読む人にとって あいまいさを避けるために必要なすべての単語を含めます。
- 不必要な言葉を省略しなさい。名前のすべての単語は、使用場所で重要な情報を伝える必要があります。
- 型の制約ではなく、役割に応じて変数、パラメータ、および関連する型 に名前を付けます。
- パラメーターの役割を明確にするために、弱い型の情報を補います。
たとえば、コレクション内の特定の位置にある要素を削除するメソッドを考えてみましょう。
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?
意図を明確にしたり意味を明確にするためには、もっと多くの単語が必要かもしれませんが、読者がすでに持っている情報と重複するものは省略してください。特に、型情報を 繰り返すだけ の単語は省略します。
public mutating func removeElement(_ member: Element) -> Element? allViews.removeElement(cancelButton)
この場合、Element という単語は、呼び出し場所で何も重要なものを追加しません。次の API はより良いでしょう:
public mutating func remove(_ member: Element) -> Element? allViews.remove(cancelButton) // clearer
時折、あいまいさを避けるために繰り返される型情報が必要な場合もありますが、一般的には、型ではなくパラメータの 役割 を記述する単語を使用する方が良い場合があります。詳細は以下の項目を参照してください。
var string = "Hello" protocol ViewController { associatedtype ViewType : View } class ProductionLine { func restock(from widgetFactory: WidgetFactory) }
このように型名を再度目的とすると、明確さと表現力が最適化されません。代わりに、実体の 役割 を表す名前を選択するように努めます。
var greeting = "Hello" protocol ViewController { associatedtype ContentView : View } class ProductionLine { func restock(from supplier: WidgetFactory) }
関連する型がそのプロトコル制約に非常に堅く束縛されているため、プロトコル名がその役割である場合、プロトコル名に Protocol を追加して重複を避けます:
protocol Sequence { associatedtype Iterator : IteratorProtocol } protocol IteratorProtocol { ... }
特に、パラメータ型が NSObject、Any、AnyObject、または Int や String などの基本型である場合、使用時に型情報とコンテキストが意図を完全に伝えていない可能性があります。以下の例では、宣言は明瞭ですが、使用場所は曖昧 (vague) です。
func add(_ observer: NSObject, for keyPath: String) grid.add(self, for: graphics) // vague
明確さを回復するには、弱い型付きの各パラメータの前に、その役割を説明する名詞を付けます。
func addObserver(_ observer: NSObject, forKeyPath path: String) grid.addObserver(self, forKeyPath: graphics) // clear
流暢な使用を目指す
- 使用場所が文法的な英語のフレーズを形成するメソッド名と関数名を使用することをお勧めします。
- ファクトリメソッドの名前を "make"で始める。例えば x.makeIterator()。
- イニシャライザと ファクトリメソッド の呼び出し の最初の引数は、ベース名で始まるフレーズを構成すべきではありません。例えば、 x.makeWidget(cogCount:47)
- 副作用に応じて関数と関数に名前を付ける
- 副作用のないものは名詞句として読み込む必要があります。例えば、 x.distance(to:y)、i.successor() など。
- 副作用があるものは動詞句の命令形として読み込む必要があります。例えば、 print(x)、x.sort()、x.append(y) など。
- 変化する/しないのメソッドのペア に一貫して 名前 を付けます。変化するメソッドには、同様の意味を持つ変化しない型がしばしばありますが、インスタンスを適切に更新するのではなく新しい値を返します。
- 操作が 動詞で自然に記述されている 場合は、変化するメソッドに動詞の命令法を使用し、接尾辞 "ed" または "ing" を適用して変化しない相手に名前を付けます。
- 動詞の過去 分詞 (通常は "ed" を追加します) を使って変化しない変数に名前を付けることを推奨します:
- 動詞に直接のオブジェクトがあるために "ed" を追加するのが文法的でない場合は、動詞の現在 分詞 を使用して、変化しない変数の名前に "ing" を付けます。
- 操作が 名詞で自然に記述されている 場合は、変化しないメソッドに名詞を使用し、変化する相手に接頭辞 "form" を付けます。
- ブール値メソッドおよびプロパティの使用は、使用が変化しない場合に 受信者についてのアサーションとして読み取る必要があります。例えば x.isEmpty、line1.intersects(line2)。
- 何かが何か を記述するプロトコルは名詞 (例えば、Collection) として読むべきです。
- 機能性 を記述するプロトコルには、接尾辞 able、ible、または ing を使用して命名する必要があります (例えば、Equatable、ProgressReporting)。
- 他の 型、プロパティ、変数、定数 の名前は 名詞として読みます。
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()
最初の引数の後に流暢さが低下してもよいし、それらの引数が呼び出しの意味の中心でないときには 2 つでもよい。
AudioUnit.instantiate( with: description, options: [.inProcess], completionHandler: stopProgressBar)
たとえば、これらの呼び出しの最初の引数は、ベース名と同じフレーズの一部として読み取られません。
let foreground = Color(red: 32, green: 64, blue: 128) let newPart = factory.makeWidget(gears: 42, spindles: 14) let ref = Link(target: destination)
以下の例では、API の作成者が最初の引数で文法的連続性を作成しようとしました。
let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128) let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14) let ref = Link(to: destination)
実際には、このガイドラインと 引数ラベル のガイドラインは、呼び出しが 値の保存型変換 を実行していない限り、最初の引数にラベルが付くことを意味します。
let rgbForeground = RGBColor(cmykForeground)
Mutating(変化する) | Nonmutating(変化しない) |
x.sort() | z = x.sorted() |
x.append(y) | z = x.appending(y) |
/// Reverses `self` in-place. mutating func reverse() /// Returns a reversed copy of `self`. func reversed() -> Self ... x.reverse() let y = x.reversed()
/// 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()
Nonmutating(変化しない) | Mutating(変化する) |
x = y.union(z) | y.formUnion(z) |
j = c.successor(i) | c.formSuccessor(&i) |
用語をよく使う
芸術の用語 | 名詞 (noun) - 特定の分野や職業の中で正確で特殊な意味を持つ語句。 |
- もっと一般的な言葉でも同様に意味を伝えられるならば、あいまいな言葉を避けてください。"皮膚(skin)" があなたの目的に役立つ場合、"表皮(epidermis)" とは言わないでください。芸術の用語は必須のコミュニケーションツールですが、そうでなければ失われる重要な意味を捉えるためにのみ使用してください。
- あなたが芸術の用語を使っているなら、確立された意味に固執します。
- 専門家を驚かせてはいけません。既にこの用語に精通している人は、私たちがその新しい意味を発明したように思えば、驚いて多分怒るでしょう。
- 初心者を混乱させないでください。用語を学びたい人は誰でも、web 検索を行い、その伝統的な意味を見つけることでしょう。
- 略語は避けてください。略語は、特に標準的でない形は、省略していない形に正しく翻訳することに理解が依存するため、効果的な芸術の用語です。
- 先例を受け入れる。既存の文化への準拠を犠牲にして、完全な初心者のための用語を最適化しないでください。
より一般的な言葉ではなく専門用語を使用する唯一の理由は、そうでなければあいまいであるか不明瞭なものを 正確に 表現することです。したがって、API は受け入れられた意味に従って厳密に用語を使用すべきです。
初心者が List の意味をより簡単に把握できるにもかかわらず、List などの簡略化された用語を使用するよりも、連続したデータ構造に Array の名前を付ける方がいいでしょう。配列 (Array) は現代のコンピュータ技術では基本的なものなので、すべてのプログラマーは配列が何であるかを知っているか、すぐに知るでしょう。ほとんどのプログラマーが慣れ親しんでいる用語を使用し、その Web 検索や質問には報酬が与えられます。
数学などの特定のプログラミング 分野 では、 verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x) などの説明フレーズよりも、sin(x) などの広く先例のある語句が好まれます。この場合、前例が、略語を避けるためのガイドラインより勝っていることに注意してください。完全な単語は sine ですが、何世紀にも渡ってプログラマーや数学者の間で "sin(x)" がよく使われています。
規約
一般的規約
- O(1) でない計算されたプロパティの複雑さを文書化します。人々はしばしば、プロパティのアクセスが精神的モデルとして、格納されたプロパティを持っているため、重要な計算を伴わないと仮定します。その仮定が守られていないときには、注意を喚起してください。
- 自由な関数にメソッドとプロパティの方が望ましい。自由な関数は特別な場合にのみ使用されます:
- 明らかな self がないとき:
- 関数が制約のない汎用の場合:
- 関数の構文が確立されたドメイン表記法の一部である場合:
- case の規則に従ってください。型とプロトコルの名前は UpperCamelCase です。他はすべて lowerCamelCase です。
- メソッドは、同じ基本的な意味を共有する場合、または異なるドメインで動作する場合には、ベース名を共有できます。
min(x, y, z)
print(x)
sin(x)
頭字語と頭文字 はアメリカ英語では通常すべて大文字で表示されますが、大文字小文字の規則に従って均一に大文字または小文字にされます。
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)
デフォルトの引数は、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)。
- 値の保存型変換を行うイニシャライザでは、最初の引数のラベルを省略します。例えば: Int64(someUInt32)。
- 最初の引数が 前置詞句 の一部を構成するときは、引数ラベルを付けます。引数ラベルは、通常、前置詞 で始まります。例えば、 x.removeBoxes(havingLength:12)。
- それ以外の場合、最初の引数が文法的なフレーズの一部を構成する場合は、そのラベルを省略して、先行する単語をベース名に追加します。例えば、x.addSubview(y)
- 他のすべての引数にラベルを付けます。
最初の引数は常に変換元でなければなりません。
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)
ただし、"絞り込み" 型の変換では、その絞り込みを表すラベルが推奨されます。
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) }
注意:元の値を取得する機能は、変換が値保持かどうかに関係しません。
最初の 2 つの引数が単一の抽象化の一部を表すときは、例外が発生します。
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?
デフォルト値の引数は省略することができ、その場合は文法的なフレーズの一部を構成しないので、常にラベルを付ける必要があることにも注意してください。
特別な命令
- API に表示される クロージャパラメータとタプルメンバーにラベルを付けます。
- オーバーロード設定のあいまいさを避けるために、制約のない多形性 (例えば:Any、AnyObject、および制約のない汎用パラメータ) に十分注意してください。
これらの名前は説明力を持ち、文書のコメントから参照したり、タプルメンバーへの表現力豊かなアクセスを提供します。
/// 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(_ newElements: S) where S.Generator.Element == Element }
これらのメソッドは意味のあるファミリーを形成し、引数型は最初はっきりとしています。ただし、 Element が Any の場合、単一の要素は要素のシーケンスと同じ型を持てます。
var values: [Any] = [1, "a"] values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?
あいまいさを解消するには、2 番目のオーバーロードをより明示的に名付けます。
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 }
新しい名前が文書のコメントとどのようによく一致するかに注目してください。この場合、文書のコメントを書く行為は実際に API の著者に注目を集めました。