安全に壊れるための設計原則
安全に壊れるための設計原則
Section titled “安全に壊れるための設計原則”「正常に動く」よりも「異常時に安全に壊れる」ことを優先する設計原則を詳しく解説します。
境界防御 (Boundary Defense)
Section titled “境界防御 (Boundary Defense)”外部(API・DOM・ユーザ入力)からのデータは常に汚染されていると仮定し、型・形式・範囲を検査してからロジックに渡す。
// ❌ 悪い例: 無防備な入力受付export async function getServerSideProps({ query }: { query: any }) { // 問題: 型チェックなし、バリデーションなし const page = query.page; const posts = await fetch(`https://api.example.com/posts?page=${page}`);
return { props: { posts: await posts.json(), }, };}
// ✅ 良い例: 境界防御の実装interface QueryParams { page?: string;}
function validateQueryParams(query: unknown): QueryParams { if (typeof query !== 'object' || query === null) { throw new Error('Invalid query parameters'); }
const obj = query as Record<string, unknown>;
if (obj.page !== undefined && typeof obj.page !== 'string') { throw new Error('Invalid page parameter'); }
return { page: typeof obj.page === 'string' ? obj.page : undefined, };}
export async function getServerSideProps({ query }: { query: unknown }) { const validated = validateQueryParams(query); const page = parseInt(validated.page || '1', 10);
if (page < 1 || page > 1000) { throw new Error('Page number out of range'); }
const res = await fetch(`https://api.example.com/posts?page=${page}`); const posts = await res.json();
return { props: { posts, }, };}なぜ重要か:
- 型安全性: TypeScriptの型システムで型チェック
- バリデーション: 形式・範囲を検査
- セキュリティ: XSS、インジェクションなどの攻撃を防止
副作用の局所化
Section titled “副作用の局所化”DOM更新・API呼び出し・ストレージ操作などの副作用をロジックの末尾に集約し、それ以前を状態を持たない純粋処理として保つ。
// ❌ 悪い例: 副作用が散在'use client';
export default function Page() { const [data, setData] = useState<any[]>([]);
useEffect(() => { // 副作用1: API呼び出し fetch('/api/data') .then(res => res.json()) .then(json => setData(json));
// ビジネスロジック(副作用が混在) if (data.length > 100) { // 副作用2: ストレージ操作 localStorage.setItem('largeData', JSON.stringify(data)); }
// 副作用3: DOM更新 document.title = `Data: ${data.length}`; }, [data]);
return <div>{data.length}</div>;}
// ✅ 良い例: 副作用の局所化'use client';
function processData(rawData: any[]): ProcessedData[] { // 純粋関数: 副作用なし return rawData.map(item => ({ id: item.id, name: item.name.toUpperCase(), timestamp: new Date(item.timestamp).getTime(), }));}
export default function Page() { const [data, setData] = useState<ProcessedData[]>([]);
useEffect(() => { fetch('/api/data') .then(res => res.json()) .then(rawData => { // 1. 純粋処理: ビジネスロジック(副作用なし) const processed = processData(rawData);
// 2. 副作用の集約: すべての副作用を末尾に setData(processed); saveToStorageIfNeeded(processed); updateDocumentTitle(processed.length); }); }, []);
return <div>{data.length}</div>;}
function saveToStorageIfNeeded(data: ProcessedData[]) { if (data.length > 100) { localStorage.setItem('largeData', JSON.stringify(data)); }}
function updateDocumentTitle(count: number) { document.title = `Data: ${count}`;}なぜ重要か:
- テスト容易性: 純粋関数は単体テストが容易
- 可読性: 副作用が明確に分離される
- デバッグ容易性: 副作用の発生箇所が明確
ビジネスロジックが特定ライブラリやNext.jsの仕様に依存しないよう、インターフェース層で抽象化する。
// ❌ 悪い例: Next.jsのAPIに直接依存export async function getServerSideProps() { // Next.jsのAPIに直接依存 const res = await fetch('https://api.example.com/data'); return { props: { data: await res.json(), }, };}
// ✅ 良い例: インターフェースで抽象化interface DataRepository { fetchData(): Promise<Data[]>;}
class ApiDataRepository implements DataRepository { async fetchData(): Promise<Data[]> { const res = await fetch('https://api.example.com/data'); return res.json(); }}
class MockDataRepository implements DataRepository { async fetchData(): Promise<Data[]> { return [{ id: 1, name: 'Mock Data' }]; }}
// サービス層: インターフェースに依存class DataService { constructor(private repository: DataRepository) {}
async getData(): Promise<Data[]> { return this.repository.fetchData(); }}
// Next.jsのAPI Routesで使用export async function getServerSideProps() { const repository = new ApiDataRepository(); const service = new DataService(repository); const data = await service.getData();
return { props: { data, }, };}なぜ重要か:
- 交換容易性: APIを変更してもビジネスロジックは変更不要
- テスト容易性: モックで簡単にテスト可能
- 保守性: フレームワークの変更に強い
安全に壊れるための設計原則のポイント:
- 境界防御: 外部データは常に汚染されていると仮定し、型・形式・範囲を検査
- 副作用の局所化: 副作用をロジックの末尾に集約し、純粋処理と分離
- 依存の隔離: ビジネスロジックが特定ライブラリに依存しないよう抽象化
これらの原則により、「異常時に安全に壊れる」堅牢なシステムを構築できます。