はじめに
Railsの命名について、Model, Controllerやテーブルに対する命名のことは数多く書かれているが、メソッド名、変数名、スコープ名についてはあまり書かれていないように見受けられる。
命名の良し悪しはコードの可読性に大きく影響を与えるが、一方で英語が苦手な僕たちは度々血迷ったネーミングをしてしまい、しばしば技術的負債を溜め込む。
そもそもrubyは英語っぽい書き味(あくまで「ぽい」)を意識した言語仕様になっているので、メソッド名、変数名、スコープ名についてもある程度このマナーに則るべきだと思う。そこで改めて、これら命名が従うべきガイドラインを考察してみた。
色々整理しながら書くので小分けにしたく、今回は一旦モデルのメソッド、スコープ、(少しだけ)変数の命名だけ考えたが、後日コントローラーについても考えるつもりでいる。
TL;DR (モデル編)
※ いろいろ考えているうちに頭がこんがらがってきた。後で修正するかもしれません。
命名対象 | 形式 |
---|---|
スコープ | 「形容詞(句)」, 「前置詞_名詞」, 「動詞ing」, 「動詞の過去分詞形」 |
作用を持つメソッド | 「他動詞」 → 他動詞の目的語がレシーバーobjectになるように! |
判定系メソッド | 「形容詞(句)?」, 「前置詞_名詞?」, 「動詞ing?」, 「動詞の過去分詞形?」 |
作用を持たないメソッド | 「名詞(句)」 → プロパティとして使えるように |
オブジェクトそのものを変換するメソッド | 「to_名詞」 |
変数 | 「名詞(句)」 |
変数(例外) | 「動詞の過去分詞形_前置詞(_名詞)」 → 避けられるなら避ける |
使用する単語の品詞に留意する。そして、読んで意味がわかるのが一番大切。
詳細
スコープ名
形容詞または形容詞句であるべき。
スコープ名がモデル名を後置修飾する。
「どんな〇〇」の「どんな」の部分をスコープ名にする。
〇〇にはモデル名が入る。
形容詞句で後置修飾するようにスコープを命名しておくと、スコープチェーンしたときにも英文法的に破綻しない。
Book.in_sale.published_by("O'Reilly")
← Book (which is) in sale published by O'Reilly
典型例
####モデル名.動詞+ing または モデル名.動詞の過去分詞形
Shop.selling_books # 本を売っている店
Task.not_running # 否定形
Book.published # 過去分詞形
Book.published_by("O'Reilly") # 引数も取るスコープ
Book (which is) published by O'Reilly
の (which is)
が省略された名詞句に見えるのが良い命名です。
モデル名.前置詞+名詞
Shop.without_blue_roof
Devil.in_the_prada
Man.with_the_machine_gun
モデル名.形容詞
Mission.impossible
Daemon.alive
Picture.not_displayable
Tower.taller_than(100)
Session.expired # 補足) expired は形容詞ともみなせるし、自動詞の過去分詞形ともみなせる(大きな差はない)
だめパターン
時々 :filtered_xxxx
というスタイルのスコープ名を見かけることがあるけど、そもそもスコープの機能が絞り込みなのでこういう命名は冗長。
:can_xxxx
も推奨しない。いい命名かどうかの判定は, 「Model (which is) scope名」というふうに省略していた which is を復活させて文法的に崩壊しないかどうか。
メソッド名
作用(変数等の変更)を伴うメソッドか、作用のないメソッドかで考え方が大きく異なってくる。
作用を伴うメソッド
「主語(呼び出し元オブジェクト)」が「目的語(レシーバー)」を「どうするか」。
「どうするか」の部分をメソッド名とする。
どうにかした結果として内部の変数状態を変えたり、外部のオブジェクトに作用したり、外部のサービスにアクセスしたりする。
cow.grow # <= I grow up the cow. その結果 cow.age はインクリメントされる、などの作用が起こる。
file.delete # <= I delete the file. その結果、(プログラムにとっては)外部のファイルシステム上でファイルが消える作用を起こす。
job.perform # <= I perform the job.
時々、レシーバー(オブジェクト)が主語とされるようなメソッドを見かけるが、良くない。
一瞬良さそうに見えるかもしれないが、長い目で見るとオブジェクト指向じゃなくなってしまい、崩壊し始める。
※ オブジェクトに対してメソッドというインターフェースを通じて作用を加える。この連鎖で作るのがオブジェクト指向開発。
# 悪い例
manager.evaluate(member) # <= 語順そのまま A manager evaluates his member.
作用を伴わないメソッド
判定系メソッド(Predicate methods)
true
かfalse
を返す判定系(作用しない)メソッドは、通常の(作用を起こす)メソッドと明確に命名のアプローチが違う。
この場合に限り、レシーバーは主語扱いする。
例えば、Shop
がopen
状態かどうかを判定するStatementは
shop.open? # <= Is the shop open?
他の言語だと、しばしば shop.is_open
というbe動詞付きのノリで判定系メソッドを作るが、
Ruby / Railsの通例ではbe動詞は省略して、末尾に?
をつける。?
が使えるという言語仕様特有の文化だと思うし、そもそも疑問形だと「S+V+C」は「V+S+C?」になるので、主語と補語の間には is
は入らない。
判定系メソッドの場合に限り、メソッド名は「補語句的」である必要がある。(S=object, V=省略, C=メソッド名)
→ スコープの命名と似ていて、「形容詞?」か「前置詞+名詞?」か「動詞+ing?」か「動詞の過去分詞形?」が基本となる。
can_xxx
, can_be_yyy
は有りか無しか? できるだけ避けたほうが良いとは思う。後で考える。
判定系の否定メソッド
shop.not_open?
というメソッドは極力作らない。 !shop.open?
と書けば済む。
判定条件が複雑になると、そのメソッドの可読性も落ちてしまうので、そもそも否定の意味を内包しないように設計するのが吉だと思う。
作用は伴わないが判定しないメソッド
true
/ false
を返すわけではないメソッドは, 外部からプロパティとして利用できるようなものはプロパティとして定義するのが良さそう。
このときの命名は後述の「変数名」と同じスタイル。
もしくは、オブジェクトそのものを別の形式に変換するなら to_xxx
という命名にすべき。convert_to_xxx
とはしない。
例えば
class Sylinder
attr_accessor :bore, :stroke
def initializer(bore, stroke)
@bore = bore
@stroke = stroke
end
def displacement # <= オブジェクトの内部状態に作用しないので :calculate_displacement のようなメソッド名にはしない
(@bore/2) * (@bore/2) * Math::PI * @stroke
end
end
変数名
基本的には名詞句を変数名としたい。
変数はオブジェクト(またはクラス)が所有する属性。属性名は名詞。名詞の前に形容詞とか所有格を付けて意味を絞り込んでも良い。
user.email
user.first_name
例外的に、そのオブジェクトに対するアクション(作用)に関する補助的な情報を保持する変数は、動詞の過去分詞形+前置詞(+名詞)という形式の変数名となることがある。
こういう変数名を使わずに表現できるのであれば、避けたほうが後で読みやすい気がする。
article.created_at
article.published_by
article.referenced_in_indices
考察
Ruby言語仕様
automator.call bob if not bob.awake?
みたいにメソッドの引数を括弧無しで呼び出せるところや、not
, and
キーワードが定義されているあたりからも、
もともと記号的記述を少なく押さえて自然言語っぽく書けるようになっている。
呼び出す際に自然言語っぽい表現になるように、スコープやメソッド、変数の命名も工夫してあげるとコードリーディングのストレスが軽くなる。
英文法の基本
英文法は基本的に下記の5文型に類型されることが知られている。
- S+V
- S+V+C
- S+V+O
- S+V+O+O
- S+V+O+C
文型の構成要素と、それぞれが対応する Rails プログラミングにおける概念は下記になると思う。
S: 主語(主部) → 呼び出し元(サブジェクト)
V: 動詞 → メソッド
O: 目的語(名詞) → レシーバー(メソッドを生やしているオブジェクト) と, メソッド引数(第4文型の場合)
C: 補語(形容詞/副詞/形容詞句/副詞句) → 代入値
S+V+Oの"O"はオブジェクト指向(Object-oriented)の"O"です。
オブジェクト指向的言語がカバーすべき表現力
基本的にメソッド呼び出しは第3文型のセンテンスを表現していると考えて良さそう。それ以外は、代入やプロパティ変更として実装するか、第3文型に変換して実装することになるか。
第1文型: S+V
あんまりない。なぜなら、オブジェクト指向は呼び出し元がレシーバーに対して作用することで処理を実装していくパラダイムだから。
シンプルな file.delete()
とかは、実は第3文型だと思う。
file.delete # <= I deletes the file.
第2文型: S+V+C
O (Object)が登場しないので、これもメソッドの形としてはあまりないケース。あるとすれば呼び出し元オブジェクトが自分のプロパティに何かを代入するとか、そういう実装になると思う。
この文型をとるV(動詞)のバリエーションは下記のとおり。
状態型: be, look, seem, appear, keep, remain, lie
変化型: become, get, turn, grow, make, come, go, fall
感覚型: smell, taste, feel, sound
出典: https://www.eng-builder.jp/learning/sentence-pattern2/
つまり、これらの動詞は変化を伴うか伴わないかなどの違いはあれどどれも結果的にSがCという状態 (S = C
) であることを表現しているわけである。
この表現をプログラミングで記述するにあたり、大概の場合はオブジェクトの状態を表すプロパティに対して値を代入することで実現できそう。
alice.health = :fine # <= Alice becomes fine.
第3文型: S+V+O
これはわかりやすい。一番多そう。
file.delete # <= I delete the file.
window.move_to(100, 300) # <= I move the window to (100, 300).
与えられる引数は副詞句として文全体を修飾する役割に使われることが多い気がする。
要するに、引数はオブジェクトに対する作用を調整するパラメータ。
パラメーターは呼び出された側のメソッド内ではconstであるほうがいいような気もするけど、そこまでまだ自分の中で考えられてません。
第4文型: S+V+O1+O2
この文型をとるV(動詞)のバリエーションは下記のとおり。
give, show, offer, hand, pass, send, teach, tell, pay, lend, do
傾向として, SがO1にO2を与えるような意味になっていると分かる。
オブジェクト指向的プログラミングで記述する場合、プロパティへの代入/追加などで表現できることが多い気がする。
alice.belongings.push book # <= I give Alice a book
alice.give book # <= I give Alice a book
第5文型: S+V+O+C
この文型をとるV(動詞)のバリエーションは下記のとおり。
make, get, keep, leave, think, believe, find, call, name
どれも O=Cであるという状態を表現する動詞ばかりであることが分かる。
SがOに働きかけるか否かの違いはあるが、結果的に 「Oの何らかの状態がC」になったり、 「Oの何らかの状態がC」であることを認識するという意味の動詞。
これは、オブジェクト指向的に実装しようとするとCとOだけ使ってもっと単純なSVC表現に書き換えることができそう。
Room.tidiness = :clean # <= I make the room clean.
cat.name = "Tom" # <= I name the cat "Tom."
まとめ
ややこしくてもうわからん。なんかまとまりの悪い文章になってしまった。