😵

Rubyアンチパターン: なんでもクラスメソッド

2022/03/13に公開

(Ruby初学者向けの啓蒙記事です。「クラスメソッドはクラスクラスのインスタンスの特異メソッドで云々」みたいな話が得意な人は対象外です。ご了承ください。)

クラスメソッドは便利なこともありますが、使いすぎるとよくないコードになってしまいます。

少なくともRubyでは、クラスメソッドは比較的特殊なもので、普通のメソッドはインスタンスメソッドである、と考えるくらいのほうがいいと思います。

典型的な症状

  • クラス内に定義されているインスタンスメソッドがほとんどない、あるいはまったくないにも関わらず、クラスメソッドがたくさん定義されていたりします
  • クラスメソッドからクラスメソッドの呼び出しが多用されたりします
  • 上記と合わせて、クラスメソッドの引数が増えたりします

生じる問題

  • どこからでも呼べてしまう

インスタンスメソッドはそのメソッドが所属するクラス(or そのサブクラス群)のオブジェクトからしか呼べませんが(まあ当然ですが)、クラスメソッドはどこからでも呼べてしまいます。
それはそれで便利かもしれませんが、関係ないところから呼ばれたり、関係ないクラスに属したりしかねません。それでは使いやすいクラス群になりません。

  • クラスの責務があいまいになる(なっている)

「責務」というのは役割分担みたいなやつです。
結局のところ、設計を見直した方がよい場合が多そうです。

解決策

  • 適切なクラスのインスタンスメソッドにしましょう

クラスメソッドを使う場合でも、インスタンスを作ってからそのインスタンスに対してメソッドを実行するようにします(下記「Rubyのクラスメソッドがリファクタリングに抵抗する理由」参照)。
「適切なクラス」の候補としては、そのクラスメソッドの引数のオブジェクトが所属するクラスが有力ですが、それだけとは限りません。

  • 適切なクラスがない場合は、必要に応じて別途クラスを作り、そのクラスのメソッドにしましょう

クラスが足りていない状況で無理やりメソッドを定義しようとしたため、クラスメソッドになってしまっているのかもしれません。必要に応じて新しいクラスを作ることも大事です。

例外

  • コンストラクタ(新しいオブジェクトを生成するメソッド)

new以外のコンストラクタをクラスメソッドとして実装するやり方はイディオム的によく使われています。Foo.create_with_bar(.....)みたいなものです。
コンストラクタでオブジェクトを生成した後は、そのオブジェクトのインスタンスメソッドを使えばOKです。

ただし、コンストラクタについても、あまりに複雑になりそう(何十行にもなるとか、メソッドを分割したくなるとか)場合には、コンストラクタ用の別クラスを用意することを検討してください。

  • 呼び出し用便利メソッド

下記「Rubyのクラスメソッドがリファクタリングに抵抗する理由」にあるように、便利な呼び出し用メソッドを1つ定義しておく手法もよく使われます。
呼び出し用のメソッド以外は(当然ながら)インスタンスメソッドとして実装します。

ただしこの場合についても、ちょっと便利になるだけというメリットしかなさそうであれば、都度newした方が使い方もシンプルで一貫性もある形になるかもしれません。

あわせて読みたい

必要なことはだいたいこの記事に書かれていました…(翻訳ありがとうございました)。

関連するアンチパターン

Discussion