Yanonoblog!

こつこつと

クラスの適切な設計2

はじめに

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

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

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

クラスベースのオブジェクト指向設計

クラスを定義してそのクラスからオブジェクトを作成することで、プログラムの振る舞いやデータの操作を行います。

データとそのデータを操作するロジックをクラスにひとまとめにし、プログラムの構造を定義していく手法です。クラスが構造の基本単位となります。

Rubyもまたクラス定義や継承、メソッドの定義などオブジェクト指向の機能をサポートしています。

クラス単体で正常に動作するよう設計する

クラスは単体で正常に動作するよう設計し、煩雑な初期設定を必要とせずにすぐに利用できるようにする必要があります。

また、クラスが不正状態に陥ったりバグを生じたりしないように、外部に提供するのは正しく操作できるメソッドのみにするのが基本です。

本著での例えがわかりやすかったため引用します。

ドライヤーには電源のオン/オフスイッチと、風量調節スイッチ、冷風か熱風を切り替えるスイッチが用意されています。これらのスイッチを切り替えるだけで正常に風量や温度を調整可能です。使用者の些細な操作ミスによってドライヤーが破損するほどの風量や温度になることはありません。これらの製品は、それ自体単体で正常に動作するよう設計されています。まどろっこしい初期設定をしたり、ほかの部品と組み合わせないとまともに使えなかったり、ということは基本的にはありません。また、操作方法も、製品が破損しない操作手段が提供されています。

自己防衛責務を持ったクラスが重要です。各クラスは自身の品質を保証し、安全に利用できるようにすることが求められます。個々のクラスが品質的に完結していることで全体の品質が向上します。

適切な初期化ロジック

デフォルトコンストラクタを使用せず、Moneyクラスにすべてのインスタンス変数を初期化できる引数を持ったコンストラクタを実装する例です。

class Money
  def initialize(amount, currency)
    @amount = amount
    @currency = currency
  end
end

これだけでは不十分で、引数に不正な値を渡すことができてしまいます。例えば、次のように不正な値を渡してしまうと、バグが発生する場合があります。

money = Money.new(100, nil)

不正な値が含まれたままプログラムが動作すると、予期しない結果やエラーが発生する可能性があります。

コンストラクタ内にバリデーションを実装し不正な値の場合は例外を発生させるようにします。

正常な値のルールを定義
  • 金額(amount)は0以上の整数であること
  • 通貨(currency)はnil以外であること

これらのバリデーションをコンストラクタに実装した例が以下になります。

class Money
  def initialize(amount, currency)
    validate_amount(amount)
    validate_currency(currency)

    @amount = amount
    @currency = currency
  end

  private

  def validate_amount(amount)
    raise ArgumentError, "金額は0以上の整数である必要があります" unless amount.is_a?(Integer) && amount >= 0
  end

  def validate_currency(currency)
    raise ArgumentError, "通貨はnil以外である必要があります" if currency.nil?
  end
end

このようにバリデーションを実装することで、不正な値が渡された場合に例外が発生し問題を早期に検出できるようになります。

ガード節

上記で解説したコードは初期化の時点でバリデーションのメソッドを呼び出して、例外を洗い出していました。

メソッドの先頭で処理対象外となる条件を定義する方法をガード節といいます。

  def initialize(amount, currency)
    validate_amount(amount)     # バリデーション&処理対象外は例外を返すメソッドを先頭で呼び出す
    validate_currency(currency) # バリデーション&処理対象外は例外を返すメソッドを先頭で呼び出す

ガード節を使用することで、不要な要素を先頭で除外し、後続のロジックをシンプルにすることができます。

コンストラクタにガード節を導入することで、さらなる利点があります。

不正な値が渡されるとコンストラクタで例外がスローされます。これにより、不正な値を持つMoneyクラスのインスタンスが存在できなくなります。

ガード節を使用することで、初期化ロジックがシンプルになり、不正状態に陥る可能性が減ります。正常なインスタンスのみが生成され、安全に利用できるようになります。

参考

続く…

コメント

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

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


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

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

https://runteq.jp/r/ohtFwbjW

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

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

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

https://twitter.com/outputky