プログラミングセオリーの価値と原則【6つの原則編】
この記事の目次
はじめに
前回の記事では、プログラミングセオリーを支える3つの価値「コミュニケーション」「シンプル」「柔軟性」について詳しく解説しました。今回は、それらの価値を実際のコードに反映させるための6つの原則について掘り下げていきます。これらの原則は、最高のコードを作り上げるための具体的なガイドラインとなります。
1. 結果の局所化 (Local Consequences)
変更の影響を抑える
「結果の局所化」とは、コードの変更が他の部分に与える影響を最小限に抑えることを指します。モジュール化はこの目的のために生まれた技術の一つです。
修正と確認が容易に
コードがモジュール化されていないと、ある部分の変更がまったく別の部分に影響を与える可能性が高くなります。結果、変更コストが急増します。影響範囲が明確であれば問題ありませんが、ほとんどの場合は不明瞭なため、影響範囲を探す作業が必要になります。
関係が濃密なコードをまとめる
関係性の高いコードを密集させ、関係性の低いコード同士が依存しないようにしましょう。以下のような方法があります:
- 適切なモジュールに機能を寄せる
- モジュール機能を統合する
- 別の観点のモジュールを作成し、機能をそちらに抽出する
TypeScriptの例:
// Good: モジュール化して影響範囲を限定する module UserModule { export interface User { id: number; name: string; email: string; } export function createUser(name: string, email: string): User { return { id: Date.now(), name, email }; } export function updateUserEmail(user: User, newEmail: string): User { return { ...user, email: newEmail }; } }
この例では、ユーザーに関する機能を一つのモジュールにまとめることで、変更の影響を局所化しています。
2. 繰り返しの最小化 (Minimize Repetition)
重複を排除する
「繰り返しの最小化」とは、コード内の重複を極力排除することを意味します。関数化はこの目的のために生まれた技術の一つです。
修正の影響を局所化する
繰り返しのコードは、変更コストを増大させます。同じ機能を複数の場所にコピー&ペーストすると、一つの変更が全ての場所に波及する可能性があります。
コードを分割して管理
コードを小さな部分に分割することで、共通項が導きやすくなります。そして、コード内でどこが完全に同じで、どこが似ているのかを明確にします。
TypeScriptの例:
// Good: 重複を排除し関数化する function calculateTax(amount: number, rate: number): number { return amount * rate; } function calculateTotalAmount(price: number, taxRate: number): number { const tax = calculateTax(price, taxRate); return price + tax; }
この例では、税金計算を関数化し、重複を排除しています。
3. ロジックとデータの一体化 (Logic and Data together)
データとその操作は近くに
「ロジックとデータの一体化」とは、ロジックとそのロジックが操作するデータを近くに置くことを意味します。
データと操作は修正タイミングが同じ
ロジックとそのロジックが操作するデータは、たいてい同じタイミングで修正されます。したがって、それらがコード上の同じ場所にあれば、変更コストを抑えることができます。
データと操作を同じ場所に
ロジックとデータをコード上で近くに配置します。いったん仮配置して、後から妥当な場所に移し替えていく手順が効率的です。
TypeScriptの例:
// Good: データとロジックを一体化する class User { constructor(public id: number, public name: string, public email: string) {} updateEmail(newEmail: string): void { this.email = newEmail; } } const user = new User(1, 'John Doe', 'john@example.com'); user.updateEmail('john.doe@example.com');
この例では、Userクラスにデータとロジックを一体化させています。
4. 対称性 (Symmetry)
コードに一貫性を持たせる
「対称性」とは、同じ考え方がコードのどの場所に現れても同じように表現されることを意味します。同じ種類のことは、同じように表現します。
ほかの部分も類推できる
対称性があると、コードの一部を読むだけでその他の部分も類推できるようになります。これはコードの読みやすさを向上させます。
同じことは同じ表現で
同じ種類のことは、同じように表現します。例えば、「追加」メソッドがあれば「削除」メソッドも作成する、同じ引数を取る関数をグループにまとめるなど。
TypeScriptの例:
// Good: 対称性を持たせる class List<T> { private items: T[] = []; add(item: T): void { this.items.push(item); } remove(item: T): void { const index = this.items.indexOf(item); if (index !== -1) { this.items.splice(index, 1); } } getAll(): T[] { return this.items; } }
この例では、リストに対する「追加」と「削除」のメソッドを対称的に定義しています。
5. 宣言型の表現 (Declarative Expression)
宣言型でプログラミング
「宣言型の表現」とは、コードの意図を伝えるために命令型よりも宣言型で表現することを意味します。宣言型プログラミングは、解決すべき問題の性質や制約を記述します。
フローがないと読みやすい
宣言型のコードは順序や条件分岐がなく、純然たる事実が書かれているため読みやすくなります。
宣言型を取り入れる
宣言型のコードを取り入れることで、シンプルに意図を表現できます。関数型言語やDSL(ドメイン特化言語)を利用するのも一つの方法です。
TypeScriptの例:
// Good: 宣言型の表現 const numbers = [1, 2, 3, 4, 5]; // 命令型 let sum = 0; for (let i = 0; i < numbers.length; i++) { sum += numbers[i]; } // 宣言型 const sum = numbers.reduce((acc, num) => acc + num, 0);
この例では、reduceメソッドを使って宣言型の表現を取り入れています。
6. 変更頻度 (Rate of Change)
変更理由でグルーピング
「変更頻度」とは、コードを修正するタイミングが同じ要素をグループ化することを意味します。変更理由が同じ要素を同じ場所にまとめることで、コードの変更範囲を狭くします。
変更範囲が狭くなる
変更理由が同じコードは関連性が高いため、グループ化すると高い凝集性を持つ堅牢なコードになります。
変更理由で所属を決める
変更するタイミングが同じ要素は同じ場所に、違う要素は別の場所に置きます。これはロジックにもデータにも適用されます。
TypeScriptの例:
// Good: 変更頻度を考慮した設計 class UserService { private users: User[] = []; addUser(user: User): void { this.users.push(user); } removeUser(user: User): void { this.users = this.users.filter(u => u.id !== user.id); } getUsers(): User[] { return this.users; } }
この例では、ユーザーの追加・削除・取得のロジックをUserServiceクラスにまとめることで、変更頻度に基づいてコードをグルーピングしています。
まとめ
プログラミングセオリーを実現する6つの原則「結果の局所化」「繰り返しの最小化」「ロジックとデータの一体化」「対称性」「宣言型の表現」「変更頻度」は、コードの品質を向上させるための具体的なガイドラインです。これらの原則を理解し、実践することで、コミュニケーションが円滑になり、シンプルで柔軟なコードを作り上げることができます。最高のコードを目指すための道筋を理解し、実践していきましょう。
参考書籍:上田勲(2016). プリンシプルオブプログラミング 3年目までに身に着けたい一生役立つ101の原理原則 秀和システム
カテゴリー: