2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「現場で役立つシステム設計の原則」 CHAPTER2 場合分けのロジックを整理する まとめ

2
Last updated at Posted at 2022-04-14

目次

1.区分ごとのロジックはプログラムを複雑にするやっかいな存在
2.早期リターンやガード節を使うと区分ごとのロジックをわかりやすく整理できる
3.区分ごとに別のクラスに分けると独立性を高めることができる
4.多態を使うと、区分ごとのロジックをif文/switch文を使わずに記述できる
5.Javaの列挙型(enum)は多態をシンプルに記述するしくみ・列挙型を活用すると、区分ごとの業務ロジックをわかりやすく整理して記述できる
6.列挙型を活用すると、区分ごとの業務ロジックをわかりやすく整理して記述できる

1.区分ごとのロジックはプログラムを複雑にするやっかいな存在

■区分や種別がコードを複雑にする

・顧客区分
・料金種別
・商品分類
・地域区分
・製品タイプ

区分や分類は、対象ごとに異なる業務ルールを適用するために使われます。
区分や分類の組み合わせがif文やswitch文などの分岐がどんどん複雑化します。
このような複雑になりがちな、区分ごとの業務ルールのコードをすっきりと整理して、
変更のやりやすいプログラムにするにはどう設計すればよいか? 以下でテクニックを紹介します。

■判断や処理のロジックをメソッドに独立させる

・コードのかたまりは、メソッドとして抽出して独立させる
・関連するデータとロジックは、1つのクラスにまとめる
まず、コードのかたまりを、メソッドとして抽出して独立させる方法から見ていきましょう。

// Before
// 【悪い例】 判断や処理のロジックをそのままif文の中に書く
if(customerType.equals("child")) {
  fee = baseFee * 0.5;
}

// After
// 【良い例】 メソッドに抽出する
if(isChild()){
  fee = childFee();
}

// 判断ロジックの詳細
private Boolean isChild() {
  return customerType.equals("child");
}
 
// 計算ロジックの詳細
private int childFee() {
  return baseFee * 0.5;
}

子供を判断する式をisChild()メソッドで定義します。子供料金の計算をchildFee()メソッドで定義します。
そうすると、もともとのif文は、メソッドを呼び出すだけのシンプルな表現になります。
メソッド名を読むだけで何をやろうとしているか明確です。


2.早期リターンやガード節を使うと区分ごとのロジックをわかりやすく整理できる

■else句をなくすと条件分岐が単純になる

場合分けのコードを整理するもうひとつのやり方は、else句を書かないことです。
else句は、プログラムの構造を複雑にします。else句をできるだけ書かないほうがプログラムが単純になります。
下記は、「子供」「大人」「シニア」で別料金にするロジックを、else句を使って書いた例です。

// Before
// else句を使った書き方(悪い例)
Yen fee(){
  Yen result;
  if(isChild()){
    result = childFee();
  }elseif(isSenior()){
    result = seniorFee();
  }else{
    result = adultFee();
  }
  return result;
}

// After
// 早期リターンやガード節を用いた書き方(良い例)
Yen fee(){
  if(isChild()) return childFee();
  if(isSenior()) return seniorFee();

  return adultFee();
}

  • 早期リターンとは ・・・ else句を使わず条件分岐内でリターンすること
  • ガード節とは ・・・ else句を使わずに早期リターンする書き方

■複文は短文に分ける

文の中に文を書く「複文」は、日本語の文章であれ、プログラミング言語の記述であれ、意図をわかりにくくします。そこで、複文を分解して、単文を並べるシンプルな構造に変えます。

// 「幼児」区分'isBaby()'を追加
Yen fee(){
  if(isBaby()) return babyFee();
  if(isChild()) return childFee();
  if(isSenior()) return seniorFee();
  return adultFee();
}

このように、単文を並べた方式であれば、もう1つ単文(「幼児」区分)を追加するだけで済む。


3.区分ごとに別のクラスに分けると独立性を高めることができる

■区分ごとのクラスを同じ「型」として扱う

区分ごとにクラスを分けると、ロジックの整理はしやすくなります。
しかし、クラスを使う側は、AdultFee型とChildFee型をいつも意識して使い分けなければいけません。
インターフェース宣言を使えば、異なるクラスを同じ型として扱うことができます。

// AdultFeeクラスとChildFeeクラスをFee型として宣言する
interface Fee{
  Yen yen();
  String label();
}

class AdultFee implements Fee{
  Yen yen(){
    return new Yen(100);
  }
  String label(){
    return "大人";
  }
}

class ChildFee implements Fee{
  Yen yen(){
    return new Yen(50);
  }
  String label(){
    return "子供";
  }
}

Fee型を使うと、料金を計算する側のコードはこうなります。

class Charge{
  Fee fee;
  Charge(Fee fee){
    this.fee = fee;// feeはAdultFee型またはChildFee型どちらでもよい
  }
  Yen yen(){
    return fee.yen();
  }
}

Chargeクラスのオブジェクトを生成する時に、コンストラクタにFee型のどのクラスのオブジェクトを渡すかによって、
Chargeクラスの振る舞いが変わります。コンストラクタに渡すオブジェクトがAdultFee型であれば大人料金を計算します。
ChildFee型であれば子供料金の計算をします。Chargeクラスは、AdultFee型とChildFee型の違いを意識しません。
どちらの型のオブジェクトもFee型のオブジェクトとして扱います。


