Rubyスタイルガイドを読む: 命名

こんにちは、hachi8833です。スタイルガイドを読むシリーズ、今回は「命名編」です。

Rubyは日本で生まれた言語でありながら、英語を強く意識した作りになっています。Railsではそれがさらに推し進められ、ActiveSupport::Inflectorで英語の活用形変換がさかんに利用されているのは皆さまもご存知かと思います。

命名編のスタイルでも同様に、英語圏を中心とした記述が多く見当たります。今回は変数命名に役立ちそうなことも書いてみました(「述語メソッド命名のヒント」)。

Rubyスタイルガイド: 命名

現実のプログラミングで本当に難しいのは、キャッシュの無効化と名前付けだけだ
— Phil Karlton

3-01【統一】識別子の命名には英語を使う

Name identifiers in English.

# 不可 - 識別子が非ASCII文字で書かれている
заплата = 1_000

# 不可 - 識別子がブルガリア語で書かれている(キリル文字ではなくラテン文字ではあるが)
zaplata = 1_000

# 良好
salary = 1_000

サンプルからわかるように、「英数字(alphanumeric)を使う」ということと「他の国の言語で書かない」ということが含意されています。つまり日本語をローマ字表記するのもなしということになります。

Railsではコントローラやモデルやテーブルの名前がsingularizepluralizeで活用されることが前提になっているので、ローマ字で日本語を表記すると複数形になるなど不都合が生じてしまいます。

3-02【統一】シンボル名、メソッド名、変数名はスネークケースにする

Use snake_case for symbols, methods and variables.

# 不可
:'some symbol'
:SomeSymbol
:someSymbol

someVar = 5

def someMethod
  # (コード)
end

def SomeMethod
  # (コード)
end

# 良好
:some_symbol

some_var = 5

def some_method
  # (コード)
end

スタイルでは禁止されていますが、Rubyでは:'some symbol'などのようにスペースを含む識別子を作ることもできてしまうんですね。

Rubyでは変数の命名によって変数のスコープが変わります(変数定義場所なども影響します)。今さらですが、主なものを以下にまとめてみました。

小文字または_で始まる識別子
ローカル変数(最初の代入はそのスコープに属するローカル変数の宣言になる)
またはメソッド呼び出し(宣言されていない識別子の参照は引数のないメソッド呼び出しとみなされる)
@で始まる変数
インスタンス変数(特定のオブジェクトに所属し、そのクラスまたはサブクラスのメソッドから参照できる)
@@で始まる変数
クラス変数(クラス定義の中で定義され、クラスの特異メソッド、インスタンスメソッドなどから参照・代入できる)
$で始まる変数
グローバル変数(宣言なしでコードのどこでも利用できる)
アルファベット大文字 ([A-Z]) で始まる識別子
定数(定義と初期化は代入で行われる、メソッド内では定義できない)

3-03【統一】シンボル名、メソッド名、変数名に付ける数字は文字と接すること

Do not separate numbers from letters on symbols, methods and variables.

# 不可
:some_sym_1

some_var_1 = 1

def some_method_1
  # (コード)
end

# 良好
:some_sym1

some_var1 = 1

def some_method1
  # (コード)
end

これは明らかに統一が目的と思われます。some_sym_1some_sym1が混用されるとチーム開発で不便が生じるので、some_sym1という命名に統一するということですね。

3-04【統一】クラス名とモジュール名はキャメルケースにする

Use CamelCase for classes and modules. (Keep acronyms like HTTP, RFC, XML uppercase.)

HTTP、RFC、XMLといった略語は大文字のままにします。

# 不可
class Someclass
  # (コード)
end

class Some_Class
  # (コード)
end

class SomeXml
  # (コード)
end

class XmlSomething
  # (コード)
end

# 良好
class SomeClass
  # (コード)
end

class SomeXML
  # (コード)
end

class XMLSomething
  # (コード)
end

3-05【統一】ファイル名とディレクトリ名はスネークケースにする

Use snake_case for naming files, e.g. hello_world.rb.
Use snake_case for naming directories, e.g. lib/hello_world/hello_world.rb.

3-06【統一】ソースファイル名にはクラス名やモジュール名のキャメルケースをスネークケースに変えたものを使う

Aim to have just a single class/module per source file. Name the file name as the class/module, but replacing CamelCase with snake_case.

ひとつのソースファイルにクラスやモジュールをひとつしか含まないようにすることが目的です。ソースファイルにクラスが複数含まれるとこの命名法でファイル名を決められなくなるので、このスタイルを守ることで1ソースファイル=1クラス(またはモジュール)を自然に保てるようになります。

なお、クラスやモジュールが互いにネストしているのをActiveSupportなどでよく見かけますが↓、この場合のファイル名についてはスタイルでは言及されていません。

class ERB
  module Util                ## クラスにモジュールがネストしている
  ...
  end
end

module ActiveSupport
  class SafeBuffer < String  ## モジュールにクラスがネストしている
  ...
  end
