【Swift3】Swift API Design Guidelines を詳しく読む
原文を自分で読んでポイントをメモしてみようと思いましたが、
割とまるまる翻訳するかたちになってしまいました。
原文:https://swift.org/documentation/api-design-guidelines/
の併読もおすすめいたします。
Fundamentals(基本)
・使用する時にわかりやすいことが最も重要な目標です。
メソッドやプロパティは繰り返し使われるので、用途を明確かつ簡潔にしましょう。
・わかりやすさは、簡潔さよりも重要です。
Swiftのコードはすごく短く書くことも出来ますが、わかりやすさを犠牲にしないようにしましょう。
・すべての宣言についてのドキュメンテーションコメントを書きましょう。
書くことによって良いAPI設計の洞察が得られます。
もしそのAPIの機能が簡単に説明できないとしたら、API設計が間違ってるかもしれません。
・SwiftにおけるMarkdown記法を使って書きましょう。
・APIのコメントはサマリー(概要)から書きます。
/// Returns a "view" of `self` containing the same elements in /// reverse order. /// 自身を逆順にしたものを返します。 func reversed() -> ReverseCollection
・サマリーにフォーカスしましょう。最も重要です。優れたドキュメンテーションコメントの多くは、わかりやすいサマリーだけで書かれています。
・完全な文章でなく、出来れば一行ぐらいで説明しましょう。文章の最後にはピリオドをつけます。
・メソッドや関数が何をして、何を返すのかを書きましょう。戻り値がVoidの説明は省略します。
/// Inserts `newHead` at the beginning of `self`. /// `self`の先頭に` newHead`を挿入します。 mutating func prepend(_ newHead: Int) /// Returns a `List` containing `head` followed by the elements /// of `self`. /// `head`とそれに続く` self`の要素を含む `List`を返します。 func prepending(_ head: Element) -> List /// Removes and returns the first element of `self` if non-empty; /// returns `nil` otherwise. /// 空でなければ `self`の最初の要素を削除して返します。そうでなければ `nil`を返します。 mutating func popFirst() -> Element?
Note: popFirstのような稀なケースでは、サマリーをセミコロンで区切った複数の文章を書きます。
・subscriptが何にアクセスするかを記述しましょう:
補足 : https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Subscripts.html
/// Accesses the `index`th element. /// `index`番目の要素にアクセスします。 subscript(index: Int) -> Element { get set }
・イニシャライザの作成内容を記述しましょう:
/// Creates an instance containing `n` repetitions of `x`. /// n回、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. /// selfの先頭のエレメント。空の場合はnilです。 var first: Element?
・必要に応じて、段落と箇条書きの項目を続けます。段落は空行で区切り、詳細な説明を書き加えます。
/// Writes the textual representation of each ← Summary(概要) /// element of `items` to the standard output. /// 標準出力にitems各要素のテキスト表現を書き込みます。 /// ← Blank line(空行) /// The textual representation for each item `x` ← Additional discussion(追加のディスカッション) /// is generated by the expression `String(x)`. /// 各アイテムXのテキスト表現は 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`. ⎭ /// 注釈:末尾を改行せずにプリントするには、terminator: ""を渡します。 ⎭ /// 参考:CustomDebugStringConvertible, CustomStringConvertible, debugPrint。 public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
・サマリー以上の情報を追加するにはシンボルドキュメンテーションマークアップを使用します 。
・シンボルコマンドシンタックスを使用しましょう 。Xcodeなどのポピュラーな開発ツールでは次のキーワードで始まる項目に特別な処理を行います。
Attention | Author | Authors | Bug |
Complexity | Copyright | Date | Experiment |
Important | Invariant | Note | Parameter |
Parameters | Postcondition | Precondition | Remark |
Requires | Returns | SeeAlso | Since |
Throws | Todo | Version | Warning |
Naming(命名)
・命名にはあいまいさを避けるために必要な全ての単語を入れましょう。
例えば、コレクション内の特定の位置にある要素を削除するメソッドを考えてみます。
/* OK */ extension List { public mutating func remove(at position: Index) -> Element } employees.remove(at: x)
もし、次のようにこのメソッドのシグネチャからatを省略してしまうと「リストのx位置の要素を削除する」のではなく「リストの中からxと一致するものを削除する」メソッドのように見えてしまいます。
/* NG */ employees.remove(x) // unclear: are we removing x?
Promote Clear Usage(明確に使用されるように)
・不必要な言葉は捨てましょう。すべての単語はっきりとした情報を伝えている必要があります。
意図や意味を明確にするために、多くの単語が必要に思うかもしれませんが、読む人が既に持っている情報と重複するものは省略してください。特に、型情報を繰り返すだけの単語は除きます 。
/* NG */ public mutating func removeElement(_ member: Element) -> Element? allViews.removeElement(cancelButton)
この場合のElementは何の情報ももたらしません。
次のようにすると、より良いでしょう:
/* OK */ public mutating func remove(_ member: Element) -> Element? allViews.remove(cancelButton) // より明確
時には、曖昧さを避けるために繰り返される型情報が必要になることもありますが、仮引数名には型ではなく引数の役割を記述した方が良いです。詳細については、次の項目を参照してください。
・変数、引数、associatedtypeの命名には、型名をそのまま付けるのではなく役割名を付けましょう
/* 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) }
(ただし以下のように)associated typeがそのプロトコル制約に強い結びつきがあり、プロトコル名が役割である場合、associated typeに"Type"を付加したすることで衝突を避けます :
protocol Sequence { associatedtype IteratorType : Iterator }
・引数は役割を明確にするように曖昧な型情報は補いましょう。
引数の型が、NSObject、Any、AnyObject、もしくは、基本型のInt、Stringの時は特に、使う時点での型情報とコンテキストが意図を完全に伝えていない可能性があります。この例では、宣言を見ると明確かもしれませんが、呼び出し箇所では曖昧です。
/* NG */ func add(_ observer: NSObject, for keyPath: String) grid.add(self, for: graphics) // 曖昧
明確さを取り戻すには、曖昧な型の各引数の頭に、その役割を説明する名詞を付けます。
/* OK */ func addObserver(_ observer: NSObject, forKeyPath path: String) grid.addObserver(self, forKeyPath: graphics) // 明確
Strive for Fluent Usage(流暢な呼び出し方を目指す)
・呼び出しが文法的な英語のフレーズを形成するような、メソッド名と関数名を優先します。
/* 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”
/* NG */ x.insert(y, position: z) x.subViews(color: y) x.nounCapitalize()
それらの引数が呼び出しの意味の主要でない時、第一引数または第二引数の後では流暢さが落ちてもかまいません。:
AudioUnit.instantiate( with: description, options: [.inProcess], completionHandler: stopProgressBar)
・ファクトリメソッドの名前は"make"で始めましょう。例 x.makeIterator()
*・イニシャライザとファクトリメソッドの呼び出しは、第一引数を含まないフレーズを構成しましょう。 例x.makeWidget(cogCount:47)
例えば、これらの呼び出しで暗示されるフレーズには、第一引数は含まれません。
/* OK */ let foreground = Color(red: 32, green: 64, blue: 128) let newPart = factory.makeWidget(gears: 42, spindles: 14)
以下では、API作成者が第一引数で文法的連続性を作成しようとしました。
/* NG */ let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128) let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
実際には、このガイドラインと引数ラベルのガイドライン は、呼び出しが値の型変換を実行している場合を除いて、第一引数にラベルが付いていることを意味します 。
let rgbForeground = RGBColor(cmykForeground)
・関数とメソッドは副作用に応じた命名をしましょう。
∟副作用のないものは名詞句として読めなければなりません。例 x.distance(to: y)
、i.successor()
∟副作用のあるものは動詞句として読めるべきです。例 print(x)
、x.sort()
、x.append(y)
∟可変(mutating)/不変(nonmutating)メソッドのペアは一貫した命名をしましょう。可変メソッドは同じセマンティックスで不変型の変数を持つことが良くありますが、インスタンスをそこで更新するのではなく、新しい値を返すようにしましょう。
∟操作が動詞での表現が自然な場合、可変メソッドには動詞が不可欠であり、接尾辞"ed"または "ing"を付けて不変メソッドを命名します。
可変(Mutating) | 不変(Nonmutating) |
---|---|
x.sort() | z = x.sorted() |
x.append(y) | z = x.appending(y) |
∟動詞の過去分詞を使用して、不変型を命名するようにしましょう。 (通常は "ed"を追加します):
/// Reverses `self` in-place. /// in-placeでselfを逆順にします。 mutating func reverse() /// Returns a reversed copy of `self`. /// selfを逆順にしたコピーを返します。 func reversed() -> Self ... x.reverse() let y = x.reversed()
∟動詞に直接目的語があるため"ed"を文法的に追加しない場合に、動詞の現在分詞を使用して不変型の名前に "ing"を追加します。
/// Strips all the newlines from `self` /// selfから全ての改行を取り除きます。 mutating func stripNewlines() /// Returns a copy of `self` with all the newlines stripped. /// 全ての改行を取り除いたselfのコピーを返します。 func strippingNewlines() -> String ... s.stripNewlines() let oneLine = t.strippingNewlines()
∟操作が名詞での表現が自然な場合、不変メソッドには名詞を使用し、また可変に対する接頭辞"form"を適用します。
不変(Nonmutating) | 可変(Mutating) |
---|---|
x = y.union(z) | y.formUnion(z) |
j = c.successor(i) | c.formSuccessor(&i) |
・Booleanメソッドとプロパティの使用は、不変の呼び出しの場合、レシーバーに関するアサーション(表明)として読み取れる必要があります。例 x.isEmpty
、line1.intersects(line2)
・それが何であるかを示すプロトコルは名詞にしましょう。(例 Collection
)
・何が出来るかを示すプロトコルは接尾辞 able、ible、ing を使用した命名をしましょう。 (例 Equatable
、ProgressReporting
)
・その他の型、プロパティ、変数、定数の名前は名詞にしましょう。
Use Terminology Well(専門用語の良い使い方)
専門用語でのアート 名詞 - 特定の分野や職業で、正確で特別な意味を持つ語句。
・より一般的な言葉で意味が伝わる場合は、よくわからない専門用語を避けましょう。「皮膚(skin)」でも良い場合「表皮(epidermis)」とは言わないでください。専門用語はコミュニケーションに欠かせないツールですが、重要な意味が失われてしまわないようにする時だけ使用してください。
・専門用語を使うなら、正確な意味にこだわりましょう。
・より一般的な言葉ではなく、専門用語を使用する唯一の理由は、そうでなければ曖昧であるか不明瞭なものを正確に表現することです。したがって、APIはその意味に従って厳密にこの用語を使用すべきでしょう。
∟エキスパートをびっくりさせないようにしましょう:その用語に慣れ親しんだ人にとって、違う意味で使われていればびっくりして怒ってしまいます。
∟初心者をびっくりさせないようにしましょう:誰もがウェブ検索を行って、その用語の伝統的な意味を知ることができます。
・単語を略しないようにしましょう。略語、特に非標準的なものは、専門用語でのアートするに効果的でしょう。なぜなら、理解するには、略語を非省略形に正しく直すことに依存しているからです。
あなたが使用する省略形の意味は、Web検索で簡単に見つかるはずです。
・先例を受け入れましょう。既にある文化への適合性を無駄にしてまで、初心者向けに用語を最適化しないでください
初心者が List
の意味をより簡単に理解したとしても、 List
などの単純化された用語を使用するより、連続したデータ構造はArray
と名付けるほうが良いです。数学のような特定のプログラミング領域で、sin(x)
のような一般に先例のある用語は、verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)
のような説明フレーズよりもベターです。 この場合「先例の受け入れ」が「略語を避けるガイドライン」よりも優先であることに注意してください。完全な単語はsine(正弦)
ですがsin(x)
は何十年もの間、プログラマ、そして数世紀にわたって数学者の間で共通して使用されていました。
Conventions(慣例)
一般的慣例
・O(1)でないcomputed propertyは複雑さについてコメントを書きましょう。人々はよく、プロパティアクセスのメンタルモデルとしてstored propertyの認識を持っているため、大した計算を伴わないと仮定します。 その前提にそぐわない可能性があるときは、必ず警告してください。
・free functionよりメソッドとプロパティを使用しましょう。free functionは、特別な場合にのみ使用されます。
1.明確なselfが無いような場合:
min(x, y, z)
2.関数に縛りがなく汎用的な場合:
print(x)
3.関数構文が特定領域の表記法の一部である場合:
sin(x) // 数学でsinは一般的
・case規約を守りましょう。型名とプロトコル名はUpperCamelCaseです。それ以外はすべてlowerCamelCaseです。
アクロニム(例 NATO ナトー等)とイニシャリズム(例 FBI エフ・ビー・アイ)など頭字語(とうじご、略語)は、ふつうアメリカ英語ですべて大文字ですが、case規約に従って一様にup-もしくはdown-caseにすべきでしょう。 :
var utf8Bytes: [UTF8.CodeUnit] // 補足:イニシャリズム"UTF8"は変数名の先頭では小文字なのでutf8、型名は大文字なのでUTF8と記述 var isRepresentableAsASCII = true // 補足:アクロニム"ASCII"は変数名の先頭以外では大文字なのでASCIIと記述 var userSMTPServer: SecureSMTPServer // 補足:イニシャリズム"SMTP"は変数名の途中では大文字。型名の途中でもSMTPと記述。
・他の略語(アクロニム)は日常的な単語として扱われるべきでしょう。:
var radarDetector: RadarScanner // Radar(radio detection and rangingの略)レーダー、電波探知機 var enjoysScubaDiving = true // Scuba(self‐contained underwater breathing apparatus)スキューバ
・同じ基本的な意味を共有する場合、または異なるドメインで操作する場合、メソッドは基底名を共有できます
たとえば、メソッドが本質的に同じことを実行するため、以下のことが推奨されます。
/* OK */ extension Shape { /// Returns `true` iff `other` is within the area of `self`. /// other が self の範囲内にある場合は true を返します。 func contains(_ other: Point) -> Bool { ... } /// Returns `true` iff `other` is entirely within the area of `self`. /// other が完全に self の範囲内にある場合は "true"を返します。 func contains(_ other: Shape) -> Bool { ... } /// Returns `true` iff `other` is within the area of `self`. /// other が self の範囲内にある場合は true を返します。 func contains(_ other: LineSegment) -> Bool { ... } }
ジオメトリック型とコレクションは別々のドメインなので、これは同じプログラムでもうまくいきます:
/* OK */ extension Collection where Element : Equatable { /// Returns `true` iff `self` contains an element equal to /// `sought`. /// self に sought に等しい要素が含まれている場合は true を返します。 func contains(_ sought: Element) -> Bool { ... } }
しかし、これらのindex
メソッドは異なるセマンティクスを持ち、異なる名前を付ける必要があります。:
/* NG */ extension Database { /// Rebuilds the database's search index /// データベースの検索インデックスを再構築します。 func index() { ... } /// Returns the `n`th row in the given table. /// 与えられたテーブルの n 番目の行を返します。 func index(_ n: Int, inTable: TableID) -> TableRow { ... } }
最後に、型推論するときに曖昧さを引き起こすため「戻り値型のオーバーロード」を避けてください。
/* NG */ extension Box { /// Returns the `Int` stored in `self`, if any, and /// `nil` otherwise. /// self が格納されている場合は Int を、そうでない場合は nil を返します。 func value() -> Int? { ... } /// Returns the `String` stored in `self`, if any, and /// `nil` otherwise. /// self に格納されている String を返します。それ以外の場合は nil を返します。 func value() -> String? { ... } }
Parameters(引数)
func move(from start: Point, to end: Point)
・説明的なパラメータ名にしましょう。関数やメソッドを使用する時にパラメータ名は見えませんが、パラメータ名は重要な説明的役割を果たします。
ドキュメントを読みやすくするために、こんな命名をしてください。 たとえば、これらの名前はドキュメントを自然に読めるようにします。:
/* OK */ /// Return an `Array` containing the elements of `self` /// that satisfy `predicate`. /// predicateの条件を満たす self要素を含んだ Array を返します。 func filter(_ predicate: (Element) -> Bool) -> [Generator.Element] /// Replace the given `subRange` of elements with `newElements`. /// 指定された subRange の要素を newElements に置換します。 mutating func replaceRange(_ subRange: Range, with newElements: [E])
以下の例は、ドキュメンテーションが扱いづらく、文法的でもありません。
/* NG */ /// Return an `Array` containing the elements of `self` /// that satisfy `includedInResult`. /// containedInResult を満たす self要素を含んだ Array を返します。 func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element] /// Replace the range of elements indicated by `r` with /// the contents of `with`. /// r で指定された範囲の要素を with の内容に置換します。 mutating func replaceRange(_ r: Range, with: [E])
・一般的な利用を簡素化する時は、デフォルトパラメータを活用しましょう。 一般的に使用される値が1つの引数は、デフォルト引数の候補になります。
デフォルト引数は、無関係な情報を隠すことで可読性を向上させます。 例えば:
・パラメータリストの最後にデフォルトでパラメータを配置することを推奨します。デフォルトを持たないパラメータは、通常、メソッドのセマンティクスにとってより重要であり、メソッドが呼び出されるときに安定した初期の使用パターンを提供します。
/* NG */ let order = lastName.compare( royalFamilyName, options: [], range: nil, locale: nil)
とてもシンプルにできます。
/* OK */ let order = lastName.compare(royalFamilyName)
**・デフォルト引数は、APIを理解しようとする人に認知的負担をかけることが少ないため、一般的にはメソッドファミリを使用するよりも好ましい方法です。
(補足:メソッドファミリ・・・ARCによってオーナーシップが発生するものと解釈される特定の文字列の名前がついたメソッド。)
/* OK */ extension String { /// ...description... public func compare( _ other: String, options: CompareOptions = [], range: Range? = nil, locale: Locale? = nil ) -> Ordering }
上記はシンプルではないかもしれませんが、これよりかは遥かにシンプルでしょう。
/* NG */ 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()
は必ずしも同義ではありません。 例えば、これはほとんど同じ文書から小さな違いを見つけ出すような面倒なプロセスです。 デフォルト引数を使用したシングルメソッドを使用すると、プログラマーエクスペリエンスが大幅に向上します。
・引数リストの最後にデフォルトパラメータを設定しましょう。 デフォルトを持たないパラメータは、通常、メソッドのセマンティクスにとってより重要であるため、メソッドを呼び出して使用するときの安定したイニシャルパターンを提供します。
Argument Labels(引数ラベル)
func move(from start: Point, to end: Point) x.move(from: x, to: y)
・引数が有効に区別できない場合は、すべてのラベルを省略します。例 min(number1, number2)
、zip(sequence1, sequence2)
・値の型変換を実行するイニシャライザでは、最初の引数のラベルを省略します。例 Int64(someUInt32)
最初の引数は常に変換元でなければなりません。
extension String { // x を与えられた 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 { /// valueを持つインスタンスを作成します。 init(_ value: Int16) ← ワイドニング変換なのでラベル無し /// 最低32ビットの source を持つインスタンスを作成します。 init(truncating source: UInt64) /// valueToApproximateの最も近く表現可能な近似値を持つインスタンスを作成します。 init(saturating valueToApproximate: UInt64) }
値の型変換はモノモーフィズムであり、つまり、ソースの値の違いがすべて結果の値の違いになります。 たとえば、
Int8
からInt64
への変換では、すべてのInt8
値がInt64
値に変換できるため、値が保持されます。 しかし、他の方向への変換は、値を保持することができません。:Int64
はInt8
で表現できる値よりも多くの可能な値を持ちます。 Note:元の値を取得する能力は、変換が値の保存であるかどうかには関係しません。
・第一引数が前置詞句の一部を構成するときは、引数ラベルを付けましょう。引数ラベルは通常、前置詞で始まるべきです。例 x.removeBoxes(havingLength: 12)
最初の2つの引数がひとつの抽象化の一部を表すときは、例外が発生します。
/* NG */ 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)
このガイドラインは、第一引数が文法的なフレーズの一部を構成しない場合は、ラベルを持つ必要があることを意味します。
/* OK */ view.dismiss(animated: false) let text = words.split(maxSplits: 12) let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)
フレーズが正しい意味を伝えることが重要であることに注意してください。次は文法的であるが、間違ったことを表現しています。
/* NG */ view.dismiss(animated: false) let text = words.split(maxSplits: 12) let studentsByName = students.sorted(isOrderedBefore: Student.namePrecedes)
デフォルト値の引数は省略することができ、その場合は文法的なフレーズの一部を構成しないので、常にラベルを付ける必要があることにも注意してください。
・その他のすべての引数には、ラベルを付けます。
Special Instructions(特例)
・APIにはclosure引数とtupleメンバをラベルしましょう。.
これらの名前は説明力があり、ドキュメンテーションコメントから参照したり、tupleメンバへの表現力豊かなアクセスを提供します。
/// 最小限 requestedCapacity 要素に対して一意に参照される記憶域を保持します。 /// /// さらに多くの記憶域が必要な場合は、割り当てられる最大アライメントのバイト数に等しい byteCount で 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)
・オーバーロードセットの曖昧さを避けるため、制約のないポリモーフィズム(例 Any
、AnyObject
、および制約のない一般的なパラメータ)に注意しましょう。
たとえば、このオーバーロードセット。
/* NG */ struct Array { /// newElement を self.endIndex に挿入します。 public mutating func append(_ newElement: Element) /// newElements の内容を self.endIndex に順に挿入します。 public mutating func append(_ newElements: S) where S.Generator.Element == Element }
これらのメソッドはセマンティックファミリを形成し、第一引数型はとてもはっきりします。ただし、Element
がAny
の場合、1つのエレメントは一連のエレメントと同じ型を持つことができます。
/* NG */ var values: [Any] = [1, "a"] values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?
曖昧さを解消するには、2番目のオーバーロードをより明示的に指定します。
/* OK */ struct Array { /// newElement を self.endIndex に挿入します。 public mutating func append(_ newElement: Element) /// InewElements の内容を self.endIndex に順に挿入します。 public mutating func append(contentsOf newElements: S) where S.Generator.Element == Element }
新しい名前がドキュメンテーションコメントと、どうなふうによりマッチしたかに注目してください。 このケースでは、ドキュメンテーションコメントを書く行為が、実際のAPIの著者のissueへの着眼をもたらしました。
@snoozelagの感想
自分で訳してみると、けっこう発見や気づきが得られたように思います。後半は訳をすることに必死になって、要約は少し息切れしてしまってもいますが、原文に近い方が意味が取れやすい部分もあるかなとも思いました。ぎこちない部分には適宜修正更新を入れて行こうと思っています。
あと公式に、このような明確でわかりやすいAPI設計の指針が文書化されていることが大変ありがたいなと思いました。例えばJavaについて検索してしましたが、Java API Design Guidelines(個人の方の文書)といった感じでした。Swiftのサポート具合って最高かな、と思いました。
個人的な動機としては、英語が得意な訳でもないし、メソッド名に前置詞が付くパターン、ラベルに前置詞がつくパターン、メソッド名に型が付くパターン、引数名には役割を記述するなどなど、色々あるけど整理が付かなかった部分がきちんと資料の中で言及されていてことが嬉しかったです。
最初からよりよいメソッドの記述ができることによって、修正や間違いに気づきやすくして、コーディングのスピードアップも期待出来ると思いますね。
APIを記述する上で迷いやすい部分のプラクティスを学び、より良い記述を目指したいですね^^。
追記
ちなみにObjective-CのAPI Design Guidelines 相当の資料になるようです。
https://developer.apple.com/jp/documentation/CodingGuidelines.pdf
Introduction to Coding Guidelines for Cocoa
https://t.co/fdezs0OXIaに API Design Guidelines あるけど、それ相当のObjective-Cの資料あった。今まで見過ごしていたんだな。/ Cocoa向けコーディングガイドライン https://t.co/n5ZMEfwiUJ
— Teruto Yamasaki ☕️ (@snoozelag) 2017年7月20日