https://www.youtube.com/watch?v=4vVkg4ByafE
今回は Swift API Design Guidelines の中から、メソッド名の命名ガイドラインを中心に眺めていきます。副作用の有無に応じた名前の付け方、戻り値が Bool の場合の名付け方など、プログラムを書いている中で出会う機会の多いところと思うので、この機にみんなでしっかりおさらいをして、身に馴染ませていきましょう。よろしくお願いいたしますね。
————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #16
00:00 開始 00:36 副作用の有無に応じた関数名 02:49 副作用の有無で対になっている関数 03:53 一貫した名前を付ける 06:18 副作用のある関数 08:03 副作用のない関数 09:23 increment か increase か 10:53 対になっている関数 12:51 副作用を発生させないスタイル 16:38 状態変化と変数との相性 18:04 Swift の記述のしかた的なところ 19:49 return の省略 21:05 self. の省略 23:14 form で始まる関数名 24:54 form で始まる関数が使われている例 28:37 var と let に持つイメージ 32:20 副作用を伴わない関数を動詞で表現するとき 33:55 副作用を伴う関数を名詞で表現するとき 34:46 真偽値型のメソッドやプロパティー 35:20 レシーバーとメッセージパッシング 36:34 レシーバーについての主張として読み取れるように 37:34 主張として読み取れる機能の実際の例 43:43 プロトコルの名称 47:12 NSCoding と NSCopying 48:43 関連型で使うプロトコルの名称 49:40 これまでのルールに合致しないもの 51:03 次回の展望 —————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #16
はい、じゃあ始めますね。今日も API デザインガイドラインを見ていきますが、前回の最後にお話しした関数やメソッド名の命名規則について、もう少し深掘りしていくという流れです。
一応おさらいしておくと、副作用の有無に応じて関数名を変えるという基本ガイドラインがあります。副作用を持たない関数、要は何かしらの状態変化が起こらない関数――例えば計算して計算結果だけを返すようなもの――については名詞で名前を付けます。副作用を持つ関数、つまり自分自身を変更したり、その関数が呼び出されることで他の状態が変化するようなもの――例えば、他のプロパティの値が変わるとか、コンソールに出力するとか、データベースへ書き込むとか――については動詞で名前を付けるのが基本原則です。
ドキュメントの中には命令形の動詞と書かれていますが、一般的には副作用がないものは名詞、副作用があるものは動詞と大雑把に捉えると良いでしょう。そして、動詞の形がいろいろ変化することがありますが、ここでは命令形の動詞と言われています。
ここで大事なポイントとして、副作用を持つか持たないかに着目すると、特に副作用を持つメソッドと副作用を持たせないで結果を返すメソッドが対になって存在することがよくあります。そういったときに、どういったガイドラインやルールで作っていくかが記されています。
根本的に重要なのは、一貫した名前をつけることです。副作用があるものと副作用がないものに全然違う名前をつけるのではなく、同じ表現方法で区別してあげるということです。したがって、名前を動詞にするか名詞にするかが基本として掲げられています。
動詞で自然に表現できるとき、メソッドの役割が動詞で表現できるときには、副作用ありのものは動詞の命令形、動詞の原形で表現するのが良いでしょう。私は英語にあまり詳しくないので違うことが出てくる可能性もありますが、たぶん動詞の原形で大丈夫ですね。分かりやすい方法で捉えてもらえればと思います。
副作用ありの動詞でつけられたメソッド名は動詞の原形(命令形)で、それと対にして副作用なしのものを表現するには動詞の過去分詞形や現在分詞形を使います。つまり、何とかed
や 何とかing
という形で表現するのが基本原則です。
では、ここだけプレイグラウンドで見てみましょう。そんなに難しい話をしているわけではないですが、具体例があった方が分かりやすいと思います。例えば、データがあって、何かしらの型を持っているとします。ここでは、シリアルナンバーの更新を例にしてみましょう。以下のように雑に書いてみます。
struct MyData {
var serialNumber: Int
mutating func updateSerialNumber() {
self.serialNumber += 1
}
func currentSerialNumber() -> Int {
return self.serialNumber
}
}
このように updateSerialNumber
は副作用があるので動詞の命令形を使い、一方 currentSerialNumber
は副作用がないので名詞を使っています。こんな感じにしておくと、一貫性が保たれますね。 インクリメントで増加するみたいな文脈ですね。インクリメントっていう動詞が出てきますよね。これで自分自身を書き換えて、例えば ナンバー += 1
みたいな感じです。これでメソッドができました。自分を書き換える、つまり副作用のあるメソッド、インクリメントが作られました。
昔の常識だと、これで完成って感じがするんですけど、Swiftになるとメソッドチェーンとか、もっとオブジェクト指向をしっかり使っていくようになったんです。他に副作用を持たないインクリメントというメソッドも用意します。こちらは副作用を持たないもので、リターンで自分自身の型で例えば ナンバー + 1
を返すようにします。これが結構一般的な実装方法みたいですね。自分もこのスタイルに変わってきました。多分Swiftをやってからかなと思いますが、標準ライブラリがこのスタイルになっているのも影響しているかもしれませんね。
皆さんの英語力もすごいですね、いろんな回答が寄せられていて。確かに「Increase」も使われますね。C++とかの時代には、値を1つ増加させるっていうのは「インクリメント」を使うことが多かった気がします。
具体的な例としては、配列があったときに items.sort
って書くと配列そのものを並び替えます。一方で items.sorted
って書くと並び替えた結果が新しい配列として返されます。このように、状態変化させるメソッドと状態変化させないメソッドが存在します。昔のスタイルは副作用を発生させながら制御していくというものが多かったですね。オブジェクト指向プログラミングと関数型プログラミングの違いもここにあります。
オブジェクト指向プログラミングは状態を変更しながら処理を進めていくのに対し、関数型プログラミングは入力固定で出力固定の計算を積み重ねていくスタイルです。状態変化が伴うと、ランタイムエラーが発生しやすいと言われています。一方で関数型は、入力と出力が一対一で変化が把握しやすく、安全性が高いとされています。
メソッドチェーンというのも、状態変化を伴わない手法として有効です。状態変化を伴うメソッドと伴わないメソッドの使い分けが重要で、戻り値として返すことで別の処理に繋げることができます。例えば、絶対値を取るような計算をメソッドチェーンで行うことができますね。これが現代のプログラミングのスタイルの一つです。 とりあえず、そんなこともあって、状態変化は状態変化で考えると、Swiftで言うなら変数(var
)と非常に相性がいいですし、状態変化を伴わない戻り値として返すメソッドならば、これは定数(let
)と非常に相性がいいです。そのように使い分けや状況に応じて、いろいろなことができます。同じ性質のメソッドを持たせることが有意義に働くことがあります。
こういう風に似たメソッドが存在することは、Swiftではよくあることです。その時には名前を似たものにして、両方が関係していることを暗に示すようにします。ここで、少しソースコードの書き方の省略形についてお話しします。Swiftに慣れていない人には参考になるかと思います。
まず、「self」は省略可能です。これは好みにもよりますが、Appleのドキュメントを読む限り、self
を省略するのが推奨されています。また、自分自身の型を表現する時に、自分自身の型を明記する方法のほかに「Self」と書く方法もあります。個人的には、大文字で始まる「Self」を使った方が、自分自身だということを端的に伝えることができると感じます。
例えば、このシリアル(Serial)という型であるということがわかれば、Self
と書くことができます。そして、1ステートメントで戻り値を返すときにはreturn
を省略可能です。この書き方を覚えておくと、今後の勉強会で出てきた時に戻り値だと分かりやすいかと思います。ただし、このリターン省略方法は1ステートメントに限られるため、ログを出したい場合には省略できません。また、命令を1行にまとめるためにセミコロンを使ってもダメです。
Appleのドキュメントでは、self
を書くのを避けるような記述が推奨されていますが、具体的なガイドラインを明確に示しているわけではないので、その辺はガイドラインやAPIデザインガイドラインなどを自主的に参照するのが良いでしょう。
ちなみに、メソッド名が動詞の場合は原型、過去分詞型、または現在分詞型(例: applying
やadding
)で表現されることがあります。これは、英語的にどちらが自然かを考えて選ぶと良いと思います。メソッド名が名詞の場合、副作用なしのものは名詞そのまま、副作用ありのものには「form」をつけるガイドラインがありますが、この形式が必ずしも多く使われるわけではありません。
ということで、この部分についてはまた後で触れるとして、進んでいきます。 なので、例えば突然フォーム
(form)というプレフィックスが出てきたら、「あ、これは副作用を伴うメソッドなんだな」という風に読めるようになります。そうすると、他の人が書いたコードをスムーズに読めるようになりますね。
フォーム
が具体的にどこにあるかというと、セット(Set)にはあるかもしれません。確かアイテムズ配列にはないかなと思いますが、インデックス関連の話ですね。前回紹介したインデックス
(index)というメソッド、副作用の話で紹介したやつです。具体的には、アイテムズ自体には副作用はないんですけど、インデックス変数自体に副作用を与えるので、メソッドは副作用を伴うと解釈します。
例えば、アイテムズインデックスアフターインデックス
(items.index(after: index))と書くと、指定したインデックスの次のインデックスを返すメソッドとして存在します。これの副作用版として同じ名前体系で名前を付けています。大事なポイントとしては、名詞のメソッドとして副作用を伴わないものが自然に存在しているので、副作用を持つ名詞のメソッドにはプレフィックスとしてフォーム
を付けるということです。
例えば、セットに他のメソッドもありましたよね。セットバリューズフォーム
(setValues(form:))とは違ったかな?フォームユニオン
(formUnion)などがそうです。例えば、他のセット345
を加えると、バリューズ
(values)自体が12345
というセットになります。ここでバリューズ
はバー
(var)ではないといけません。フォームユニオン
も、元々のユニオン
(union)メソッドと対にして存在しています。
このバー(var)の部分も重要なポイントです。レッド
(let)は定数で、バー
(var)は変数と言われています。ただ、バー
は「状態」を示すこともあるんです。例えば、温度を例にすると、レッド
は特定の温度(例えば20度)を示し、バー
は温度計のように変化する状態を示します。この捉え方は、Swift UIでも関連してきますが、詳細は別の話になります。
副作用を持つメソッドと持たないメソッドが並存するという現象がSwiftにはよく見られます。そのような場合には、それらを対として捉えることが必要です。これがこのスライドの大事なポイントです。
次に、副作用を伴わない関数を動詞で表現する場合についてですが、例えばリバース
(reverse)というメソッド名には、何かを反転させる意味が含まれています。動詞のテンスを適切に使用することで、読みやすいメソッド名を作ることができます。
逆に、副作用を伴う関数を名詞で表現するときには、あらかじめ説明したようにプレフィックスとしてフォーム
を付けます。この命名ルールを守ることで、コードの可読性が向上します。適切な命名が行われることで、Swiftのガイドラインに沿った表現が自然にできるようになりますね。
こうして、単に変数や定数を使うだけでなく、その意味や状態を加味した視点で、Swiftの言語仕様に従った表現方法を使うことの重要性が理解できるようになるのです。 自分は変更しませんが、第三者の値を直接変更するため、副作用のあるメソッドには名詞の前に動詞を付けると自然な表現になります。一方、副作用を持たないものは名詞そのまま、副作用を持つものは名詞の頭に動詞を付けるといった感じです。解説すると長くなりますのでここでは割愛しますね。
次に進みます。これは大事なポイントで、自分の中で特に記憶に残っている部分です。真偽値型のメソッドやプロパティに関する話です。これの内容変化を伴わない場合、つまり副作用が発生しない場合には、レシーバーについての主張として読み取れるようにするというのがガイドラインとして掲げられています。
レシーバーというのは 何とかドット
と書かれたその最初の部分です。ここに表示されている例だと x.ドット
であれば、x
がレシーバーと呼ばれるものです。これはオブジェクト指向の用語ですね。また、line1.ドット
であれば、line1
がレシーバーとなります。メッセージパッシングという方式を使い、レシーバーに対してメッセージを送り、そのレシーバーがメッセージを受け取って応答を返すというのがオブジェクト指向の基本原則です。
Objective-C はこのメッセージパッシングを忠実に再現したオブジェクト指向言語で、それに習って発展してきた Swift 言語でも最初のドットの前の部分をレシーバーと呼びます。細かいことを言うと、そういった話になります。この例では、x.isEmpty
のように、副作用を持たないメソッドやプロパティの場合、そのレシーバーの主張として読み取れるように表現します。
x.isEmpty
の場合、x
が空であるかどうかを示しています。このように、副作用を持たないメソッドやプロパティは名詞のまま表現することがガイドラインとして推奨されています。
次に、例えば Line1 と Line2 の交差を調べる場合、line1.intersects(line2)
のように、副作用を持たないメソッドは名詞のまま使用します。つまり、レシーバーがそのメソッドを持っていることを主張する形になっています。
さらに、Dictionary や Array の例で言うと、例えばあるキーが存在するかどうかを調べるとき、userOptions.contains(key)
のように、副作用を持たないメソッドは名詞のまま、contains
を使って表現します。このようにメソッドを定義すると、ブール値を返すメソッドとなります。
副作用がないメソッドやプロパティについては、対となる副作用を持つ版はほとんど存在しません。したがって、副作用を持たないメソッドはレシーバーの主張として読み取れる形で表現するというガイドラインが存在します。
例えば contains
ではなく、x.contains(y)
のように、x
が y
を含むという形にすると、より自然な表現になります。このガイドラインは、APIデザインガイドラインについての勉強会で話題にされたことがあり、そのときに自分自身も納得がなかったため、特に心に残っています。そのため、皆さんにこのガイドラインを強調してお伝えしたいと考えています。 このガイドラインを意識できるようになると、「ブール」というメソッドが出てきた時に非常に綺麗な表現に変わっていくという感覚があります。このガイドラインは心に残るし、お気に入りでもあるんです。
次に進みましょう。プロトコルの名称についてです。プロトコルが何かを表す場合は名詞を使い、そのプロトコルで特性や能力を表すものは、接尾辞として「〜able」や「〜ing」を付けます。この辺りはSwiftでプロトコルを使うのに慣れてくると自然と身についてくるものだと思います。
具体的に標準ライブラリにあるものとしては、「Collection」のように値が連続してかつ順序立てられて存在している集合を表すものがあります。他にもこのプロトコルに準拠したものは、自分自身のインスタンス同士で比較可能な能力を持つ場合、「Equatable」を使います。代償関係も含めて比較可能な場合は「Comparable」を使います。「Convertible」のように接尾辞が「〜ible」になるものもあります。
具体的な例として「CustomStringConvertible」は頻出です。このように友好的なガイドラインに従うと、プロトコル名も適切に付けられます。
他にも「〜ing」が付くプロトコルがあります。例えば、「NSCopying」や「NSCoding」などです。Objective-Cで文化に寄り添った型を作成すると、必ず「NSObject」と「NSCopying」「NSCoding」を適用させてデータ型を表現するクラスを作ることになります。これにより「NSKeyedArchiver」で変換できたり、コピーをするための共通認識ができたりします。
このように「〜able」「〜ible」「〜ing」といった接尾辞を付けてプロトコル名を考えます。もし迷ったら、このガイドラインを参考にして名前を付けてください。
また、前回(もしくはもっと前)に紹介したこととして、アソシエイティブ・タイプ(付属型)として使うプロトコルの場合、役割を明確に表すために「〜Protocol」とするルールもあります。たとえば「CollectionProtocol」のように表現します。
これまでに挙げた関数やプロパティの命名規則について、名詞で付ける場合もあります。流暢な英語表現を目指す観点でメソッドやプロパティ、関数、変数、定数の名前付けを紹介しました。今回も3回ほどにわたって紹介してきました。意外と長いので最初の方の内容を忘れているかもしれません。この資料は途中ですが、アップしても良いかもしれません。
今日はここで終わりにします。次回は引き続き命名規則について、特に専門用語に注目してガイドラインを見ていこうと思います。1時間経ちましたので、今日はこれで終わりにしましょう。ありがとうございました。