データクラスについての個人的まとめ

2022/11/15に公開

設計パターンなどについて1から学んでいる若造の入門メモ的なノリで書いています🙇‍♂️
参考:「良いコード・悪いコードで学ぶ設計入門」

データクラスとは

  • データを保持するだけのクラスのこと
public class ContractAmount {
  public int amountIncludingTax;  // 税込み金額
  public BigDecimal salesTaxRate; // 消費税率
}

上記データクラスの問題点

  • メソッドとデータが低凝集になることによる下記に挙げるような弊害が多数発生してしまう。

問題点:仕様変更に弱くなる

なぜデータを保持するだけの一見単純なクラスがバグの温床になるのか?

1. 重複コードの発生

  • 関連するコード同士が離れていると関連するメンバ(変数とメソッド)の把握が困難になり実装済みのメソッドをもう一つ誤って実装したりするミス(重複コード)が起こりうる。

2. 修正漏れの危険性

  • 重複コードがあると、仕様変更時に全てのコードを変更しないといけない。そこで変更が入った場合、修正忘れが起こりうるので、バグの元になる。

3. 可読性の低下

  • 関連するコードが離れていると上記のように仕様の変更が入った場合に、重複コードを見つけたりメンバ同士の関連を把握したいのに把握しにくかったりして、可読性が低下する。無駄な労力がかなり増えることにつながってしまう。

また、以下のことにもつながってしまう

未初期化状態を許容してしまう(生焼け)

public class ContractAmount {
  public int amountIncludingTax;  // 税込み金額
  public BigDecimal salesTaxRate; // 消費税率
}
  • ContractAmountクラス(データクラス)にはコンストラクタがないのでインスタンス化時に初期化忘れが起きる可能性が高まってしまう。
ContractAmount amount = new ContractAmount(); //インスタンス化したものの
System.out.println(amount.salesTaxRate.toString());
//ContractAmountクラスのsalesTaxRateは初期化しない限りnull

//→ContractAmountクラスが初期化しないといけないクラスであることを
//インスタンス利用側が覚えていないといけないが膨大なコードがある中で
//それはほぼ不可能なのでバグの元になる。

→このクラス(ContractAmount)はクラスのフィールド変数の初期化を呼び出し元、つまり外部に任せってしまっている。よって何回か呼び出すような処理になればなるほどその都度初期化確認をしてあげないといけない「不完全なクラス」となってしまう。利用側が注意して使わないといけないものは「欠陥」であるということ。

  • これらの未初期化状態オブジェクト=アンチパターン「生焼けオブジェクト」と呼ぶ

不正値の混入を許してしまう可能性が高まる

  • 「不正」= 「仕様として」正しくない状態のこと

例としては、

  • 値段がマイナス
  • 現在HPが最大HPを超えている

など。

ContractAmount amount = new ContractAmount(); 
amount.salesTaxRate = new BigDecimal("-0.1"); 
// 不正な初期値を弾く設計を忘れたりした時点でドボンでーす
// 結局「呼び出し側」で何か操作をしてやっと使えるクラスは「欠陥品」ですと言うことだ

結局これもデータクラス内で不正値を弾くコンストラクタが無いために、「呼び出し元」で気を遣って不正値を弾かないといけない「不完全なクラス」であると言えるし、毎度毎度忘れず呼び出し元でバリデーションを張ったところでいろんなところで重複コード、低凝集、修正漏れなど上記の多くのバグの元になる可能性が大いにあるのでダメ。

データクラスを脱却し堅牢で完全なクラスにするには

  • フィールド変数・それらを用いた関連メソッド・コンストラクタを「クラス内に」持たせる
  • 関連メソッドは高凝集にするために必ずそのクラス内に定義する
  • 不正値を弾くようなバリデーションは必ず「クラス内」に張る

結論と考察

  • 外部の処理に任せることなく自分のクラス内で完結させること

が結論として大切

商品も何か部品をつけないと動かなかったりしないと動かない、とかだと不便だし、変な手順を踏んで壊すこともある。
まあ当然っちゃ当然で、何回も使い回すメソッドは一つのクラスにまとめておきたいし、
「何回もクラス外でバグの温床を潰す(そしていつか忘れてバグる)」よりも「一つの完成したクラスを作りそれをどこでも気兼ねなく使う」方が使う労力、手間などが全く違うよねって。

基本、設計パターンと呼ばれるもの(DI/値オブジェクト/完全コンストラクタなど、、)はこの「クラス単体」で完全に使えてバグのない状態を目指しているものだと認識している。

この手の設計について勉強し始めたとこなのでどんどんコメントくださいませ🙏

GitHubで編集を提案

Discussion