サイト内を検索

YAGNIとOCPを守ることで柔軟性と拡張性を保つシンプルな設計

こんにちは。

私はバックエンドエンジニアとして、主にJava、Kotlin、そしてSpring Bootを使用したシステム開発を行っている、Oです。

本記事では、YAGNIとOCPの原則を守ることでシンプルで拡張性の高い設計を実現する方法を解説します。

ソフトウェア設計において、YAGNI(You Aren’t Gonna Need It)とSOLID原則の一つであるOCP(Open-Closed Principle)は、シンプルで効果的なアーキテクチャを作るための重要な原則です。

これらの原則はそれぞれ独立していますが、一緒に守ることでシンプルで拡張性の高い設計が実現し、システムが将来の変更にも柔軟に対応できるようになります。

はじめに

YAGNI(You Aren’t Gonna Need It)とは

「現在必要のない機能を作らない」という原則です。

将来必要になるかもしれないという理由で機能を先取りして作ってしまうことがよくありますが、これが過剰な設計を招き、コードを複雑にしメンテナンスが難しくなる原因となります。

OCP(Open-Closed Principle)とは

「モジュールは拡張に対して開いて (Open) おり、修正に対して閉じて (Closed) いなければならない」という原則です。

コードの拡張は可能でありながら、既存の動作に影響を与えないようにすることで、システムの安定性と柔軟性を保つことです。

つまり、既存のコードに手を加えることなく、新しい機能や変更を追加できるように設計することを意味します。

 

アンチパターン

それでは実際のコードでアンチパターンを見てみましょう。
このコードはエアコンのオン/オフ機能を表したものです。

