📖

複雑性の本質とは - a philosophy of software design を読んで

2024/12/28に公開

A Philosophy of Software Design を読んで理解を深める記事

この記事は、A Philosophy of Software Design を読んで、自分なりに理解した内容を人に説明することで、さらに理解を深めることを目的としています。間違って理解している点などがあれば、ぜひご指摘いただけると嬉しいです。

この記事では、

Chapter 2: The Nature of Complexity
2.1 Complexity defined

について僕なりに理解したことを説明しています。


複雑性とは何なのか

この本では、ソフトウェアの設計において複雑性を最小にする方法について述べていますが、そもそも複雑性とは何なのかを理解しないと始まりません。また、システムが不必要に複雑になっている状態を認識する能力も重要です。

この章では、複雑性について抽象的なレベルで説明しています。この後の章では、より具体的な話が続くようです。


複雑性を認識する能力

複雑性を認識する能力は非常に重要です。これがあれば、システムが複雑すぎることを見抜き、よりシンプルな方向へ設計を進めることができます。

設計が複雑であると分かった場合には、他の設計案を試してみて、どれがよりシンプルになるかを確認することができます。


複雑性の定義

本書では、複雑性を以下のように定義しています。

複雑性とは、ソフトウェアシステムの構造に関連し、そのシステムの理解と変更を困難にする要素のすべてを指す

この定義に基づくと、理解と変更が難しいソフトウェアは複雑であり、理解しやすく変更が容易なソフトウェアはシンプルと言えます。

コード量と複雑性の関係

  • コード量が多いからといって複雑であるとは限らない
    理解と変更が容易であれば、コード量が多くてもシンプルといえます。
  • コード量が少なくても複雑な場合がある
    理解や変更が困難であれば、コード量が少なくても、それは複雑だと判断されます。

どれくらい頻繁に使われる場所なのか

システム内で、ある一部分が非常に複雑だったとしても、その部分がほとんど触られることがない場合、全体の複雑性にはほとんど寄与しません。

ソフトウェア全体の複雑性を表す式

C = \sum_{p} c_p t_p

この式は、ソフトウェア全体の複雑性 (C) を、各部分の複雑性 (Cp) と、その部分に対して開発者が費やす時間 (tp) の積を足し合わせたものとして表現しています。

この考え方に基づくと、どれだけ複雑なコードであっても、開発者がほとんど触らない部分であれば、ソフトウェア全体の複雑性に与える影響は小さいよね、ということです。


具体例:日数計算ロジック

以下のコードは、startDateendDate を渡すことで差分の日数を計算するロジックです。

// 日数計算ロジック(複雑だが一度作ればほとんど触らない部分)
function calculateDaysBetween(startDate: string, endDate: string): number {
  console.log("日数計算を実行中...");
  const start = new Date(startDate);
  const end = new Date(endDate);

  if (isNaN(start.getTime()) || isNaN(end.getTime())) {
    throw new Error("無効な日付フォーマットです");
  }

  const millisecondsPerDay = 24 * 60 * 60 * 1000; // 1日のミリ秒数
  const differenceInMilliseconds = end.getTime() - start.getTime();

  if (differenceInMilliseconds < 0) {
    throw new Error("終了日は開始日より後である必要があります");
  }

  return Math.ceil(differenceInMilliseconds / millisecondsPerDay); // 日数を返す
}

このコードは、日数計算の方法が頻繁に変更される可能性は低いと考えられるため、たとえ複雑であってもソフトウェア全体の複雑性にはほとんど影響を与えません。

このロジックを使う側ではこんな感じになるかなと思います。

// プロジェクトの情報表示ロジック(頻繁に触られる部分)
function displayProjectInfo(projectName: string, duration: number): void {
  console.log(`プロジェクト「${projectName}」の期間は ${duration} 日です。`);
}

// メインロジック
function main() {
  const projectName = "ウェブサイトリニューアル";

  // 日付データ(開始日と終了日)
  const startDate = "2024-12-01";
  const endDate = "2024-12-31";

  try {
    // 日数計算(この部分は複雑だが一度作れば再利用される)
    const duration = calculateDaysBetween(startDate, endDate);

    // プロジェクト情報の表示(頻繁にデザイン変更される可能性がある)
    displayProjectInfo(projectName, duration);
  } catch (error) {
    console.error("エラー:", error.message);
  }
}

main();

複雑だったとしても、頻繁に変更されると想定される部分と、うまく切り離されていれば、問題にはならないよね、ということですね。

複雑性は「読むとき」に明らかになる

コードの複雑性は、「書くとき」よりも「読むとき」に明らかになります。

自分ではわかりやすいと思って書いたコードでも、他の人が読んで「分かりにくい」と感じることがあります。このような場合、それは複雑と考えるべきです。

プルリクでのレビュー時に、このコードは何をやってるのかという質問を受けることってありますよね。本書では、そんな時に「他人が何を複雑だと感じたか」を知ることで学びを得られる、と述べられています。

Discussion