Rubyのクラスメソッドをclass << selfで定義している理由(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

Rubyのクラスメソッドをclass << selfで定義している理由(翻訳)


https://pixnio.com/nature-landscapes/winter/landscape-sky-winter-snow-ice-water-tree-nature-outdoor-reflectionより

クラスメソッドは私の同僚の間で常に議論や反論の種になっています。クラスメソッドは的確かつ有用と考える人もいますが、実際にはコードの読みやすさや管理のしやすさを損ないがちな邪魔者だと感じる人もいます。私はRubyのオブジェクト指向的な本質を信奉しており、オブジェクトで考えることを(読むのも!)好んでいますが、私には後者が真実になる傾向があることに気づきました。よく言われるように、クラスメソッドがどうしても必要になることもあります。ファクトリーメソッドから、ActiveRecordモデルのカスタムクエリメソッドで使われる複雑なメタプログラミングインターフェイスにいたるまで、クラスメソッドを全否定することはできません。もちろんクラスメソッドの利用は控えめにすべきではありますが(詳しくはCode Climateのこちらの良記事をご覧ください)。

本記事ではクラスメソッドそのものの良し悪しについては言及せず、クラスメソッドが必要になった場合のクラスメソッドのスタイル上の記法について議論します。

コーディングスタイルとスタイルガイド

Rubyスタイルガイドでは、クラスメソッドはdef self.methodという記法を用いるのが望ましいとされています。このスタイルでは、より明示的な def ClassName.methodという記法が批判されていますが、より謎めいたclass << selfという記法も補助的にサポートされています。記法の実際の表示については該当のセクションをご覧ください。

組織内でスタイルガイドを共有し、それに従うことは重要です。Sandi Metz氏の良記事では次のように指摘されています。

(略)スタイル上の選択は多くの場合任意であり、純粋に個人の好みの問題です。スタイルガイドを選択することは、ほとんど重要でない点で意見が割れてしまった場合の合意を形成するということです。スタイル(そのもの)が重要だということではなく、スタイルが揃うことが重要です
Why we argue styleより

この指摘は実にもっともです。しかし、スタイルを正しく選択できることもやはり重要です。服装と同じく、コードのスタイルには開発者としての信条や価値観や哲学が反映されるのですから、この問題についても十分理解が必要です。私たちの誰もが多くのクラスメソッドを定義していますが、果たして私たちはクラスメソッドの動作を理解しているのでしょうか。


https://images.askmen.com/1080×540/2015/11/06-042951-men_s_fashion_must_haves.jpgより

シングルトンクラス

上の問いに答えるために、Rubyのオブジェクトモデルについて取り急ぎ調べる必要があります。一般に、Rubyのメソッドはクラスに保存され、データはオブジェクト(クラスのインスタンス)に保存されます。これはかなり一般的な知識なので、次の例でもう少し追ってみましょう。

an_array = [1, 5, 10]

an_array.averageを実行すると、Arrayやそのスーパークラスにaverageメソッドが定義されていないので、次のようにNoMethodErrorエラーが出力されます。

an_array.average
# NoMethodError: undefined method `average' for [1, 5, 10]:Array

Arrayにモンキーパッチを適用してaverageメソッドを定義してもよいのですが、このメソッドはan_array以外では不要なのであれば、次のようにすることもできます。

def an_array.average
  reduce(:+) / count.to_f
end

これで次のように動きます。

an_array.average
# => 5.333333333333333

同じメソッドをArrayの別のインスタンスで実行しようとすれば、次のようにまたNoMethodErrorが出力されます。

another_array = [1, 3, 7]
another_array.average
# => NoMethodError: undefined method `average' for [1, 3, 7]:Array

この理由は、Rubyが舞台裏でaverageメソッドを特殊なクラスに保存しているからです。an_arrayだけがその特殊なクラスを指しています。つまり自身のシングルトンクラスです。

an_array.singleton_class
# => #<Class:#<Array:0x007fcf27848750>>

an_array.singleton_methods
# => [:average]

Rubyでは、どんなクラスのどんなインスタンスにも必ず自身のシングルトンクラスがあり、そこにシングルトンメソッドが保存されます。先ほど定義したメソッドもシングルトンメソッドであり、シングルトンクラスに保存されています(ただし例外として、Numericオブジェクトはこれに該当しません)。

あるオブジェクトのメソッドを呼び出すと、Rubyは最初にそのオブジェクトのシングルトンクラスでメソッドを探索し、それから通常のクラスや先祖クラスのチェインを探索します。

クラスメソッドはすなわちシングルトンメソッドである

Rubyではクラスもオブジェクトなので、クラスメソッドはClassの特定のインスタンス上に定義された単なるメソッドです。次の例で考えてみましょう(gist)。

class Example
  def self.a_class_method; end
  def an_instance_method; end
end

実際の動作を見れば理屈はすぐわかります。

Example.is_a? Object
# => true

Example.class
# => Class

Example.singleton_class
 => #<Class:Example>

Example.instance_methods(false)
 => [:an_instance_method]

Example.singleton_class.instance_methods(false)
 => [:a_class_method]

instance_methodsの引数をfalseで呼び出すと、継承されたメソッドを除いたメソッドリストが返されます(instance_methods)。

記法を選ぶ

ついにRubyのクラスメソッドを正確に理解できました。これでコーディング方法について詳しく議論する準備が整いました。本記事のタイトルでおわかりのように、私はdef self.method記法よりclass << self記法を好んでいます。理由はおわかりでしょうか?

私はメソッドを、それらが属しているクラスの内部で定義したいのです。class << self記法ならこのアプローチ、すなわちメソッドを実際のシングルトンクラスのスコープ内で定義していることがはっきりと伝わります。しかしdef self.methodを使うと、複数のスコープにわたってメソッドを定義していることになります。通常のクラススコープの中にいるにもかかわらず、コードのどの場所でも特定のインスタンスにメソッドを定義できるRubyの機能を使っているのです。クラス定義内におけるselfは、その場所における現在の(クラス自身などの)Classインスタンスを指します。このため、def self.methodを使うとスコープを飛び越えることになり、私にはこれがおかしいと感じられるのです。

def self.method記法にはもうひとつ疑問があります。その理由は、privateやprotectedなメソッドを定義できることです。Rubyには、クラスメソッドをprivateとして宣言するためのprivate_class_methodメソッドが用意されていますが、protectedメソッドについては同等のものがありません。また、privateなクラスメソッドでは、各メソッドを個別に宣言しなければなりません(たとえば、クラスの途中で単純にprivateを用いることはできません: これはそのクラスのインスタンスメソッドに適用されるからです)。要は、class << selfの方が実際にはより明確だということです。

予想される反論

Sandi Metzの記事で指摘されているように、スタイルに関する議論はときとして開発者の間で感情的にこじれてしまうことがあります。class << self記法について予想される反論として次のようなものが考えられます。

  • 大きなクラスだとどれがクラスメソッドだかわかりにくい: 私もその点にはまったく同意です。コード量が450行もある神クラスなら、まずリファクタリングしてより小さなクラスに分解しつつdef self.method記法は変えないようにすべきでしょう。いくらスクロールしても終わらないほどコードが多ければ、スコープが変わる場所を簡単に見落としてしまいます。しかしそれでもそのクラスはリファクタリングすべきです

  • 明快でなくなる: これは一方的な見方ですし、根拠も不十分です。def self.methodはまったくもって明快ではありません。前述のようにdef self.methodの本当の意味を理解できれば、def self.methodはスコープが混じるために実際には曖昧になってしまいます。この動作の背後にある理論を把握することが理解の助けになります。

  • 素直にスタイルガイドに従えばいいんじゃね?: これに対する私の回答は、動作の詳細に注意を向けることが多くの場合重要だということです。これは重大な問題ではありませんし、def self.methodは「コードの匂い」にも該当しません。実際、これ自体議論になるかどうかというトリビアな問題です。それぞれの選択肢について動作を学んだり知識を得たりすることで、初めて議論に値打ちが生まれます。さらに、class << self記法は私にとってよりよいものであり、RubyやRubyのオブジェクト指向を安定して理解できると信じています。最後に、スタイルガイドは変更/改訂される(ただし慎重にですが)可能性があることもどうかお忘れなく。

本記事が皆さんにとって有益であることを願っています。このトピックについてお気づきの点やご質問がありましたらコメント欄までどうぞ。本記事が面白い/有益だと思っていただけた方は、ぜひ👏ボタンをクリックして応援してください。

関連記事

【保存版】Rubyスタイルガイド(日本語・解説付き)総もくじ

Railsの`Object#try`がダメな理由と効果的な代替手段(翻訳)

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の書いた記事

BPSアドベントカレンダー

週刊Railsウォッチ

インフラ

BigBinary記事より

ActiveSupport探訪シリーズ