以下の機能をすべて一つのクラスに詰め込むと、どのような問題が発生するでしょうか?

      • 確定機能
        1.リモコンによるエアコンのオン/オフ
        2.スケジュールによるエアコンのオン/オフ
        • 将来的に予想される追加機能
          1.設定された温度に到達したらエアコンをオン/オフ

           

          @Service
          @RequiredArgsConstructor
          public class AirConditionerService {
          
              private final RemoteControlService remoteControlService;
              private final ScheduleControlService scheduleControlService;
              private final TemperatureControlService temperatureControlService; // 将来的に使うかもしれない機能であり、現在は未実装のクラス
          
              // リモコンによるエアコンのオン/オフ
              public void controlWithRemote(boolean isTurnOn) {
                  if (isTurnOn) {
                      remoteControlService.turnOn();
                  } else {
                      remoteControlService.turnOff();
                  }
              }
          
              // スケジュールによるエアコンのオン/オフ
              public void controlWithSchedule(boolean isTurnOn) {
                  if (isTurnOn) {
                      scheduleControlService.turnOn();
                  } else {
                      scheduleControlService.turnOff();
                  }
              }
          
              // 温度によるエアコンのオン/オフ(将来的に使うかもしれない過剰な実装)
              public void controlWithTemperature(boolean isTurnOn) {
                  // TODO: 機能開発が決定された場合に処理を書く。現時点では仮の実装。
                  if (isTurnOn) {
                      temperatureControlService.turnOn();
                  } else {
                      temperatureControlService.turnOff();
                  }
              }
          }
          
          

          YAGNIに違反

          現時点で「温度によるエアコンのオン/オフ」が必要ないにもかかわらず、最初から仮の実装がされています。

          将来的に必要になる機能を予測して最初に組み込んでしまうことで、過剰設計となりシステムが複雑化し、メンテナンス性が低下します。

           

          OCP違反のリスクがある

          もし「温度によるエアコンのオン/オフ」が不要になり、「AIによる季節や使用状況に応じたエアコンのオン/オフ」を追加したい場合、AirConditionerServiceクラスに手を加えなければならなくなります。

          例えば、AIControlServiceをフィールドに追加し、controlWithAI()メソッドを組み込む必要が出てきます。

          つまり、コードを修正することによって機能追加を余儀なくされるわけです。
          このような場合にOCP違反となります。

           

          ベストプラクティス

          それでは改善されたコードを見る前に、もう一度それぞれの原則のポイントだけ抑えておきましょう。

          • YAGNI原則を守るためには、現在必要な機能だけを実装し将来的な機能は必要になったときに追加する。
          • OCPを守るためには、新しい機能を追加する際に既存コードを変更することなく、拡張できる設計にする。

           

          これらの原則を踏まえてコードを考えてみましょう。

          デザインパターンの一つ、Strategyパターンを使用してエアコン制御の機能を拡張可能にしたコード例です。

          ※本記事ではStrategyパターンの詳細説明は割愛しますが、Strategyパターンとは異なるS動作(戦略)を動的に切り替えるデザインパターンであり、システムに柔軟性と拡張性を提供します。

          この設計は、今後新しい機能(温度、AIやその他機能)を追加する際に、ControlStrategyインターフェースに従った新しいクラスを追加するだけで済むため、AirConditionerServiceクラスを修正することなく機能追加が可能です。

          // ControlStrategy インターフェースを定義
          public interface ControlStrategy {
              void turnOn();
              void turnOff();
          }
          
          // リモコンによるエアコンのオン/オフ
          @Component
          public class RemoteControlService implements ControlStrategy {
              @Override
              public void turnOn() {
                  // エアコンオンの処理
              }
          
              @Override
              public void turnOff() {
                  // エアコンオフの処理
              }
          }
          
          // スケジュールによるエアコンのオン/オフ
          @Component
          public class ScheduleControlService implements ControlStrategy {
              @Override
              public void turnOn() {
                  // エアコンオンの処理
              }
          
              @Override
              public void turnOff() {
                  // エアコンオフの処理
              }
          }
          
          // 設定された温度に到達したらエアコンをオン/オフ
          // 必要になった時に作成すれば良い
          @Component
          public class TemperatureControlService implements ControlStrategy {
              @Override
              public void turnOn() {
                  // エアコンオンの処理
              }
          
              @Override
              public void turnOff() {
                  // エアコンオフの処理
              }
          }
          

          次にAirConditionerServiceクラスでは機能を表す処理を引数として受け取り、それに基づいてエアコンを制御します。
          異なる機能(例えば、リモコン操作やスケジュール)を外部から渡すことで、エアコンの制御方法を柔軟に切り替えることができ、AirConditionerService自体は機能に依存せず、拡張性が高くなります。

          @Service
          public class AirConditionerService {
          
              // 機能(Strategy)を引数として受け取る
              public void turnOnAirConditioner(ControlStrategy controlStrategy) {
                  controlStrategy.turnOn();
              }
          
              // 機能(Strategy)を引数として受け取る
              public void turnOffAirConditioner(ControlStrategy controlStrategy) {
                  controlStrategy.turnOff();
              }
          }
          

          使用方法の例

          @Service
          @RequiredArgsConstructor
          public class RemoteControlAirConditioner {
          
              private final AirConditionerService airConditionerService;
              private final RemoteControlService remoteControlService;
          
              // リモコンを使ってエアコンをオンにする
              public void turnOnWithRemote() {
                  airConditionerService.turnOnAirConditioner(remoteControlService);
              }
          
              // リモコンを使ってエアコンをオフにする
              public void turnOffWithRemote() {
                  airConditionerService.turnOffAirConditioner(remoteControlService);
              }
          }
          

          もし、「AIによる季節や使用状況に応じたエアコンのオン/オフ機能」を追加したい場合、ControlStrategyに従って、AIControlServiceクラスを作成すれば良いのです。

          // AIによる季節や使用状況に応じたエアコンのオン/オフ
          @Component
          public class AIControlService implements ControlStrategy {
              @Override
              public void turnOn() {
                  // エアコンオンの処理
              }
          
              @Override
              public void turnOff() {
                  // エアコンオフの処理
              }
          }
          

          終わりに

          YAGNIとOCPという原則を守ることで、シンプルで拡張性の高いシステムを作る方法について解説しました。
          過剰な機能の実装や、変更を加えるたびに既存のコードを修正するような設計は、後々のシステムの柔軟性や保守性に大きな影響を与えます。

          日々の開発において、こうした原則を意識して実践していくことが、より良いソフトウェア作りに繋がります。
          ぜひ、これらの原則を活かして、次のプロジェクトに取り組んでみてください。
          最後までお読みいただき、ありがとうございます。

          Tag

          Category

          Tag