😵

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

2022/03/12に公開約1,400字

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

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

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

典型的な症状

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

生じる問題

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

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

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

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

解決策

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

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

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

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

例外

  • コンストラクタ

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

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

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

あわせて読みたい

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

関連するアンチパターン

  • 神クラス
  • なんでもユーテリティクラス

Discussion

ログインするとコメントできます