0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プリンシプルオブプログラミングの要点整理

Last updated at Posted at 2025-08-08

プリンシプルとは

  • プログラミングの指針となる「前提」「原則」「思想」「習慣」「視点」「手法」「法則」などを指す
  • 特定の技術に特化したものではなく、抽象度の高い情報
  • プリンシプルを理解していれば、具体的な技術を学んだ時に「なぜその技術が必要なのか」を理解することができ、習得が早く、深くなる

1.前提

プログラミングに銀の弾丸はない

  • プログラミングには魔法のような解決策はない
  • これさえあれば必ずうまくいくという「特効薬」はない
  • コードは設計書である
    • 基本設計、詳細設計、プログラミング、テスト、デバッグまでが設計であり、そのアウトプットが設計書である「コード」である
  • ロゼッタストーン
    • 将来の保守担当者に対する簡潔な手引書(ドキュメント)
    • 開発環境⇒ビルド/テストプロセスをの実行方法を記述する
    • アーキテクチャ⇒コードからは読み取れない全体図を記述する
    • その他⇒設計理由を記述する
  • コードは必ず変更される
    • 障害対応や機能拡張などの「変更に強いコードを書く」
    • 書いている時間より読んでいる時間のほうが長いため、「読みやすいコードを書く」

2.原則

KISS

  • 「シンプル」「単純」「簡潔」を最優先の価値を考える
    • 本当に必要なコードだけ書く
    • 「今」必要なコードだけ書く
    • 要件にない余分なコードを書かない
  • シンプルなソフトウェアは使いやすく、結果として長く使われる

DRY

  • コードを重複して書いてはいけない
    • 量が多くなり複雑になる ⇒ 読みづらく修正が難しくなる
  • コードを抽象化する
    • 関数化、モジュール化する ⇒ 読みやすく修正が1か所で済む

SLAP

  • コードのレベルを合わせる
    • 同じところには同じ抽象度の処理を書く
    • 1つの関数の中で、データベース接続(低水準)とビジネスロジックの実行(高水準)といった不揃いな書き方をしない

OCP

  • 「拡張に対して開いている」「修正に対して閉じている」という2つの属性を同時に満たす設計
  • インターフェースを実装することで、コードの変更に柔軟に対応する

名前重要

  • 命名は最重要課題
  • 「使う側」「読む側」の視点に立って命名する

3.思想

プログラミングセオリー

プログラミングセオリーを支える3つの価値

  1. コミュニケーション
    • コードは人に見せる文書であり、読む人のことを考えて書くべき
  2. シンプル
    • コードをどうにか動かそうとした痕跡による複雑性を排除する
  3. 柔軟性
    • コードの変更が容易な設計をする
    • ただし柔軟性を言い訳に複雑なコードや設計を正当化しない

プログラミングセオリーを支える6つの原則

  1. 結果の局所化
    • 変更の影響が局所にとどまるようにコードを構成する(ex.モジュール化)
  2. 繰り返しの最小化
    • 重複を極力排除し、修正の影響を局所化する(ex.関数化)
  3. ロジックとデータの一体化
    • 「ロジック」と「ロジックが操作するデータ」は同じ関数/モジュールにまとめる
    • データと操作は修正タイミングがたいてい同じ
  4. 対称性
    • コードに一貫性を持たせる
      • アクティブがあればパッシブがあり、addがあればdeleteがあるなど
      • 関数内で呼び出す関数の抽象度をそろえる
  5. 宣言型の表現
    • 「命令型」ではなく「宣言型」で表現する
      • 命令型
        • 「どうやってやるか」を意識し、手順を細かく指示する(for文、while文)
        • 細かく制御できるが冗長になりやすい
      • 宣言型
        • 「何をしたいか」を意識し、結果を指示する(map, filter, SQL, JSX)
        • コードが簡潔で分かりやすい
    // 配列の偶数だけを集める
    // 命令型の場合
        const nums = [1, 2, 3, 4, 5];
        let evens = [];
        for (let i = 0; i < nums.length; i++) {
          if (nums[i] % 2 === 0) {
            evens.push(nums[i]);
          }
        }
        console.log(evens); // [2, 4]
    
    // 宣言型の場合
        const nums = [1, 2, 3, 4, 5];
        const evens = nums.filter(n => n % 2 === 0);
        console.log(evens); // [2, 4]
    
  6. 変更頻度
    • コードを修正するタイミングが同じ=変更理由が同じ要素をグルーピングする
    • 単一責任の原則

アーキテクチャ根底技法

パッケージ化/モジュール化/カプセル化

イメージ図
パッケージモジュールカプセル.png

関心の分離

  • 「関心 = ソフトウェアの機能や目的」ごとにコードを分離(モジュール化)する
    • 代表例はMVCで、ビジネスロジック/ユーザーへの表示/入力処理に分離している
    • 影響範囲が関心内にとどまり、変更時の品質が安定する

ポリシーと実装の分離

  • ポリシー:ソフトウェアの"前提に依存する"ビジネスロジックや引数
  • 実装:ソフトウェアの"前提に依存しない"独立したロジック部分
  • ポリシーが変更されたときに実装の変更が必要にならないよう、モジュールを分ける

インターフェースと実装の分離

  • インターフェース
    • アクセス可能な関数のシグネチャで構成
    • モジュールの使用方法を定義する
  • 実装
    • ロジックとデータで構成
    • モジュールが持つ機能を定義する
  • モジュール同士の呼び出しにはインターフェースが使用されるようにする
