Yanonoblog!

こつこつと

if文とelse句のネストを解消する

はじめに

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

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

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

if文が多重にネストした構造

アンチパターン

下記は条件が多重に存在する要件のコードです。

RPGではプレイヤーがメンバーに行動を指示し、魔法を発動するまでの条件分岐コードになります。

# 生存しているか判定
if member.hit_point > 0
  # 行動可能かを判定
  if member.can_act?
    # 魔法力が残存しているかを判定
    if magic.cost_magic_point <= member.magic_point
      member.consume_magic_point(magic.cost_magic_point)
      member.chant(magic)
    end
  end
end
  1. memberhit_pointが0より大きいかどうかを判定し、敵の攻撃を受けて戦闘不能になっている場合は処理を終えます。
  2. can_act?メソッドで状態異常によって戦闘不能になっている場合は処理を終えます。
  3. magicの消費魔力がmemberの魔法力よりも少ないかどうかを判定し、魔法を使える場合はブロック内の処理を実行します。
  4. 魔法力が残存している場合、if文の中の処理が実行されます。memberの魔法力を消費し、magicを唱える処理を実行します。
問題点

プロジェクトの規模が大きくなるにつれて下記のようにネストが深くなったり、処理が大量に必要になるケースもあります。

if 条件
  # 数十~数百行に及ぶ何かの処理
  if 条件
    # 数十~数百行に及ぶ何かの処理
    if 条件
      # 数十~数百行に及ぶ何かの処理
    end
    # 数十~数百行に及ぶ何かの処理
  end
  # 数十~数百行に及ぶ何かの処理
end

そうなるとどこからどこまでがif文の処理ブロックなのか、読み解くのが難しくなり、コードの見通しが悪くなります。

また、変更や追加が必要な場合、関連するすべてのif文を理解して修正する必要があるため、手間がかかり、ミスのリスクも高まります。

コードを読みに来た人すべてが理解に時間を浪費しまい、チーム全体で開発生産性が低下してしまうのです。

改善例: 早期リターン

早期returnとは、条件を満たしていない場合に、returnで結果を返して処理を終了する手法です。

先程のコードに早期returnを適用します。

# 生存していない場合、処理を終了する
return if member.hit_point <= 0

# 早期returnへの変更には、条件を反転させる
# 生存していない場合には処理が続行されないため、条件を反転させて「生存している場合」の条件とする
if member.can_act?
  # 魔法の消費魔力がメンバーの魔力ポイント以下の場合に処理を実行する
  if magic.cost_magic_point <= member.magic_point
    member.consume_magic_point(magic.cost_magic_point)
    member.chant(magic)
    # ここから先の処理を記述する
  end
end

最初のreturn文では、メンバーが生存していない場合に処理を終了することを示しています。

このようなコードの置き換えにより、条件分岐が分かりやすくなり、処理の意図が明確になります。

また、早期returnを利用することで、必要のない処理をスキップし、パフォーマンスの向上を図ることができます。

このようなコードの置き換えにより、条件分岐が分かりやすくなり、処理の意図が明確になります。

また、早期returnを利用することで、必要のない処理をスキップし、パフォーマンスの向上を図ることができます。

他の処理も早期リターンにしたコードです。

# メンバーの体力が0以下の場合、処理を終了する
return if member.hit_point <= 0

# メンバーが行動できない場合、処理を終了する
return unless member.can_act?

# メンバーの魔力ポイントが魔法の消費魔力よりも少ない場合、処理を終了する
return if member.magic_point < magic.cost_magic_point

# 魔力ポイントを消費し、魔法の詠唱を行う
member.consume_magic_point(magic.cost_magic_point)
member.chant(magic)

条件ロジックと実行ロジックを分離できるという利点もあります。

else句を早期returnに置き換え

else句も、見通しを悪化させる要因のひとつです。

アンチパターン

値の範囲に応じて状態を切り替える際にelse句が使われがちで、rubyのコードにすると以下のようになります。

hit_point_rate = member.hit_point.to_f / member.max_hit_point

# メンバーの体力割合に応じて適切な健康状態を返す
if hit_point_rate == 0
  return HealthCondition.dead
elsif hit_point_rate < 0.3
  return HealthCondition.danger
elsif hit_point_rate < 0.5
  return HealthCondition.caution
else
  return HealthCondition.fine
end
改善例

こちらも条件分岐のネストと同様に、早期returnにより解決できます。各ifブロック内の処理を、returnに置き換えます。

hit_point_rate = member.hit_point.to_f / member.max_hit_point

return HealthCondition.dead if hit_point_rate == 0
return HealthCondition.danger if hit_point_rate < 0.3
return HealthCondition.caution if hit_point_rate < 0.5

HealthCondition.fine

returnで返してしまえば、else句は不要になります。

参考

続く…

コメント

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

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


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

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

https://runteq.jp/r/ohtFwbjW

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

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

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

https://twitter.com/outputky