https://www.youtube.com/watch?v=xDznRwROXms
今回はこれまでに見てきた Swift API Design Guidelines にある「表現方法」の残りの部分を眺めていきます。そのあとは特別な状況における規則を確認して、これでガイドラインをひととおり眺め終わりそうです。今回で終わりまでたどり着きそうな気配もしますけど、終わらなかったら続きは次回で、急がずじっくり眺めていきますね。
—————————————————————————— 熊谷さんのやさしい Swift 勉強会 #22
00:00 開始 00:15 前回のおさらい 00:40 前置詞句 05:34 前置詞句を構成しない最初の引数 07:00 ベース名 08:59 主体を扱うときのベース名 10:07 ベース名の付け方のおさらい 11:27 オプション的な引数の扱い 13:52 それ以外の引数 15:31 既定値をもつ引数 16:47 名前付けを行うときの心持ち 18:25 特記事項 19:07 タプルに対するラベル付け 24:28 複数の戻り値 26:20 ラベル付きタプルにラベルなしタプルを代入 29:49 タプルの要素数に関する制限 32:51 タプルならではの特徴 36:00 API 定義内におけるクロージャーの引数 42:16 制約のないポリモーフィズムに注意 43:42 ポリモーフィズム 45:40 要素の表現範囲が広い配列 51:54 クロージング ——————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #22
引き続き、APIデザインガイドラインについて眺めていこうと思います。今画面に映っているのは、前回最後にお話しした全知識(preposition)の部分ですね。前回はプレイグラウンドを使わずに話をしていましたが、やはり何か実際に書いたほうがイメージが膨らみやすいと思うので、軽くここから触れていこうかなと思います。
全知識の表現において大事なポイントとしては、基本的に全知識は引数ラベルに入れるということです。具体的なコードを見たほうが分かりやすいですね。例えば、「move to」が良さそうなので、それを例にしましょうか。
ある直線上でその直線上をどこまで移動させたいか、といったときの話です。例えば「Localization」という名前があるとします。これで例えば、水平方向のx座標にしようかな。そうすると「Line」があって、ある線がLocalizationを持っていて、これが移動する手段を振る舞いとして持っていたとき、以下のように書いたとしましょう。
func moveTo(x: Double) {}
これで「offset」が良いかな、いや「distance」か…。いや、ディスティネーション(destination)かな。まあ何でもいいですが、「move to 〜」で両方を持たせるとします。
func moveTo(x: Double, y: Double) {}
こういう風に持たせるときに、Swiftのガイドライン的には、このような書き方ではなく、全知詞で構成されている名前の部分をラベルとして表現しましょうというガイドラインです。しかし、たとえばこれが二次元だったとき、「move to x」と「move to y」と書くと見た目がアンバランスになります。そこで、この場合はベース名に「to」を持っていきましょう、というのが基本ガイドラインです。
この辺りはガイドラインとして理解して、あとは賛同するか否かという程度の話ですね。前回のガイドラインのお話でも、このように説明していました。他にも、例えば「removeBoxes」の例であれば、
func removeBoxes(having: String) {}
のように、全知詞をラベルに入れます。次に「fadeFrom」の場合も、ラベルに「from」を入れずに以下のように書きます。
func fadeFrom(view: View) {}
これらのガイドラインに基づいて、可能な限り読みやすいコードを目指しましょう。
さて、次の例ですが「dismissAnimated」のようなものですね。こういった場合、ガイドラインでは文法的な区を表現しないときに、そのままラベルにします。
func dismiss(animated: Bool) {}
これにより、何を非表示にするのかが明確になります。例えば、「view.dismiss(false)」では何を隠すのか曖昧になりますが、ラベルを使うことで明確になります。
さらに、文の流れ的に説明する場合はベース名に入れます。例えば「addSubView」では、以下のように書きます。
func addSubView(_ view: View) {}
こうすることで、サブビューを追加するアクションが分かりやすくなります。このように、ガイドラインに従ってコードを書いていくと、より読みやすく、意図が明確なコードになります。
以上、APIデザインガイドラインについての説明を続けてきましたが、特にObjective-Cの文化的な背景にも関連しています。どこまでラベルに入れるべきか、どこまでベース名に入れるべきか、という議論が多くあるようですが、Swiftのガイドラインに従うことで、より読みやすく、メンテナンスしやすいコードが書けるようになります。
では、次に進みましょう。 今、選択した32行目から34行目、この辺りの書き方がガイドライン的には正解ということになりますね。今、ビューコントローラーとして定義しましたが、おさらいとして、例えばこれがストラクトでビューズみたいなビューのコレクション的な型を規定したとします。プロトコルで説明しておきますが、実装はしません。このようにビューのコレクションを扱う場合、主体がビューであるときには、ガイドライン的にはadd
というふうに目的語を省略しても通じるよ、というガイドラインがありましたね。
この辺りをいろいろ使い分けて、APIをデザインしていくという感じです。外部に対して意味を持つ場合や、それが主体である場合、プロパティ名やメソッド名からも取り除くよ、という話を今までのガイドラインの中でしてきたはずです。すらすらと思い出せる人は、この動画を何度も見た人でしょう。こうしたガイドラインを一度に理解するのは難しいものですからね。
今、表示されているsplit
もそうです。わざわざ説明する必要もないかもしれませんが、split(12)
というふうな書き方だと、"12が何を意味するのか"がわかりません。"12を分割するのか"というような意味不明な発想をしてしまう可能性があります。だから、maxSplits: 12
とちゃんと書かれていると、その12がどういう意味かが一目で分かります。とても良い例だと思います。
ちなみに、maxSplits
のような補足的なパラメータは、人によって感じ方が異なります。経験や価値観に左右されるでしょう。私の感覚では、maxSplits
はオプション的な、あってもなくても良いけれどカスタマイズのために存在するものです。ガイドライン的には、文法的な句を構成しない語句として存在すること自体に問題はない、というルールもあります。
こうしたガイドラインの中で、ラベルで説明するというルールと組み合わさってAPIがデザインされていますね。「全知識」という用語も出てきましたが、それがどう他の部分と関連しているのかを頭に描きながら学んでいくと、ガイドラインが身につきやすいと思います。図式化するのが得意な人がいたら、APIデザインガイドラインを図解にしてみると、有意義かもしれません。
さて、次に進みましょう。それ以外の引数についてです。ゆっくり読んでいると、それ以外って何だっけ?と混乱するかもしれませんが、引数の表現方法についていろいろな説明をこれまでできたと思います。例えば、全知識や変換イニシャライザーの話をしました。
引数を区別する必要がないときには、基本的にはラベルをつけましょうというガイドラインがあります。ラベルがあることで補足情報が伝わりますし、ガイドラインに明るい人や具体的な感覚を持っている人が「あ、このラベルいらないんじゃない?」と判断できます。迷ったときには、ラベルをつけておけば問題ないでしょう。
また、オプション的な引数、例えばmaxSplits
のように規定値を持つ引数も、文法的な句を構成しないため、ガイドラインではラベルをつけなくてもよいとされています。しかし、ラベルをつけておくことで他の部分との調和がとれ、より明確になります。
こうして振り返ってみると、引数の表現方法と命名規則が再確認できたかと思います。この知識を元に、実践することで感覚的に身についていくでしょう。理論的に理解していても、実際にやってみると難しいことが多いですが、見真似でやっていくうちに知識が身についてくると思います。 あと、チーム開発においては、どこまで重要かっていうのが自分が苦手なところで、あまり縁のなかったところで分からないところではありますけど、極端にめちゃくちゃな表現さえしなければ、綺麗なAPIさえ書ければ、おおよそ相手に意思が伝わるかなっていう気持ちでやっています。APIデザインに関しては、少々の間違いがあってもそこまで深刻な問題は招かないんじゃないかなという気がするので、お気軽にやっていったらいいかなと思っています。もちろん、詳しい人を苛立たせるとか、いろいろな弊害はあるかもしれませんが。
じゃあ、次に行きますよ。APIデザインガイドラインの最後のセクションにあります。今までいろいろとドキュメントの書き方とか名前の付け方とか表現の仕方について見てきましたが、最後は特定の場面における特別な決まり事です。特にカテゴライズはしていないですが、いくつか存在しているので、それを紹介します。
まずはタプルの要素についてです。タプルの要素には各要素にラベルをつけるというのが基本的なガイドラインの推奨事項です。タプルにラベルをつけることによって過読性が非常に高くなります。以前、この勉強会でもタプルの話をしたときに出てきた内容ですが、実際にラベルの有無で雰囲気の違いを見ていただいたと思います。このあたりがガイドラインに明記されていますので、よほど必要がない場合でなければラベルをつけることが大事なポイントかなと思います。
ここでは、戻り値にラベルをつける例を紹介します。引数はタプルを取っていないので、このようなコード例になります。例えば、関数があって、ボディの中で何かの戻り値を適当に返すとします。この場合、ラベルをつけると非常に分かりやすいコードが書けるというのを実際に見てみましょう。
func example() -> (label1: Int, label2: String) {
let result = (label1: 42, label2: "example")
return result
}
戻り値を受け取って使うとき、ラベルがあることによって、例えば result.label1
や result.label2
というように表現できます。これがラベル名がなかった場合、0
や 1
といったインデックスで表現することになり、「0は何だっけ?」というようにコードの意図が分かりにくくなってしまいます。ラベル名があることで、このコードを見るだけで理解できる表現力の高さが大事なポイントで、ガイドラインでも推奨されています。
昔はこういう表現ができなかった頃にはコメントが大活躍していましたよね。コメントで補足しなければならないことが多かったですが、ラベル名があることでその必要が減り、適切にコードを理解しやすくなります。こういった点から見ても、ラベル名を含む書き方が推奨されています。 これがマルチプルリターンタイプです。Swiftでは先進的な技術としてラベル付きタプルというものがあります。このラベル付きタプルはとても大事なポイントで、それが導入されたことで戻り値を複数返し、かつ意味を添えて返すことができるようになりました。そう考えると、これはとても偉大な発明だと思います。
ところで、ラベルが付いているタプルに対してラベルが付いていないタプルを入れることができますよね。これは好ましいと思います。コードボリュームがこれぐらいの規模なら、上を見ればわかるので問題ない気もしますが、複雑な条件が絡み合って値が決まるような場合には、ちゃんとリターンの部分でラベルを書いた方が親切かもしれません。
最終的にはラベルを書く方が親切だと分かっていても、省略して推論させたい気もします。特に戻り値を返す段階でラベルが何だったのかを忘れてしまうことがあります。やはりタプルよりも構造体の方が保管が効いて書きやすいとは思います。タプルの用法や量は非常に難しい気がします。
構造体だと自動的に保管が効くので書きやすいですが、Swiftのコンパイラがタプルのリターンを認識して保管してくれると非常に便利ですよね。Apple的には、タプルは便利ですが、あまり多用しすぎるのは良くないという立場です。あくまで軽量な用途に使うべきだということです。
そもそも、タプルは7つまでの要素しか持てなかったはずです。8個入れるとコンパイルエラーが発生します。例えば以下のようにタプルを定義していくと、7つの要素を持たせたところでエラーが出るでしょう。
let a = (1, 2, 3, 4, 5, 6, 7)
これがコンパイルエラーを引き起こす可能性があります。実際に試してみると、プレイグラウンドでも同じ結果が得られるはずです。ただし、タプル自体には良さがあり、パターンマッチングなどの活用が可能です。構造体では代用が効かない場合もあります。
タプルと構造体の特徴は似ていますが、微妙に異なる部分も多いです。タプルはバラバラに見て扱うことができる一方、構造体は一つのまとまりとして強い概念があります。タプルに関しては、また別の機会により詳しくお話ししようと思います。 そういったところが大きな違いとして現れてくるのと、タプルは複数の値をまとめるという特徴を持っており、関数のパラメータを表現する際に使用されています。実際に内部的にはタプルとして扱われており、戻り値もタプルを使用できるし、引数も同様にタプルで表現できます。文法的には全く同じ表現で、引数として書くときも同じ形式を取るわけです。実際にタプルとして即席にまとめて渡す形になっているのです。
タプルの使い道については、好きな人はどんどん使っていくでしょうが、あまり興味がないと構造体に行きがちな気がします。データ表現として捉えたときには、単純なデータ表現として構造体が使われることが多いですが、タプルにも独自のメリットがあるので、その点を考慮すると面白い発見があるかもしれません。タプルの話はまた別の機会に詳しくお話ししましょう。
さて、ここで次に進みましょう。今回の資料を整理していて、見落としていたガイドラインを発見しました。これについては個人的にはとても興味深いポイントです。人によるかもしれませんが、このクロージャーの引数のラベル付けについてのガイドラインです。ラベルを用途で説明付けするというルールがあります。
以前、Swiftに関する勉強会でタプルの話をしたときにも触れましたが、クロージャーでは基本的にラベル名が付けられません。ラベルを付けられないことがタプルの良さを活かせないポイントの一つとして挙げられます。例えば、関数型の変数を引数に取るときにラベルが付けられないわけです。
例を挙げると、map
関数でクロージャーを渡すときに、何の要素を取るのかという情報がラベルなしでは分かりづらいです。定義をたどってもコメントがなければ情報が不足するため、ラベルが欲しくなります。しかし、タプルに名前を付けるとエラーが出ます。ここでXcodeのIssueとしてラベルの省略を勧められますが、ドキュメントやコードを読む人にとっては説明が必要です。
APIガイドラインには、エレメント
のように説明を入れることでドキュメント的にも補足できるようにしましょうというルールがあります。このようにしておけば、関数の定義やQuick Helpの表示にも反映されます。
次に進みましょう。これが今回のAPIデザインガイドラインの最後のポイントであり、最も理解しにくい部分だと思います。制約のないポリモーフィズムに注意を払いましょうというガイドラインです。このルールは一見理解しづらいですが、型でしっかり制約をつけているつもりでも、メソッドをオーバーロードしたときに誤解を招く可能性があるという問題です。
特に注意が必要なのは、制約の少ないパラメータです。例えば、Any
やAnyObject
、NS系のオブジェクトなどが挙げられます。これらの制約の少ないパラメータは、思わぬ誤解を招くことがあります。この点が非常に重要なポイントです。 ポリモフィズムという概念について、そもそも何かを理解しきれていない部分もありますが、オブジェクト指向で有名な考え方です。クラスがあって、ベースクラスがあり、それをサブクラスとして継承するような形で表現されます。ポリモフィズムを日本語に訳すと「多様性」という意味で、特定の型にとらわれずに色々な表現をするというオブジェクト指向のサブタイピングといったものです。これが一つのポリモフィズムの形です。
その他にも、関数の多重定義自体もポリモフィズムに含まれます。例えば、オーバーロード(多重定義)というのは、同じラベル名、同じ署名でありながらも、引数の型が異なることによっていろんな機能を提供する方法です。これをアドホック多態と呼びます。
また、ジェネリクスプログラミングのように、どんな型でもいいけれど比較可能であるものなら何でも取る、といったパラメーター多態もポリモフィズムの一種です。このように、パラメーターはこういう性質を持つものですよ、といった表現をする際には、パラメーター多態を利用します。
特に、パラメーター多態、ジェネリクスプログラミング、そしてサブタイピングを考える際、一番問題が起こりがちなのはジェネリクスの部分のように感じます。具体的な例として配列を用いるときの話をしてみましょう。
例えば、配列があって、変数 array
があって、要素が入っているとしましょう。これを要素が Int
型の配列として捉えるとき、この array
に対して値を追加する際(append するとき)、例えばエクステンションを使って Array
にメソッドを追加することを考えます。インサート
というメソッドを定義したとします。これは 1 個の要素を取るメソッドです。
そして次に、もっと一括で値をインサートしたい場合も出てくるでしょう。例えば、Element
型の配列を取るといったものも定義したくなることがあります。このようにすると、配列 array
は次のようにして各要素をループして追加していくことになります。
for element in elements {
array.append(element)
}
しかし、この場合、例えば array
が Any
型になった場合どうなるのでしょうか。Any
型に変えたことで、1つの要素なのか複数の要素なのかが曖昧になってしまいます。これは API デザインガイドライン的には誤解を招く API としてよろしくないとされています。この問題を避けるためには、きちんとラベルを付けて説明してあげることが大切です。
最後に、この API デザインガイドラインに従って制約のないポリモーフィズムに注意を払うことが重要です。これを頭に入れた上で、日々の開発に役立ててください。
さて、これで時間がちょうどよくなりまして、API デザインガイドラインの部分は全て終わりました。次回からは「The Swift Programming Language」のコードを見ていこうかなと思います。長丁場でしたが、これで完璧ですね。気になる部分があれば何度も見返して身につけてください。では今日はこれで終わりにします。お疲れ様でした。