具体例:ユーザー情報取得サービス
// インターフェース
// IUserService.ts
export interface IUserService {
  getUser(id: number): Promise<User>;
}

export type User = {
  id: number;
  name: string;
};
// APIからユーザー情報を取得するサービス
// ApiUserService.ts
import { IUserService, User } from "./IUserService";

export class ApiUserService implements IUserService {
  async getUser(id: number): Promise<User> {
    // 実際には fetch などでAPIから取得
    console.log(`Fetching user ${id} from API...`);
    return { id, name: "Alice(API)" };
  }
}
// ローカルDBからユーザー情報を取得するサービス
// LocalUserService.ts
import { IUserService, User } from "./IUserService";

export class LocalUserService implements IUserService {
  async getUser(id: number): Promise<User> {
    console.log(`Fetching user ${id} from local DB...`);
    return { id, name: "Bob(Local)" };
  }
}
// 利用側(インターフェースだけを意識)
// main.ts
import { IUserService } from "./IUserService";
import { ApiUserService } from "./ApiUserService";
import { LocalUserService } from "./LocalUserService";

async function showUserInfo(service: IUserService, id: number) {
  const user = await service.getUser(id);
  console.log(`User: ${user.name}`);
}

(async () => {
  const apiService = new ApiUserService();
  await showUserInfo(apiService, 1); // API実装を利用

  const localService = new LocalUserService();
  await showUserInfo(localService, 2); // ローカルDB実装を利用
})();

アーキテクチャ非機能要件

  • リリース後の運用の大きなトラブルのほとんどが、パフォーマンスやシステムダウンなど、非機能な特性に起因する => 機能テストと同程度に非機能テストが重要
  1. 変更容易性(ChangeAbility)
    • 保守性
      • 簡単に修正できるか
    • 拡張性
      • 簡単に機能追加、モジュールのバージョンアップ、不要な機能の除去ができるか
    • 再構築
      • 簡単にモジュール間の関係の再組織化を行えるか、配置変更できるか
    • 移植性
      • 簡単に別のプラットフォームに移行できるか
  2. 相互運用性(InterOperability)
    • プロトコルやデータ形式の選定では、業界の標準規格を選択する
  3. 効率性(Efficiency)
    • リソースは限られているため、時間効率と資源効率を考慮する
      • 時間効率
        • スループット、レスポンスタイム、アラウンドタイムなど
      • 資源効率
        • CPU使用時間、メモリ使用量、ストレージ消費量、ネットワーク伝送量など
  4. 信頼性(Reliability)
    • 例外発生時や不正な方法で使用された場合において、機能を維持する能力
    • フォールトトレランス
      • 障害が発生したときに、正常な動作を保ち続ける能力
      • 例外発生時も正しいふるまいを保証し、内部的には修復を行う
      • システムの冗長化やフェールソフトな設計を行う
    • ロバストネス
      • 不正な使用方法や入力ミスから保護する能力
      • 例外を起こした処理は、必ずしも繰り返したり内部修復を行わない
      • フェールセーフな設計を行う
  5. テスト容易性(Testability)
    • ソフトウェアが大きくなるにつれて、テストは困難で高価なものになる
    • テストの品質=本体の品質ととらえ、小さい単位でテストが可能な設計をする
  6. 再利用性(Reusability)
    • 効率よく、品質のよい開発を行うには「できる限り作らず、借りてくる」
    • 再利用可能なモジュールを作るのは、他と比べて3倍難しい(一般化した問題を想定するため)

7つの設計原理(コードレビュー観点)

  1. 単純原理
    複雑なところにバグは出る。初心者でも読める単純なコードを書く。
  2. 同型原理
    同じことは、同じように扱う。モジュールで使用する数値の単位や引数の型を統一する。
  3. 対称原理
    アクティブがあればパッシブというように、処理の「対」になるものを考える。「set/get」「start/stop」「begin/end」など命名も一般的な対称性を考慮する。
  4. 階層原理
    リソースの獲得を行ったら同じ階層でリソースの開放を行うなど、階層構造にこだわる。
  5. 線形原理
    複雑な条件文や繰り返し文をなるべく避け、直線的な処理にこだわる。
  6. 明証原理
    不確実性を取り除き、ロジックが明瞭なコードを書く。
  7. 安全原理
    ありえないという条件をあえて考慮してコードを書く。(else, default, =NULLなど)

4.視点

凝集度

モジュールに含まれている機能の純粋さを表す尺度(参考

結合度

モジュール同士の関係の密接さを表す尺度(参考

  • その他関連性のある考え方
    • 冪等性(べきとうせい):ある操作を何回行っても結果が同じであること
    • 安全性:操作対象の状態を変化させないこと(副作用)

直行性

あるコード同志が「独立性」「分離性」を持ち、片方を変更しても他方に影響を与えない

可逆性

問題があった時、やり直しができるようにしておく

コードの臭い

コードの中で、理解しにくい、修正しにくい、拡張しにくいと感じられる部分。
臭いには以下のような傾向があげられる。

  • 関数内の処理が長すぎる
  • モジュールが担う責務が大きすぎる
  • モジュールが担う責務を分割しすぎた結果、細かいモジュールが多すぎる
  • 名前と実際のコード内容があっていない

技術的負債

「修正しにくい」「理解しにくい」といった問題のある汚いコードを技術的負債と呼ぶ。
技術的負債は素早く返すことが何よりの対策となる。仮に素早く返せない事情があれば、本来書くべきであったコードの設計をドキュメントなどに残しておくことが重要。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?