Yanonoblog!

こつこつと

メソッドチェーンのアンチパターン

はじめに

本記事では「良いコード 悪いコードで学ぶ設計入門」で学んだ内容と別途気になって調べた内容や知識も含めアウトプットしています。

書籍に記載されている内容を順序立ててまとめるというよりは、整理しておきたい周辺の知識と深ぼった内容を雑多に書いています。

書籍ではJavaで説明されていたのですが本記事ではRubyに置き換えながら解説しています。

メソッドチェーン

メソッドチェーンとは

「.」(ドット)で数珠つなぎにして、戻り値の要素に次々にアクセスする書き方をメソッドチェーンと呼びます。

Rubyでは、メソッドチェーンを活用することで、複数のメソッド呼び出しを一つの式でつなげることができます。

これにより、階層的なオブジェクトの操作や条件分岐をシンプルかつ読みやすく表現することができます。

他の言語でも用いられる一般的な記法です。

アンチパターン

ゲーム内でメンバーの装備を変更するためのメソッドです。メソッドチェーンを使用しています。

@param memberId 装備変更したいメンバーのID
@param newArmor 装備する鎧

# 鎧を装備するメソッド
def equip_armor(member_id, new_armor)
  # パーティのメンバーリストから指定されたメンバーを取得し、装備変更を行います
  party.members[member_id].equipments.armor = new_armor
end

membersから装備変更したいメンバーを取得し、さらにequipmentsで装備一覧を取得しています。

その中からさらにcanChangeを取得して装備変更可能かどうかを判断し、armorへアクセスして装備変更しています。

この例ではメソッドチェーンを使い、階層構造になっているクラスの、かなり奥深い要素にアクセスしています。この方法も低凝集に陥る、良くない書き方です。

問題点
party.members[member_id].equipments.armor = new_armor

上記のコードではarmorへ代入していますが、代入するコードをどこでも書けてしまいます。似たようなコードが複数箇所に実装される恐れがあります。

それだけではなく、membersやequipmentsなども同様です。どこでもさまざまな要素へアクセス可能となります。

たとえば、members、equipments、canChange、armorにアクセスするコードがさまざまな箇所にいくつも実装されていたとします。

これらの要素に仕様変更が生じた場合、呼び出している箇所すべての影響を調べて回らなければならなくなります。

また、バグが発生した場合も同様に、どこでバグが混入したのか呼び出し箇所をすべて調べて回らなければならなくなります。

このように、影響範囲がいたずらに拡大可能な構造なので、グローバル変数と同様の性質を帯びてきます。より多くの要素に、あらゆる箇所からアクセス可能な構造である点で、単一のグローバル変数よりも悪質だと言われています。

デメテルの法則

デメテルの法則は、オブジェクト間の関係性を制限し、各オブジェクトが自身の責任範囲内でのみ操作することを促す原則です。

本著では以下のように説明されていました。

利用するオブジェクトの内部を知るべきではない、とするもので、「知らない人に話しかけるな」と要約されたりもします。

あるオブジェクトが別のオブジェクトの内部の要素に直接アクセスするのではなく、メッセージを通じて必要な情報を取得するべきです。

デメテルの法則 - 違反例

デメテルの法則に違反しているコード例を挙げます。

class User
  attr_accessor :name, :address

  def print_address
    puts self.address.city
  end
end

class Address
  attr_accessor :city
end

user = User.new
user.address = Address.new
user.address.city = "Tokyo"

user.print_address

Userクラスのprint_addressメソッド内でself.address.cityという連鎖的なアクセスが行われています。

これにより、UserクラスがAddressクラスの内部構造に直接依存しており、デメテルの法則に違反しています。

デメテルの法則 - 改善例

Userクラス内でaddress_cityというプライベートメソッドを定義し、Addressクラスの内部を意識することなく、必要な情報を取得しています。

print_addressメソッドはUserクラスの責務に集中し、Addressクラスの詳細を意識することなく実装されています。

class User
  attr_accessor :name, :address

  def print_address
    puts address_city
  end

  private

  def address_city
    address.city
  end
end

class Address
  attr_accessor :city
end

user = User.new
user.address = Address.new
user.address.city = "Tokyo"

user.print_address

これにより、UserクラスとAddressクラスの結合度が低くなり、それぞれの変更や拡張が容易になります。また、可読性と保守性も向上します。

尋ねるな、命じろ

ソフトウェア設計には、尋ねるな、命じろ(Tell,Don'tAsk.)という有名な格言があります。

この原則によれば、オブジェクトの内部状態を尋ねたり、その状態に基づいて呼び出し側で判断したりするのではなく

呼び出し側は単に命令を行い、命じられた側が適切な判断や制御を行うべきです。

具体的には以下のような設計を行います。

  1. インスタンス変数をprivateに設定し、外部からの直接アクセスを制限します。これにより、内部状態を隠蔽し、カプセル化を実現します。
  2. インスタンス変数に対する制御は、メソッドを介して行います。外部からの命令に対して、適切な処理や判断を行うメソッドを提供します。
  3. 命令された側は、受け取った命令に応じて内部の状態や振る舞いを適切に制御します。内部の詳細な判断や処理は、命令を受けたオブジェクト自身が行うようにします。

このような設計により、オブジェクトの内部状態についての責任がオブジェクト自身に集中し、呼び出し側は単に適切な命令を出すだけで済みます。

これにより、コードの結合度が低くなり、変更への影響範囲が小さくなるため、柔軟性と保守性が向上します。

先程のコードをリファクタリング

最初のコードを本著のやり方に沿って改善した例を紹介します。

equip_armorメソッドでは、装備変更のために新しい鎧を受け取り、can_changetrueである場合にのみ鎧を装備します。

これにより、防具の着脱に関するロジックがEquipmentsクラス内に凝集されます。

deactivate_allメソッドでは、全装備を解除するために各防具をEquipment.EMPTY(空の防具)に設定しています。

# 装備中の防具一覧を表現するEquipmentsクラス
class Equipments
  private

# can_change => 装備の変更が可能かどうかを表すフラグ
  attr_accessor :can_change, :head, :armor, :arm # head、armor、arm => ****それぞれ頭部防具、鎧、腕部防具を表す

  public # 公開する必要があるメソッドだけをpublicにすることで、情報の隠蔽やカプセル化を実現

  # 防具の装備
  def equip_armor(new_armor)
    self.armor = new_armor if can_change
  end

  # 全装備解除
  def deactivate_all
    self.head = Equipment.EMPTY # **Equipment.EMPTY**のように定数を定義しておくことで、空の防具を表す値を共通化できる
    self.armor = Equipment.EMPTY
    self.arm = Equipment.EMPTY
  end
end

このような設計により、防具の着脱に関するロジックがEquipmentsクラスに集約されます。

防具の仕様が変更された場合、Equipmentsクラスに注目することで修正や変更時に、他の箇所を探し回る必要がなくなるため保守性や柔軟性が向上します。

参考

続く…

コメント

本記事の内容は以上になります!

書籍の続きのアウトプットも随時更新したいと思います。


プログラミングスクールのご紹介 (卒業生より)

お世話になったプログラミングスクールであるRUNTEQです♪

https://runteq.jp/r/ohtFwbjW

こちらのリンクを経由すると1万円引きになります。

RUNTEQを通じて開発学習の末、受託開発企業をご紹介いただき、現在も双方とご縁があります。

もし、興味がありましたらお気軽にコメントか、TwitterのDMでお声掛けください。

https://twitter.com/outputky