4.多態を使うと、区分ごとのロジックをif文/switch文を使わずに記述できる

■区分ごとのクラスを同じ「型」として扱う 続き...

子供連れの団体の料金の合計

class Reservation{
  List<Fee> fees; //大人と子供の内訳は不明

  Reservation(){
    fees = new ArrayList<Fee>()
  }

  void addFee(Feefee){ //大人でも子供でも追加できる
    fees.add(fee);
  }

  Yen feeTotal(){
    Yentotal = new Yen() //合計ゼロ円
    for(Fee each: fees){
      total.add(each.yen());
    }
    return total;
  }
}

大人料金と子供料金の場合分けのif文は登場しません。Reservationクラスは、
大人と子供を意識しないでFee型のオブジェクトの料金を合計しているだけです。
このようにインターフェース宣言(Fee)と、区分ごとの専用クラス(AdultFee/ChildFee)を組み合わせて、区分ごとに異なるクラスのオブジェクトを「同じ型」として扱うしくみを多態と呼びます。
多態を使うと区分ごとに異なる判断/加工/計算のロジックをすっきりと整理できます。
区分ごとのロジックを別のクラスに分けて記述すれば、どのクラスに何が書いてあるか特定しやすく、変更が楽で安全になります。
if文/switch文を駆使して場合分けを記述する手続き型のプログラムは、変更がやっかいで危険です。
それに比べ、多態を使ったオブジェクト指向らしい区分の書き分け方は、変更を楽で安全にします。

■区分ごとのクラスのインスタンスを生成する

多態は、利用する側のコードをシンプルにします。しかし、区分ごとのクラスのインスタンスを生成するときには、if文で場合分けが必要になりそうです。しかし、ちょっとした工夫でこのif文は不要になります。

// Mapを使った、if文を使わずに区分ごとのオブジェクトを生成するやり方の例
class FeeFactory{
  static Map<String,Fee> types;

  static {
    types = new HashMap<String, Fee>();
    types.put("adult",new AdultFee());
    types.put("child",new ChildFee());
  }

  static Fee feeByName(String name){
    return types.get(name);
  }
}

5.Javaの列挙型(enum)は多態をシンプルに記述するしくみ

■Javaの列挙型を使えばもっとかんたん

// 列挙型の使い方

//料金区分の定義
enum FeeType{
  adult,
  child,
  senior
}

//区分を使う側のクラス
class Guest{
  FeeType type;

  boolean isAdult(){
    return type.equals(FeeType.adult);
  }
}

このように、区分定数の一覧を宣言する列挙型は、Java以外の言語でも用意されています。しかし、Javaの列挙型は単純な区分定数ではありません。Javaでは列挙型もクラスです。区分ごとの値をインスタンス変数として保持したり、区分ごとのロジックをメソッドとして記述できたりします。たとえば、料金区分を、列挙型と多態を組み合わせて次のように書くことができます。

// 料金区分ごとのロジックをenumを使って表現する
enum FeeType{
  adult(new AdultFee()),
  child(new ChildFee()),
  senior(new SeniorFee());
  
  private Fee fee;
  //Feeインターフェースを実装したどれかのクラスのオブジェクト

  private FeeType(Fee fee){
    this.fee = fee; //料金区分ごとのオブジェクトを設定する
  }

  Yen yen(){
    return fee.yen();
  }

  String label(){
    return fee.label();
  }
}

このように料金区分に関するロジックを整理しておけば、区分名を指定して、区分ごとの料金を計算できます。

// 料金区分名から料金を計算するメソッドの例
Yen feeFor(String feeTypeName){
  FeeType feeType = FeeType.valueOf(feeTypeName);// たとえば、"adult"                              
  
  return feeType.yen();
}

EnumクラスのvalueOf()メソッドは、if文を使わずにタイプ名から区分ごとのオブジェクトを取得できる便利でわかりやすい方法です。列挙型を使って、区分ごとのロジックをわかりやすく整理するこの方法を
区分オブジェクトと呼びます。
区分定数を単なる定数ではなく、振る舞いを持ったオブジェクトとして表現します。「振る舞いを持つ」というのは、メソッドを指定して判断/加工/計算を依頼できるという意味です。
この振る舞いを持った区分オブジェクトをうまく使うことで、区分ごとのif文/switch文でごちゃごちゃしがちなコードをすっきりと見通しよく整理できます。
そして、区分ごとのロジックをそれぞれ別のクラスに独立させて整理することで、区分の追加や、区分ごとのロジックの変更が楽で安全になります。

6.列挙型を活用すると、区分ごとの業務ロジックをわかりやすく整理して記述できる

おわり

用語集

  • 早期リターン ・・・ else句を使わず条件分岐内でリターンすること
  • ガード節 ・・・ else句を使わずに早期リターンする書き方
  • 多能 ・・・ 区分ごとに異なるクラスのオブジェクトを「同じ型」として扱うしくみを多態と呼ぶ
  • 区分オブジェクト ・・・ 列挙型を使って、区分ごとのロジックをわかりやすく整理するこの方法を区分オブジェクトと呼ぶ

書き残し

  • 状態遷移(表)
from/to 審査中 承認済 差し戻し中 実施中 中断中 終了
審査中 - 承認 差し戻し - - -
承認済 - - - 開始 - 取り下げ
差し戻し中 再申請 - - - - 取り下げ
実施中 - - - - 中断 完了
中断中 - - - 再開 - 中止
終了 - - - - - -
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?