こんにちは、hachi8833です。Rubyスタイルガイドを読むシリーズ、今回は「クラスとモジュール編」その3をお送りします。
- Rubyスタイルガイドを読む: 総もくじ
- 前回: Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど
- 次回: Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)
クラスとモジュール(3)メソッドのスコープやエイリアスなど
5-16【統一】クラス変数(@@
で始まる変数)はできるだけ避ける
Avoid the usage of class (
@@
) variables due to their "nasty" behavior in inheritance.
クラス変数は、以下のように継承で予想外の動作を引き起こすことがあります。
class Parent
@@class_var = 'parent'
def self.print_class_var
puts @@class_var
end
end
class Child < Parent
@@class_var = 'child'
end
Parent.print_class_var # => 'parent'ではなく'child'が出力されてしまう
As you can see all the classes in a class hierarchy actually share one class variable. Class instance variables should usually be preferred over class variables.
上のParentクラスとChildクラスが同じクラス変数@@class_var
を共有していますが、場合によっては好ましくない動作を引き起こします。インスタンス変数(@
で始まる変数)を初期化すればできることはクラス変数でやらないようにしましょう。
5-17【統一】メソッドの公開範囲を用途に応じて適切に設定する
Assign proper visibility levels to methods (
private
,protected
) in accordance with their intended usage. Don't go off leaving everythingpublic
(which is the default). After all we're coding in Ruby now, not in Python.
全メソッドをデフォルトのpublic
のままにせず、必要なものはprivate
やprotected
を使って公開範囲を適切に絞り込みます。
このあたりはPythonのコーディングスタイルとは異なるようです。
Python においては、すべては、真の意味でプライベートではない; Pythonの内部的には、プライベート・メソッドやアトリビュートの名前はこっそりとバラバラにされてしまうため、もともと与えられたそれらの名前のみではアクセスができないかのように見えるのである。 MP3FileInfoクラスの__parse メソッドには、__MP3FileInfo__parse という名前によって、はじめてアクセスが可能である。 これが大変興味深いことは認めよう。しかし、このような方法でのアクセスは、実際のコードの中では決して行わない事を約束してほしい。 プライベート・メソッドは、その命名規則にもとづいて、プライベートになっている。 しかし、Python における他の多くのことと同じように、それがプライベートであることは単なる慣習上の決め事であり、強制ではないのである。
5.9. プライベート関数 (Private Functions)より
Rubyのprivateメソッドは、そのクラス自身からの呼び出しのみが想定されます。privateメソッドはレシーバを指定できないので、関数形式でしか呼び出せません。このメカニズムを使って、privateメソッドはそれが属するクラス自身からしか呼べないようになっています。
Object#send
を使うと無理やり外部から呼び出すことも一応できますが、乱用は避けたいところです。「望ましくない使い方は、わざと使いにくくする」というRuby言語設計者のポリシーを感じてしまいました。
追伸: private
メソッドはサブクラスのインスタンスメソッドから呼べる
Webチームのkazzさん(Java出身)が試してみたところ、スーパークラスのprivate
なメソッドはサブクラスのインスタンスメソッドから呼び出せました。
以下は私もRuby 2.4でダブルチェックしましたが、1.9でも動作は同じでした。
class Parent1
private
def priv1
"I'm private"
end
end
class Child1 < Parent1
def call_priv1
priv1
end
end
Child1.new.call_priv1 #=> "I'm private"
もちろん、クラスのインスタンスをレシーバとしてprivate
メソッドを呼ぶと普通にエラーになります。
class Parent1
private
def priv1
"I'm private"
end
end
Parent1.new.call_priv1 #=> NoMethodError
当初この「サブクラスのインスタンスメソッドからスーパークラスのprivate
メソッドを呼び出せる」挙動について悩んでしまいましたが、morimorihogeさんが「これはまったく仕様どおりです」と「[Ruby] privateメソッドの本質とそれを理解するメリット」という記事を教えてくれました。
同記事を元に次のように理解しました。
private
のついたメソッドを呼び出す時は、レシーバは指定できないようになっている(Rubyではそれ以外の制限をかけていない)- スーパークラスの
private
メソッドはサブクラスにもprivate
メソッドとして継承される - したがって、継承した
private
メソッドはサブクラスのインスタンスメソッド内で呼び出せる
以下はネットで見つけたコード例を若干変更したものですが、こんなふうにスーパークラスのprivate
メソッドをサブクラスでpublic
に変更するといった利用法も考えられます。もし仮にスーパークラスのprivateメソッドはサブクラスに継承されないとしたら、このようなサブクラスでのメソッドスコープは変更できないことになりますね。
class Parent
private
def some_method
"private in Parent"
end
end
class Child < Parent
public :some_method
end
Child.new.some_method #=> "private in Parent"
5-18【統一】private
とprotected
は対象となるメソッド定義とインデントを揃える
Indent the
public
,protected
, and private methods as much as the method definitions they apply to. Leave one blank line above the visibility modifier and one blank line below in order to emphasize that it applies to all methods below it.
インデントを揃えて、private
とprotected
の対象が明確になるようにします。
さらにprivate
とprotected
の前後には空行を置いて読みやすくします。
class SomeClass
def public_method
# (本文は略)
end
private
def private_method
# (本文は略)
end
def another_private_method
# (本文は略)
end
end
追伸: Rubyのprotectedメソッド
Rubyのprotectedはわかりにくいので有名です。Ruby 2.4リファレンスマニュアルより引用します。
public に設定されたメソッドは制限なしに呼び出せます。
private に設定されたメソッドは関数形式でしか呼び出せません。
protected に設定されたメソッドは、そのメソッドを持つオブジェクトが selfであるコンテキスト(メソッド定義式やinstance_eval)でのみ呼び出せます。
Ruby 2.4リファレンスマニュアルより
私の目から見て、上述のprivate
とprotected
の説明は「メカニズムの一部」を説明しているだけで、「目的」「使い方」について説明がない点に戸惑いを感じてしまいました。
この点については上述の「Ruby の private と protected 。歴史と使い分け」で非常に詳しく説明されていますが、特にJavaやC++からRubyにやってきた開発者がprotected
の定義の違いに戸惑うことが多いようです。
5-19【統一】クラスメソッドはdef self.method
で定義する
Use
def self.method
to define class methods. This makes the code easier to refactor since the class name is not repeated.
クラス名を繰り返し書かなくてよくなるので、リファクタリングしやすくなります。
class TestClass
# 不可
def TestClass.some_method
# (本文は略)
end
# 良好
def self.some_other_method
# (本文は略)
end
# 以下の書き方もOK: クラスメソッドを多数定義する場合向け
class << self
def first_method
# (本文は略)
end
def second_method_etc
# (本文は略)
end
end
end
5-20【統一】メソッドをレキシカルクラススコープでエイリアスする場合はalias
を使うのが望ましい
Prefer
alias
when aliasing methods in lexical class scope as the resolution ofself
in this context is also lexical, and it communicates clearly to the user that the indirection of your alias will not be altered at runtime or by any subclass unless made explicit.
#alias
によるメソッドのエイリアスはレキシカル(≒静的)なクラススコープのコンテキストで作成されるので、self
もレキシカルに解決されます。また、そのエイリアスが実行時に変更されず、サブクラスによっても変更されない(サブクラスで明示的に変更する場合を除く)ことがAPI使用者に明確に伝わります。
class Westerner
def first_name
@names.first
end
alias given_name first_name
end
Since
alias
, likedef
, is a keyword, prefer bareword arguments over symbols or strings. In other words, doalias foo bar
, notalias :foo :bar
.
Also be aware of how Ruby handles aliases and inheritance: an alias references the method that was resolved at the time the alias was defined; it is not dispatched dynamically.
Rubyのalias
はdef
などと同様キーワードなので、メソッド名やエイリアス名は文字列やシンボルではなく、むき出しで記述するようにします。
また、Rubyでエイリアスや継承がどのように扱われているかについても知っておきましょう。Rubyの#alias
によるエイリアス参照は、エイリアスの定義時に解決されます(動的には解決されません)。
class Fugitive < Westerner
def first_name
'Nobody'
end
end
In this example,
Fugitive#given_name
would still call the originalWesterner#first_name
method, notFugitive#first_name
. To override the behavior ofFugitive#given_name
as well, you'd have to redefine it in the derived class.
上述の例のFugitive#given_name
呼び出しは、実際にはFugitive#first_name
ではなく元のWesterner#first_name
を呼び出します。この動作をオーバーライドするには、以下のように派生クラスでalias
を使って再定義する必要があります。
class Fugitive < Westerner
def first_name
'Nobody'
end
alias given_name first_name # aliasしないと`Westerner#first_name`が呼ばれる
end
5-21【統一】モジュールやクラスやシングルトンクラスのメソッドを実行時にエイリアスする場合は常にalias_method
を使う
Always use
alias_method
when aliasing methods of modules, classes, or singleton classes at runtime, as the lexical scope ofalias
leads to unpredictability in these cases.
前述のとおり、alias
はレキシカルなので定義時に参照が解決されます。以下のような動的なエイリアス作成にalias
を使っても期待どおりに動作しません。
module Mononymous
def self.included(other)
other.class_eval { alias_method :full_name, :given_name }
end
end
class Sting < Westerner
include Mononymous
end
5-22【統一】クラスやモジュールの中で同じクラスやモジュール内の他のメソッドを呼ぶ場合は、呼び出しのself.
を省略する
When class (or module) methods call other such methods, omit the use of a leading
self
or own name followed by a.
when calling other such methods. This is often seen in "service classes" or other similar concepts where a class is treated as though it were a function. This convention tends to reduce repetitive boilerplate in such classes.
self.*
やメソッド名.*
を省略する記法は、いわゆる「サービスクラス」のようにクラスを関数のように扱う場合によく見られます。この省略記法は、そうしたクラス内での冗長な呼び出しを削減するのに役に立ちます。
class TestClass
# 不可 -- クラス名を変えるかメソッドを移動する方がよい
def self.call(param1, param2)
TestClass.new(param1).call(param2)
end
# 不可 -- 冗長
def self.call(param1, param2)
self.new(param1).call(param2)
end
# 良好
def self.call(param1, param2)
new(param1).call(param2)
end
# ...(他のメソッド定義)...
end
今回は以上です。スタイルガイドを読むシリーズ、次回は「例外処理」編をお送りします。ご期待ください。
関連記事
- Rubyスタイルガイドを読む: ソースコードレイアウト(1)エンコード、クラス定義、スペース
- Rubyスタイルガイドを読む: ソースコードレイアウト(2)インデント、記号
- Rubyスタイルガイドを読む: 文法(1)メソッド定義、引数、多重代入
- Rubyスタイルガイドを読む: 文法(2)アンダースコア、多重代入、三項演算子、if/unless
- Rubyスタイルガイドを読む: 文法(3)演算子とif/unless
- Rubyスタイルガイドを読む: 文法(4)ループ
- Rubyスタイルガイドを読む: 文法(5)ブロック、proc
- Rubyスタイルガイドを読む: 文法(6)演算子など
- Rubyスタイルガイドを読む: 文法(7)lambda、標準入出力など
- Rubyスタイルガイドを読む: 文法(8)配列や論理値など
- Rubyスタイルガイドを読む: 命名
- Rubyスタイルガイドを読む: コメント、アノテーション、マジックコメント
- Rubyスタイルガイドを読む: クラスとモジュール(1)構造
- Rubyスタイルガイドを読む: クラスとモジュール(2)クラス設計・アクセサ・ダックタイピングなど
- Rubyスタイルガイドを読む: クラスとモジュール(3)クラスメソッド、スコープ、エイリアスなど
- Rubyスタイルガイドを読む: 例外処理
- Rubyスタイルガイドを読む: コレクション(Array、Hash、Setなど)
- Rubyスタイルガイドを読む: 数値、文字列、日時(日付・時刻・時間)
- Rubyスタイルガイドを読む: 正規表現、%リテラル、メタプログラミング(最終回)