https://www.youtube.com/watch?v=RSK1LEtxzuE
今回は API Design Guidelines
の中から前回の続き、API の 命名規則
について見ていきます。いよいよ本格的なガイドラインに入っていく感じがしますね。その中のまずは 明瞭な使用を促していく
ための指針について眺めていきます。どうぞよろしくお願いいたしますね。
———————————————————————————— 熊谷さんのやさしい Swift 勉強会 #13
00:00 開始 00:25 命名規則 01:24 必要な全ての語句を含める 07:49 語弊をなくして安定性を高める 10:46 必要のない語は省く 14:06 過不足なく表現する例 18:06 レシーバーが扱う主体は省略 21:01 アメリカ英語として自然に読める API を 23:31 制約が弱い型の引数はラベルで補足 28:43 変数・引数・関連型の名前 29:25 制約ではなく役割に応じて命名 36:16 役割に応じた変数名を 38:52 関連型とプロトコルの関連 39:56 関連型 43:03 名前と役割が似通っていたとき 46:26 接尾字に Protocol を持つプロトコル 48:48 Error プロトコルの命名について 52:52 次回の展望 ————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #13
はい、じゃあ今日は前回の続き、APIデザインガイドラインについて見ていきましょう。命名規則ですね。いよいよAPIデザインガイドラインの本題に入っていきます。APIデザインガイドラインは、命名規則が確かに割合として多いんですけど、他にも表現方法や特別な場面についても規定されています。これがガイドラインの構成ですね。また、前回お話ししたドキュメントコメントについても、このガイドラインに含まれています。
今日はおそらく命名規則について急げば全部終わるかもしれませんが、ゆっくり進めますので、どれぐらい進むかはわかりません。ただ、ゆっくりやれるのも週に3回の勉強会のおかげですね。
では見ていきましょう。まず命名規則の中でも「明瞭な使用」というセクションがあります。ここは英語の訳がうまくいかないところもありますが、例えば「流暢な表現」や「明瞭に捉えられるAPI」といったことに関して規定しています。
まずは公式ドキュメントを見ていくとわかりやすいと思うので、スライドを進めていきましょう。これはAPIデザインガイドラインの中に書いてあるサンプルコードをそのまま引っ張ってきて、日本語訳しただけのページです。
Swiftでコードを書く際に大事にすることとして、「必要なすべての語句を含めて表現する」ということが挙げられています。これはなかなか新鮮です。私はC言語やC++、Rなどをやっていましたが、その言語と比べると「必要な語句を含める」というのは斬新な気がします。オブジェクティブCもやっていましたが、あの言語ではメソッドが非常に長くなることがありましたね。
ここで例に挙げられているのは Employees.remove(at: x)
というコードです。もっと明確に書くと at: index
などになるでしょうが、とりあえずこの例ではx
となっています。このコードは、Employees
という配列のx
で指定した場所の要素を取り除くということを明確に表現しています。
一方で、必要な語句が含まれていない例として Employees.remove(x)
があります。これは以前の言語では一般的だった表現方法ですが、Swiftではこのように曖昧な表現は避けるべきです。このx
が何を指しているのかが不明確ですし、操作内容もわかりにくいです。
具体的な例を挙げると、x
が人の名前ならその人を削除するのか、x
がインデックス番号ならその場所の要素を削除するのか、といった混乱が生じます。このように、曖昧さを避けて明瞭に書くことが重要です。
1行目のコード Employees.remove(at: x)
を見ると、配列のx
番目の要素を削除するというのが誰でも誤解なく読めると思います。もちろん配列の基礎知識が必要ですが、これはソフトウェア開発の文化として基礎的な知識となります。つまり、配列からx
番目の要素を削除するというふうに非常に自然に読めるわけです。これができるのも、ちゃんと必要なすべての語句が含まれているからです。 なので、それを大事にしていきましょうというルールが明記されている形になっているのかな。
なるほど、コメントをいただいたので面白い話をします。例えば、数字が入るから「この人数を消す」といった表現になるんですね。たとえば「5人消す」みたいな。ただ、そういった語がないために考えていくと、裏を読んだり、いろいろな発想が広がっていくということです。語句が足りないと、それが妄想を広げるきっかけになるんです。
その足りない語句を埋めるのは人それぞれになってくるので、そこで解釈の違いが生じてきます。コードを見たときに、そのコードが何を意味しているのかを考えなければならないため、頭のリソースの消費が大きくなります。そうすると、他のもっと気をつけなければならない大事な部分にリソースが回せないことがあります。全体的に見ても、安全性が低下する可能性があると思います。
これが信憑性があるかどうかは分かりませんが、以前に聞いた話で「人は1日に考えられること、答えを出すことが限られている」という話を聞いたことがあります。問いかけ(自分に対してでも他人に対してでも)や、何かを考えて結論を出さなければならないときに「このRemove
は何を意味しているのだろう」といった疑問も含めて、1日にできる量が限られているという主張です。これはすごく納得しました。
その審議はさておき、考える負担を減らすことは思った以上に大事なことです。したがって、そういった面でもこのデザインガイドラインが大きな効果を発揮していると言えます。コードの書き方が変わってくるかもしれませんね。
また、必要のある語句を含めるとともに、必要のない語句を省くことも大事です。過不足なく表現するということですね。それぞれの場所において、それぞれの語がしっかり意味を持って存在していることが重要です。そして、役目を持たない語はなくしていくという考え方です。
これが前回のAPIデザインガイドラインの話にも出てきた「短くすることが目的ではないが、必然的に短くなる」という部分です。一語一語は的確に略さず書きますが、要らない語は削除するのです。
具体例を挙げると、型の情報を含むmutating func removeElement
で「エレメントを消す」みたいに書いてあるけれど、この時の「エレメント」が余計な語句かもしれません。これは感覚的に慣れてこないと余計かどうかは分からないと思います。しかし、上の2行と下の2行を比べてみると、なくてもいいよねと思うことがあるかもしれません。
例えばAllViews removeElement cancelButton
がAllViews remove cancelButton
と通じますよね。大きな違いはat
があるかどうかです。例えばEmployees remove at x
というのがありましたが、ここも大事なポイントです。AllViews remove at cancelButton
というのはありえないので、at
があるかないかで表現の意味合いが大きく変わります。
これはプレイグラウンドで書いた方が分かりやすいかもしれません。例えば、Remove Element
ではなく、Remove Object
やRemove Items
などとして、もう少し具体的にしてみると良いでしょう。
mutating func remove(element: String) {
// Implementation here
}
mutating func remove(at index: Int) {
// Implementation here
}
このように、型やインターフェースについても注意深く考えることで、自然なAPIを設計することができますね。 こうすると remove
ここがエレメントになるわけですけど、こうやって比べていくとわざわざエレメントって書く必要は感じられないかなと思います。ちゃんと言語化できてないなと感じますが、removeElement
が余計なので、こういうのは消しましょうというルールになっています。
もう一つ大事なところとして、ここはどうなんだっていうのが人によってはあると思うんですよ。remove(at:)
と removeElement
、これもこれはこれで明瞭な感じしませんかね。人によってすると思いますし、慣れるまではこれがラベル名としてのエレメントがいるのかいらないのかを気にせずに書かないで済ませます。しかし、いざきれいなAPIを書こうと意気込み始めて考えると、このエレメントがいるのかいらないのか分かんなくなることもあると思います。
このあたりは人の考え方としては主体守護かな、要はレシーバーとか言われたりしますが、これが主体となっていると考えます。これがまず主体としてあるので、メインで扱うものはわざわざ言わなくてもいいよねっていう考え方をするようです。だからレシーバー(主体)が何を根本的に扱っているかを意識して、例えばバスケットは内容をメインで扱っているので、それについてはわざわざ語らなくていいからラベル名を消すという考え方があります。
コメントでもいいお話がありますが、APIデザインするときにはアメリカ英語として自然に読めるというのも大事なポイントになってきます。それが崩れてしまうというのも大きな判断材料になります。例えば remove(apple: "apple")
といった感じですね。"apple"
とわざわざ書かなくてもいいです。
これがバスケットに対して remove("apple")
と remove(at: 1)
という感じの読み方をする場合と、remove(at index: 1)
という書き方も間違いではないです。そのため、リムーブの際に明確で、かつ自然な表現を心がけるべきです。
逆に分かりにくくなるのが removeElement("apple")
といった表現です。英語例文では不自然に感じる場合があります。このように、エレメントをつけると表現がまどろっこしくなってきます。
API作成時に重要なのは、英文化された文が自然に組み立てられるかどうか、という点も大事なポイントになります。
remove(at index: Int)
という表現方法もあり、このようにインデックスをクリアに示すことで誤解のない表現になることがわかります。特に、受け取る型が汎用的であるときには、インデックスなどの明確なラベルが必要になることがあります。
このように、言語的な自然さとAPIの一貫性を保つために必要な語を使うことが大切であり、それぞれの理想を追求した形で形作っていければ良いのかなと感じます。
ということで、必要な語を使い、不要な語を省くことでAPIデザインを改善していくという話でした。 このあたりがなかなか意外と難しい点です。特に、英語が苦手な自分のような人間にとっては難しいと感じます。しかし、逆に英語が得意な人にとっては、特に問題なく書けるのではないかと思います。そういった人は、分からなくなったら英文で書いてみると視点が明らかになってくるかもしれません。これが、前回お話ししたドキュメントコメントの話にもつながります。英語としてさらさらっと書けるとOKという感じです。
もう少し具体的な部分を見ていくと、変数や引数、関連型などの名前は役割に応じてつけましょうという規定があります。関連型というのは、プロトコルで associatedtype
と記述したものです。名前付けについては、ここで一番下の正しい例がわかりやすいかと思います。たとえば、ContentView
という名前付けが良い例です。
「制約ではなく役割に応じて」ということですが、制約とは一般的には型のことを指します。特に64ビット整数の場合は Int
が制約です。しかし、その数字が何なのか、たとえば「ラッキーナンバー」として表現するときの役割を考えます。associatedtype
の例がわかりやすいと思いますが、悪い例としては「Viewに属する」のような曖昧な名前です。「View」に準拠している ViewType
を取るというのは、これだけでは全然意味がわかりません。全体の事情を知っていれば理解できるかもしれませんが、それだけでは伝わりません。
この点が重要です。下の例にある ContentView
は、「このViewにはコンテンツが表示されるんだ」という意図が直感的に伝わります。役割に応じた名前をつけることで、意図が相手に伝わるのです。
次に、関数やメソッドの引数についても同様の考え方が適用されます。例えば、restock(from: widgetFactory)
よりも restock(from: supplier)
と書いた方が、意図が明確に伝わります。「サプライヤーからリストックする」ほうが、「ビジェットファクトリーからリストックする」よりも具体的でわかりやすいのです。
確かに、慣れてくると from: supplier
と自然に書くようになりますが、最初は難しいかもしれません。ビジェットファクトリーはビジェットのインスタンスを生成するものであり、これ自体は間違っていませんが、「サプライヤー」という存在からリストックすることで、引数に渡すものが明確にサプライヤーを表すものになります。それ以外のものを渡そうとは思わなくなるでしょう。
ですので、型がビジェットファクトリーであるなら、インスタンスはビジェットファクトリーであるのが当然です。過不足なく表現するためにも、「リストック フロム ビジェットファクトリー」というのは冗長です。インスタンス名の方をもっと具体的な名前にすることで、APIが明瞭になります。
こういう発想が生まれてくると、より良い名前が付けられるようになります。油断していると普通に型名を使ってしまいがちですが、一歩踏み込んだ表現を狙うことで、意図が伝わりやすいきれいな表現になります。これらの点は一度に習得できるものではなく、1ヶ月、2ヶ月、あるいは1年とじっくり考えて身につけていくものです。日頃からこういった点を頭の片隅に置きながらコードを書くことで、良い結果が得られると思います。 多分そうしていくことでセンスが磨かれていくのかなという気がするので、ぜひこういったところも楽しみどころとしてやってほしいなと思います。
あと、最近だとそんなにおろそかにされないかなと思うんですけど、変数名ですね。ここに書かれている var string
っていうのと var greeting
っていうのね。これも結構大事なところです。先ほどのウィジェットファクトリーとも同じようなポイントになってくるところですが、var string
と書くと、そのストリングが何を意味しているのか分からないので、間違った使い方をするかもしれないし、うっかり変なところに渡したりすることがあります。また、こういう var
として宣言したものだと、IDを知ってしまったり、全然違う用途で使ってしまったりと、最近のプログラミングでは良くないとされていることです。これをちゃんと役割に応じて greeting
というふうに名前を付けてあげることによって、間違ったものを入れないという抑止力になります。
greeting = melon
なんて書きたい人はそうそういないと思います。変数名だけで抑止力が働くということです。さらに、例えば basket.addGreeting
なんて書きたくないですよね。しかし、basket.addString
だったら、ストリングにメロンが入っていたら書いてしまうかもしれません。でも、グリーティングにメロンが入っていても、バスケットにグリーティングは追加しないといった感じで、より適切なコードの組み立てに誘導できます。自分ではなく他の誰かをちゃんと導いていけるという大事なポイントになるので、そういったところも意識して、型の制約ではなく役割に応じて名前を付けていくと大事です。
ここまで、結構シンプルな例を挙げてきましたが、少し例外を含む場合もあります。これがまた難しい点で、型の制約と結びつきすぎている関連型の場合ですね。例外というと大げさかもしれませんが、とにかく役割が型の制約そのものだったりするといったことが、特にプロトコルのアソシエイティブタイプで行われたりします。こうした時には、型の方に IteratorProtocol
というふうに名前を付けてあげるのが API デザインガイドラインの指針です。
アソシエイティブタイプが分からない場合は少し難しいかなと思います。アソシエイティブタイプは、例えばジェネリクスプログラミングで使うものです。例えばバスケットの例を使いましょう。バスケットから値を取り出すときに、あらかじめ String
や Protocol
でなくてもいいですが、例えば Collection
というプロトコル標準ライブラリがあります。これに準拠しているのが Array
です。Array
は Int
型の配列や String
型の配列など、要はエレメントの型が自由に選べます。この時に使われているのがアソシエイティブタイプです。他にも Set
などがあり、Dictionary
も Collection
です。キーとバリューが設定されているので、どんな値をコレクションしているか選べるというわけです。
これらは Collection
という統一のプロトコルによって表現されており、汎用的なコードの書き方ができるようになっています。どんな値をコレクションしているか、配列だったら一つですが、辞書だったらキーとバリューの組み合わせといった形で自由に選べます。これについてはプロトコルの話に入ったら詳しく説明しようと思います。
また、シーケンスは値が連続的に繋がっていることを表現するプロトコルで、順番に存在しているため、先頭から順に引き出していくことができます。ここで Iterator
というものが出てきます。これによって値を順番に引き出す役割を持った型が内包されており、どんな Iterator
でも共通インターフェースとして使えますが、それで色々な事情に応じて異なるイテレータが存在します。
そのため、プロトコルに名前を付ける際には、例えば Iterator
という型については IteratorProtocol
といったふうに語尾にプロトコルをつけるという約束事があります。 これ、なかなか難しいところなんです。なぜここに制約をつけたのか、ルールをつけたのかと思うんですが、とにかくプロトコルの名前を調整しないといけません。これが他の人が作ったプロトコルだとどうにもならないんですよね。そういう時には「これでいいのかな」と思う部分もありますが、じっくり考えていくと、プロトコルを定義した時点でプロトコル名自身が役割に密接に関係していることに気づくことがあります。そのため、先手を打って「何々プロトコル」という名前をつけておくのが大事なルールになってきます。
話していてもよく分からない部分もありますが、昔はイテレータータイプやアソシエイティブタイプのような名前の付け方をしていました。例えば、イテレータータイプ
やアソシエイティブタイプ
といった具合です。その方が自然な気もするのですが、とりあえずどのようなプロトコル名があるのか見てみましょう。
例えば、StringProtocol
やIteratorProtocol
、LazySequenceProtocol
などです。これらの名前は、とりあえずAPIデザインガイドラインに書かれているイメージに近いものです。もしプロトコルに「プロトコル」が付いていなかったら、使う側がなんとかすると思いますので、致命的ではないかもしれませんが、流暢な表現や自然な感じにするためにも、このルール、つまり役割と名前が一致しがちなプロトコルには「プロトコル」を付けるのがちょっとオシャレな感じで、APIデザインガイドラインに則っていますよというアピールになると思います。
特に知っておくと良い点として、これはアソシエイティブタイプに関連する話ではないのですが、「エラー」を「フェイラー」と表現するのは悪くないなと思います。しかし、SwiftのエラープロトコルはError
という名前です。例えばエンコーダーがエラーを返すときに、Error
プロトコルを使うことがありますが、これがちょっと曖昧に感じることがあります。コンバージョンエラー
のように名前を付けたりもしますが、エラープロトコルとアソシエイティブタイプエラーが被ることもありますね。
プロトコル命名のルールに沿ってError
という名前が適切かどうか、これはまだ自分の中で消化できていないところもありますが、誰かが明確に「プロトコルにわざわざプロトコル
と付けなくても良い」と言える人がいるのか気になります。
例えば、「何かができる」というプロトコルを作る時にその機能を表す動詞の「プロトコル」を付けるのが一般的ですし、どうしようもなく名詞で命名する時には「何々プロトコル」とサフィックスを付けるとトラブルは少ないでしょう。
今回の勉強会でも出てきたKeyedEncodingContainerProtocol
などもその一例ですね。名詞のプロトコルにはサフィックスを付けた方が良いということもあるでしょう。この辺りの関係についてもう少し探求し、自分なりの解釈を見つけるのが面白いポイントになると思います。
それでは時間になりましたので、もう少し明確な仕様を促すルールやポイントについては次回お話しします。これで今日の勉強会は終了です。お疲れ様でした。