end

3-07【統一】 その他の定数名は「スクリーミングスネークケース」にする

Use SCREAMING_SNAKE_CASE for other constants.

# 不可
SomeConst = 5

# 良好
SOME_CONST = 5

定数を大文字だけで書くスタイルはbashやC言語など多くの言語で採用されています。定数名が複数の単語でできている場合はアンダースコアで区切ります。

追伸: Rubyの定数について

前述のとおり、Rubyでは先頭が大文字で始まると定数として扱う仕様になっています。以下は先頭以外のスタイルにかかわらずすべて定数として扱われるので注意が必要です。

  • MY_VARIABLE
  • My_variable(スタイル違反)
  • MyVariable(スタイル違反)

また、定数は定義した場所によってスコープが異なります。

クラス/モジュール定義内で定義した定数
以下のすべてで有効
・そのクラス/モジュール定義の中(中のメソッドやネストしたクラス/モジュール定義も含む)
・そのクラスを継承しているクラス
・そのモジュールをインクルードしているクラスまたはモジュール
クラス定義の外(トップレベル)で定義した定数
Objectに所属する(事実上グローバル)

なお、以下のようなlower camel case(先頭のみ小文字のキャメルケース)はRubyではどの識別子でも使わない慣例になっています。

  • myVariable
  • getInputReader

参考: Wikipedia: キャメルケース

追伸: “screaming”について

英語圏では、普通の文や文章全体の文字をすべて大文字で書くと耳元で叫ばれているようなうるささを感じる人が多いようです。この大文字+スネークケースを「screaming」と呼んでいるのはそこに由来していると思われます。

やったことはありませんが、もし英語チャットをオール大文字にしたら相当うざがられそうです。

英文メールやドキュメントで、注意事項のもっとも重要な部分がすべて大文字で書かれていることがありますが、太字やイタリックやフォントサイズ拡大よりも強く響くようなので、英語のビジネス文書では大文字を多用した文はできるだけ避けるようにという指導がよくなされます。

逆に契約書などで、極めて重要な注意に限って特定のフレーズをあえて大文字にすることもあります。

日本語ではこれに直接対応するスタイルがありませんが、強いて言うなら「だ・か・ら」「と・つ・ぜ・ん」のような強調が読者に与える印象としては似ているかもしれません。

なお、アメコミは伝統的にセリフや効果音を「THOOM」や「CRAAACK」のようにすべて大文字にしますが、日本と違って米国では未だにコミックは「子ども向け」という印象が社会に根強く残っていることも大きいかもしれません(それなりに変わりつつありますが)。

3-08【統一】 述語メソッド名の末尾には疑問符?を置くこと

The names of predicate methods (methods that return a boolean value) should end in a question mark. (i.e. Array#empty?).
Methods that don’t return a boolean, shouldn’t end in a question mark.

述語メソッド(predicate method)は、論理値(truefalseのいずれか)のみを返すメソッドです。それ以外のメソッドでは疑問符を使わないこと、とあります。

3-09【統一】述語メソッドの冒頭にisやdoesやcanといった助動詞はなるべく置かないようにする

Avoid prefixing predicate methods with the auxiliary verbs such as is, does, or can.
These words are redundant and inconsistent with the style of boolean methods in the Ruby core library, such as empty? and include?.

# 不可
class Person
  def is_tall?             # is_は不要
    true
  end

  def can_play_basketball? # can_は不要
    false
  end

  def does_like_candy?     # does_は不要
    true
  end
end

# 良好
class Person
  def tall?
    true
  end

  def basketball_player?
    false
  end

  def likes_candy?
    true
  end
end

追伸: Object#is_a?について

Rubyの標準ライブラリにあるObject#is_a?メソッドは、述語メソッドでありながらis_を含んでいますが、これはおそらく上述とは考え方が異なるという指摘をmorimorihogeさんからいただきました。

以下、Rubyリファレンスを参考に考察してみました。

オブジェクトが指定されたクラス mod かそのサブクラスのインスタンスであるとき真を返します。
また、オブジェクトがモジュール mod をインクルードしたクラスかそのサブクラス のインスタンスである場合にも真を返します。 上記のいずれでもない場合に false を返します。
Rubyリファレンス・マニュアル: Object#is_a?より

リファレンスマニュアルにあるように、Object#is_a?の引数にはクラス名やモジュール名を与えます。クラス名やモジュール名は「カテゴリを表す名詞」と考えられるので、#is_a?というメソッド名と整合すると考えられます。

# Rubyリファレンス・マニュアル: Object#is_a?より
module M
end
class C < Object
  include M
end
class S < C
end

obj = S.new
p obj.is_a?(S)       # true
p obj.is_a?(C)       # true
p obj.is_a?(Object)  # true
p obj.is_a?(M)       # true
p obj.is_a?(Hash)    # false

そしてObject#is_a?は英語の「◯☓ is a △?」という構文ほぼそのままであり、この「a」は英語で悩みのタネになりがちな不定冠詞のはずです。

言い換えると、「クジラは哺乳類か?」「刑事は警察官か?」「アラスカは米国か?」といった「カテゴリに属するかどうか」という質問をメソッド化したものであり、英文法的には△には名詞(ここではクラス名やモジュール名)を置くことが期待されていると思います。

もし仮にis_を取って#a?というメソッド名にすると、#to_aの類推から「配列かどうか?」をチェックするメソッドに見えてしまうかもしれません。

なおObject#is_a?述語メソッドには#kind_of?というエイリアスもあります。

述語メソッド命名のヒント

話を戻すと、以下に該当する語はis_tall?tall?のように英語と整合しやすい形で述語メソッド名の「is_」や「can_」などを省略できると思います。いつもきれいに適用できるとは限りませんが。

  • 形容詞tall?empty?など)
  • 形容詞的用法の過去分詞terminated?finished?など) — 形容詞と同じ扱いです
  • 名詞board_member?subset?など)
  • 動詞likes?starts_with?) <– この場合は「can_」や「does_」などを省略した形

