🐥

ソフトウェアの設計とは?

に公開

はじめに

要件に対してソフトウェアを設計するとき、機能要件(やりたい機能の実現、Function Requirement、以下FR)だけであれば、どんな作り方(構造、Architecture)であっても実現できてしまいます。
しかし実際には、熟達したエンジニアであるほど思いつきだけで実装はせず、設計という作業を行います。
なぜでしょう?

なぜ設計が必要なのか?

身近な例で考えてみます。

  • 家を建てるとき:「雨風をしのげる」だけなら段ボールでもできる
  • でも実際は:耐久性、快適性、美観、コスト…を考えて設計する
  • ソフトウェアも同じ:「動く」だけなら何でもできる、けど…

設計でなにをしているのか?

よくある間違った理解は以下です。

  • 設計 = 設計書を書くこと
  • 設計者 = クラス図やシーケンス図を描く人
    本来あるべき設計は、下記の作業です。
  • 設計 = 問題を解決するための「考える」行為
  • 設計者 = 制約の中でベストな答えを見つける人

料理に例えると、

  • レシピ(設計書)を書くことが目的ではない
  • 限られた材料・時間・予算で、おいしい料理を作る「方法を考える」ことが設計

実際のソフトウェアでイメージすると、

  • 機能を実現する(=料理を作る)
  • でも条件がある:
    • 速く動かしたい(Performance)
    • 後で変更しやすくしたい(Maintainability)
    • バグを出したくない(Reliability)
    • コストを抑えたい(Cost)

設計の実例

実装例:電卓プログラム

ケース1:ひとつのクラスですべて実装

  public class Calculator {
      public void calculate(String input) {
          // 入力チェック
          if (input == null || input.isEmpty()) {
              System.out.println("エラー: 入力がありません");
              return;
          }

          // 計算処理
          String[] parts = input.split(" ");
          double num1 = Double.parseDouble(parts[0]);
          String operator = parts[1];
          double num2 = Double.parseDouble(parts[2]);

          double result = 0;
          switch (operator) {
              case "+": result = num1 + num2; break;
              case "-": result = num1 - num2; break;
              case "*": result = num1 * num2; break;
              case "/": result = num1 / num2; break;
          }

          // 結果表示
          System.out.println(num1 + " " + operator + " " + num2 + " = " + result);

          // ログ保存
          System.out.println("[LOG] 計算実行: " + input + " = " + result);
      }
  }

ケース2:複数のクラスに分けて実装

  // 入力チェック専用
  public class InputValidator {
      public boolean isValid(String input) {
          return input != null && !input.isEmpty();
      }
  }

  // 計算処理専用
  public class MathOperations {
      public double add(double a, double b) { return a + b; }
      public double subtract(double a, double b) { return a - b; }
      public double multiply(double a, double b) { return a * b; }
      public double divide(double a, double b) { return a / b; }
  }

  // 結果表示専用
  public class ResultDisplay {
      public void show(double num1, String operator, double num2, double result) {
          System.out.println(num1 + " " + operator + " " + num2 + " = " + result);
      }
  }

  // ログ記録専用
  public class CalculationLogger {
      public void log(String input, double result) {
          System.out.println("[LOG] 計算実行: " + input + " = " + result);
      }
  }

  // メイン処理
  public class Calculator {
      private InputValidator validator = new InputValidator();
      private MathOperations math = new MathOperations();
      private ResultDisplay display = new ResultDisplay();
      private CalculationLogger logger = new CalculationLogger();

      public void calculate(String input) {
          // 入力チェック
          if (!validator.isValid(input)) {
              System.out.println("エラー: 入力がありません");
              return;
          }

          // 計算実行
          String[] parts = input.split(" ");
          double num1 = Double.parseDouble(parts[0]);
          String operator = parts[1];
          double num2 = Double.parseDouble(parts[2]);

          double result = switch (operator) {
              case "+" -> math.add(num1, num2);
              case "-" -> math.subtract(num1, num2);
              case "*" -> math.multiply(num1, num2);
              case "/" -> math.divide(num1, num2);
              default -> 0;
          };

          // 結果表示とログ
          display.show(num1, operator, num2, result);
          logger.log(input, result);
      }
  }

なぜ分けるのか?

問題(ケース1)

  • ログの出力方法を変えたい → 計算処理も一緒に触る必要がある
  • 新しい演算子を追加したい → 他の処理も理解する必要がある

利点(ケース2)

  • 変更しやすい: ログだけ変えたいならCalculationLoggerだけ修正
  • テストしやすい: 計算処理だけテストしたいならMathOperationsだけテスト
  • 理解しやすい: 各クラスの役割が明確

まとめ

設計とは:同じ機能でも「将来の変更・テスト・理解」など、複数の問題や利点を考えて構造を決めること

Discussion