give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

【プログラミング初心者向け】クラスメソッドとインスタンスメソッドはどう使い分けるべき?

はじめに

ruby-jpのSlackで以下のような質問が投稿されていました。

クラスメソッドとインスタンスメソッドの具体的な違いがわかりません。
現状「クラスメソッドはクラスから実行でき全体に関する処理を書くときによく使うもの。インスタンスメソッドはインスタンスから実行でき、個別具体的な処理を書くときに使うもの。」という理解をしています。そして実装の際に「これはクラスメソッドとインスタンスメソッドどちらで書くべきなのか」悩むケースが多いです。
上記を踏まえて質問です。

  • クラスメソッドとインスタンスメソッドの具体的な違いを皆さんはどのように定義しているか
  • どこからがクラスメソッドでどこからがインスタンスメソッドなのかの境目はどのあたりにあるか

をお伺いしたいです!

クラスメソッドとインスタンスメソッドの使い分けは僕がメンターをやっているフィヨルドブートキャンプでもよく見かける質問です。

そこで僕が考えるクラスメソッドとインスタンスメソッドの使い分けを次のように説明してみました。

なお、これはプログラミング歴があまり長くなく、オブジェクト指向プログラミングも最近勉強し始めたばかり、という初心者さんを対象にしています。

クラスメソッドとインスタンスメソッドの使い分け

初心者向けのざっくりとした指針を示すなら、「クラスメソッドとインスタンスメソッドの使い分けに迷ったら、とりあえずインスタンスメソッドにしておけ」と答えます。

その上で、「もしインスタンスを引数なしでnewした直後に何かメソッドを呼び出している場合は、それはクラスメソッドの候補かもしれない」と考えると良いです。

たとえばこんな感じですね。

# 引数なしでnewした直後にメソッドを呼んでいる
Hoge.new.do_something

# このコードもやってることは上と同じ
hoge = Hoge.new
hoge.do_somehting

# do_somethingは実はクラスメソッドが適しているかも(100%必ず、というわけではない)
Hoge.do_somehting

このとき、do_somethingがインスタンス変数(@nameなど)や、Railsならモデルの属性(emailメソッドなど)に依存している(=読み書きしている)場合はインスタンスメソッドのままにすべきですが、そうでなければクラスメソッドの方が向いています。

もちろん、細かい話をすると「ケースバイケースなので、まずコードを見せてください」となるのですが、オブジェクト指向初心者さんなら上のような指針で大きく外さないんじゃないかと考えています。

上の説明のもう少し具体的な例

以下は「クラスメソッドでいいのでは?」と思われるメソッドの例です。

calculator = Calculator.new
calculator.add(2, 3) #=> 5
calculator.add(4, 5) #=> 9

class Calculator
  def add(a, b)
    a + b
  end
end

インスタンス変数を全く使ってないので、この場合は次のようにクラスメソッドにしても問題ないですね。

Calculator.add(2, 3) #=> 5
Calculator.add(4, 5) #=> 9

class Calculator
  def self.add(a, b)
    a + b
  end
end

一方、次の場合はどうでしょうか?

calculator = Calculator.new
calculator.add(2, 3) #=> 5
calculator.add(4, 5) #=> 9
# これまでの計算履歴を返す
calculator.history   #=> ["2 + 3", "4 + 5"]

class Calculator
  attr_reader :history

  def initialize
    @history = []
  end

  def add(a, b)
    @history << "#{a} + #{b}"
    a + b
  end
end

これは履歴をインスタンス変数にため込んでいくのでインスタンスメソッドのままにしておく必要があります。
(クラスメソッドとクラスインスタンス変数にすれば・・・という議論もできますが、ややこしいのでここでは無視します)

クラスメソッドが向いているパターン3つ

さらに、RubyやRailsで実際に存在しているクラスメソッド(特異メソッド)を参考にして、「こういうメソッドはクラスメソッド向きであることが多い」というパターン3つ挙げてみます。

いずれも「インスタンスメソッドで定義するとなんか不自然」と思うようなものばかりなので、とりあえずインスタンスメソッドで作ってみて、違和感を感じたらクラスメソッド化を検討する、というようなアプローチでも良いかもしれませんね。

パターン1:関数的なメソッド

ここでいう関数的とは、数学のy = f(x)を満たすようなメソッド、つまり、入力値xに対して毎回必ずyを戻り値として返す特性を指します。

たとえばDate.valid_date?などが関数的なメソッドに該当します。

# 日付として有効であればtrue、そうでなければfalseを返す
Date.valid_date?(2022, 6, 30)  #=> true
Date.valid_date?(2022, 6, 31)  #=> false

もしこれがインスタンスメソッドになっていると不自然に思いませんか?

# dateは今日の日付を表すインスタンス
date = Date.today
# なんで「今日の日付」が他の日付の妥当性を検証しなきゃいけないの?
date.valid_date?(2022, 6, 30)
date.valid_date?(2022, 6, 31)