動詞は#has_key?のように目的語を伴うこともあります。

動詞はlike?start_with?といった原型でも通じますが、英語圏ではlikes?starts_with?といった三単現の述語メソッドが好まれる傾向があるように思えます。英語的に自然であるという理由のほかに、たとえばlike?だと「似ている?」という意味の形容詞と間違えられやすいという理由もあるかもしれません。

たとえばActiveSupportのString#start_with?String#end_with?では、以下のように三単現の#starts_with?starts_with?というエイリアスがわざわざ追加されています。

class String
  alias_method :starts_with?, :start_with?
  alias_method :ends_with?, :end_with?
end

3-10【統一】破壊的でないメソッドと破壊的なメソッドがある場合、破壊的メソッド名の末尾には感嘆符!を置くことで区別する

The names of potentially dangerous methods (i.e. methods that modify self or the arguments, exit! (doesn’t run the finalizers like exit does), etc.) should end with an exclamation mark if there exists a safe version of that dangerous method.

破壊的なメソッドは、潜在的な危険をはらむ以下のようなメソッドを指します。

  • selfや引数を変更する
  • finalize処理を行わない(例外をraiseする可能性があるなど)
# 不可 - 対応する「破壊的でないメソッド」がない
class Person
  def update!
  end
end

# 良好
class Person
  def update
  end
end

# 良好
class Person
  def update!
  end

  def update
  end
end

?!を使うRubyのメソッド命名法は個人的にはかなりよくできていると思います。言葉で説明すると長くなりがちなメソッド名をコンパクトにまとめられますし、対になるメソッドの存在を!で自然に示すことができ、しかもスタイルを揃える効果があるからです。

3-11【統一】可能な場合は、!付きの破壊的メソッドに対応する!なしの「破壊的でない」メソッドも定義すること

Define the non-bang (safe) method in terms of the bang (dangerous) one if possible.

class Array
  def flatten_once!
    res = []

    each do |e|
      [*e].each { |f| res << f }
    end

    replace(res)
  end

  def flatten_once
    dup.flatten_once!
  end
end

前述のとおり、破壊的メソッド末尾の感嘆符!はそれに対応する破壊的でない(安全な)メソッドもあることを示します。

上のコード例のようにレシーバ(self)が対象の場合は、最初に破壊的メソッドを定義し、非破壊的メソッドでは#dupで破壊的メソッドを呼ぶのが簡単です。非破壊メソッドの定義のdup.flatten_once!は、selfをつけたself.dup.flatten_once!と同等です。

以下は上の定義にselfをつけた実行例です。

3-12【統一】2項演算子の定義では引数をotherに揃える

When defining binary operators, name the parameter other(<< and [] are exceptions to the rule, since their semantics are different).

2項演算子のうち<<[]は構文が異なるため、このスタイルの対象に含まれません。

def +(other)   # 2項演算子では引数をotherにする
  # (コード)
end

次回はコメント編です。どうぞご期待ください。

関連記事


Ruby on RailsによるWEBシステム開発、Android/iPhoneアプリ開発、電子書籍配信のことならお任せください この記事を書いた人と働こう! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

hachi8833

Twitter: @hachi8833、GitHub: @hachi8833 コボラー、ITコンサル、ローカライズ業界、Rails開発を経てTechRachoの編集・記事作成を担当。 これまでにRuby on Rails チュートリアル第2版の半分ほど、Railsガイドの初期翻訳ではほぼすべてを翻訳。その後も折に触れてそれぞれ一部を翻訳。 かと思うと、正規表現の粋を尽くした日本語エラーチェックサービス enno.jpを運営。 実は最近Go言語が好き。 仕事に関係ないすっとこブログ「あけてくれ」は2000年頃から多少の中断をはさんで継続、現在はnote.muに移転。

hachi8833の書いた記事

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