🐥
ソフトウェアの設計とは?
はじめに
要件に対してソフトウェアを設計するとき、機能要件(やりたい機能の実現、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