パターン2:新しいインスタンスを生成するメソッド

新しいインスタンスを生成する場合、そもそも「インスタンスがない状態」から出発しなきゃいけないので、自ずとクラスメソッドになります。

# 文字列をパースしてDateオブジェクトを生成する
Date.parse("2022-06-30") #=> #<Date: 2022-06-30 ((2459761j,0s,0n),+0s,2299161j)>

もしこれがインスタンスメソッドになっていると不自然に思いませんか?

# dateは今日の日付を表すインスタンス
date = Date.today
# なんで「今日の日付」が他の日付を生成しなきゃいけないの?
date.parse("2022-06-30")

ちなみにnewメソッドも「新しいインスタンスを生成するクラスメソッド」と考えることができますね。

# newも無から新しいインスタンスを生成するクラスメソッド
Date.new #=> #<Date: -4712-01-01 ((0j,0s,0n),+0s,2299161j)>

Railsのfindメソッドやscopeなんかもその一種だと考えられます。

# DBにアクセスして新しいインスタンスを生成(復元)する
User.find(id) #=> (指定したidのDB上のUserが返る)
User.all      #=> (DB上の全Userが返る)
User.active   #=> (activeなDB上のUserが返る)

パターン3:そのクラスの複数のインスタンスを扱うメソッド

これはRubyやRailsのメソッドで該当するものが見当たらなかったので、架空のメソッドで説明します。

# carsのデータをExcelファイルに出力する(架空のメソッド)
Car.export_as_xlsx(cars)

もしこれがインスタンスメソッドになっていると不自然に思いませんか?

# carは「ぷりうす」を表すインスタンス
car = Car.new('ぷりうす')
# なんで「ぷりうす」が他のcarのデータをExcel出力しなきゃいけないの?
car.export_as_xlsx(other_cars)

このほかにも「DSLのように振る舞うクラスメソッド(例: Railsの belongs_tohas_many 等)」もありますが、初心者が自分で定義することは少ない&インスタンスメソッドにするのがそもそも困難だと思うのでここでは割愛します。

RubyやRailsのクラスメソッドを分析すると勉強になるかも

今回はRubyやRailsが提供しているメソッドを中心に取り上げましたが、僕が業務でクラスメソッドを定義する場合もだいたいこのパターンのいずれかに合致しているように思います。

今回やったようにRubyやRailsのAPIドキュメントを眺めて、どういう目的でクラスメソッドが定義されているのかを自分なりに分析したりすると、クラスメソッドの使いどころがだんだんとわかってくるかもしれません。

まとめ

というわけで、今回は僕が考えるクラスメソッドとインスタンスメソッドの使い分けについて説明してみました。

オブジェクト指向的に適切なクラス設計がされていれば、クラスメソッドが登場する回数はインスタンスメソッドに比べてずっと少ないはずです。

一方、オブジェクト指向らしくないクラス設計だと、「クラスメソッドでもインスタンスメソッドでもどっちでも構わない」というメソッドが増えやすくなります。特に、オブジェクト指向に不慣れな人が作ったクラスは、すべてクラスメソッドに置き換え可能(関数的メソッドばかり)というケースもよくありますね。

ただ、このあたりの考え方はなかなか独学で身に付けるのはなかなか難しいので、先輩プログラマにコードレビューを受けたりしながら学ぶのが一番よかったりします。僕自身も初心者だった頃は先輩プログラマの指導を受けながらオブジェクト指向の考え方を学びました。

なので、身近に頼れる先輩がいる初心者さんは、積極的にコードレビューを依頼していきましょう!

あわせて読みたい

かなり昔に書いた記事で対象言語もJavaが中心ですが、僕が昔読んだオブジェクト指向関連の技術書を以下のエントリにまとめています。

blog.jnito.com

あと、こちらも古い記事ですが、オブジェクト指向関連で特に役に立った書籍を数冊ピックアップして紹介しています。

blog.jnito.com

【PR】フィヨルドブートキャンプならコードレビューしてもらえるよ

僕がメンターをやっているフィヨルドブートキャンプにはオブジェクト指向プログラミングのプラクティスも用意されています。

学習内容 | FJORD BOOT CAMP(フィヨルドブートキャンプ)

僕を含む経験豊富なメンターが受講生のみなさんが書いたプログラムをしっかりコードレビューするので、身近に頼れる先輩がいない場合はフィヨルドブートキャンプで学習することも検討してみてください😃

bootcamp.fjord.jp

【PRその2】「プロを目指す人のためのRuby入門」もよろしくお願いします

オブジェクト指向を専門に扱う本ではありませんが、拙著「プロを目指す人のためのRuby入門(通称・チェリー本)」でも自作クラスの作り方を丁寧に説明しています。

そもそもクラスを定義する構文やRubyの言語機能がよくわかっていない、という方はぜひ「プロを目指す人のためのRuby入門」でRubyについて学んでみてください!