安全に壊れるための設計原則
安全に壊れるための設計原則
Section titled “安全に壊れるための設計原則”「正常に動く」よりも「異常時に安全に壊れる」ことを優先する設計原則を詳しく解説します。
境界防御 (Boundary Defense)
Section titled “境界防御 (Boundary Defense)”外部(API・DOM・ユーザ入力)からのデータは常に汚染されていると仮定し、型・形式・範囲を検査してからロジックに渡す。
// ❌ 悪い例: 無防備な入力受付function createUser(data: any) { // 問題: 型チェックなし、バリデーションなし return { name: data.name, age: data.age, email: data.email, };}
// ✅ 良い例: 境界防御の実装interface UserData { name: string; age: number; email: string;}
function validateUserData(data: unknown): UserData { if (typeof data !== 'object' || data === null) { throw new Error('Invalid data type'); }
const obj = data as Record<string, unknown>;
if (typeof obj.name !== 'string' || obj.name.length === 0 || obj.name.length > 100) { throw new Error('Invalid name'); }
if (typeof obj.age !== 'number' || obj.age < 0 || obj.age > 150) { throw new Error('Invalid age'); }
if (typeof obj.email !== 'string' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(obj.email)) { throw new Error('Invalid email'); }
return { name: obj.name, age: obj.age, email: obj.email, };}
function createUser(data: unknown) { const validated = validateUserData(data); return validated;}なぜ重要か:
- 型安全性: TypeScriptの型システムで型チェック
- バリデーション: 形式・範囲を検査
- セキュリティ: XSS、インジェクションなどの攻撃を防止
副作用の局所化
Section titled “副作用の局所化”DOM更新・API呼び出し・ストレージ操作などの副作用をロジックの末尾に集約し、それ以前を状態を持たない純粋処理として保つ。
// ❌ 悪い例: 副作用が散在function processOrder(orderData: any) { // 副作用1: DOM更新 const element = document.getElementById('order-status')!; element.textContent = 'Processing...';
// ビジネスロジック(副作用が混在) const total = orderData.items.reduce((sum: number, item: any) => { return sum + item.price * item.quantity; }, 0);
if (total > 10000) { // 副作用2: API呼び出し fetch('/api/notify', { method: 'POST', body: JSON.stringify({ total }) }); }
// 副作用3: ストレージ操作 localStorage.setItem('lastOrder', JSON.stringify(orderData));
return total;}
// ✅ 良い例: 副作用の局所化function calculateTotal(items: OrderItem[]): number { // 純粋関数: 副作用なし return items.reduce((sum, item) => { return sum + item.price * item.quantity; }, 0);}
function processOrder(orderData: OrderData) { // 1. 純粋処理: ビジネスロジック(副作用なし) const total = calculateTotal(orderData.items);
// 2. 副作用の集約: すべての副作用を末尾に updateDOM(total); notifyIfNeeded(total); saveToStorage(orderData);
return total;}
function updateDOM(total: number) { const element = document.getElementById('order-status'); if (element) { element.textContent = `Total: ${total}`; }}
function notifyIfNeeded(total: number) { if (total > 10000) { fetch('/api/notify', { method: 'POST', body: JSON.stringify({ total }) }); }}
function saveToStorage(orderData: OrderData) { localStorage.setItem('lastOrder', JSON.stringify(orderData));}なぜ重要か:
- テスト容易性: 純粋関数は単体テストが容易
- 可読性: 副作用が明確に分離される
- デバッグ容易性: 副作用の発生箇所が明確
ビジネスロジックが特定ライブラリやDOM APIの仕様に依存しないよう、インターフェース層で抽象化する。
// ❌ 悪い例: DOM APIに直接依存class OrderService { displayOrder(order: Order) { // DOM APIの仕様に依存 const element = document.getElementById('order')!; element.textContent = `Order: ${order.id}`; }}
// ✅ 良い例: インターフェースで抽象化interface View { displayOrder(order: Order): void;}
class DOMView implements View { constructor(private containerId: string) {}
displayOrder(order: Order) { const element = document.getElementById(this.containerId); if (element) { element.textContent = `Order: ${order.id}`; } }}
class ConsoleView implements View { displayOrder(order: Order) { console.log(`Order: ${order.id}`); }}
// サービス層: インターフェースに依存class OrderService { constructor(private view: View) {}
displayOrder(order: Order) { this.view.displayOrder(order); }}
// 使用例const domView = new DOMView('order');const orderService = new OrderService(domView);orderService.displayOrder({ id: 1, total: 1000 });なぜ重要か:
- 交換容易性: DOM APIを変更してもビジネスロジックは変更不要
- テスト容易性: モックで簡単にテスト可能
- 保守性: フレームワークの変更に強い
安全に壊れるための設計原則のポイント:
- 境界防御: 外部データは常に汚染されていると仮定し、型・形式・範囲を検査
- 副作用の局所化: 副作用をロジックの末尾に集約し、純粋処理と分離
- 依存の隔離: ビジネスロジックが特定ライブラリに依存しないよう抽象化
これらの原則により、「異常時に安全に壊れる」堅牢なシステムを構築できます。