-
-
Save kobitoDevelopment/6e72461dd9d86c59e574e04b95a353b4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 改善前のコード | |
| async function getUserName(userId) { | |
| const response = await fetch(`https://api.example.com/users/${userId}`); | |
| const user = await response.json(); | |
| return user.name; | |
| } | |
| /* | |
| 改善が必要な点 | |
| ・エラーハンドリングが不十分 | |
| ネットワークエラーやレスポンスエラー時の処理がなく、失敗時に例外がそのまま投げられてしまう。 | |
| ・型安全性が低い | |
| APIレスポンスの型が明示されておらず、TypeScript上で安全に扱えない。 | |
| ・単一ユーザー取得によるN+1問題 | |
| 複数ユーザー取得時に逐次APIを呼び出す必要があり、パフォーマンスが低下する。 | |
| ・APIエンドポイントのベタ書き | |
| URLが関数内で直接記述されているため、変更が困難で保守性が低い。 | |
| ・認証や認可の仕組みがない | |
| API呼び出し時の認証(JWTなど)が考慮されていないため、セキュリティ上の課題がある。 | |
| ・テスト容易性が低い | |
| fetch関数が直接呼ばれており、テスト時にモックへ差し替えができない。 | |
| ・レスポンス検証が不十分 | |
| APIから取得したデータの形式や内容の検証がなく、想定外のデータが混入する可能性がある。 | |
| 障害耐性が低い | |
| ・リトライやタイムアウトなど、一時的な障害に対応する仕組みがない。 | |
| ・スケーラビリティの課題 | |
| 複数ID取得のためのバッチAPIが使われていない。リクエスト回数が増加しやすい。 | |
| 可観測性が低い | |
| ・失敗時のログ記録やメトリクス送信がなく、運用時のトラブルシュートが困難。 | |
| 必要なフィールドのみ取得する工夫がない | |
| ・APIレスポンスが不要なデータまで含む可能性があり、通信量が増加する。 | |
| */ | |
| // 改善後のコード | |
| // エラーハンドリング: Result型で値を型として明示 | |
| type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }; | |
| type UserFetchError = 'UNAUTHORIZED' | 'RATE_LIMITED' | 'INTERNAL_ERROR'; | |
| // 保守性: TypeScriptでAPIレスポンスの型安全性を確保 | |
| type UserResponse = { | |
| id: string; | |
| name: string; | |
| }; | |
| async function getUserNames( | |
| // パフォーマンス: 複数ユーザーIDをまとめて取得することで、N+1問題を回避 | |
| userIds: string[], | |
| // テストビリティ: 依存性注入により、モックに差し替え可能に | |
| deps: Dependencies, | |
| ): Promise<Result<Map<string, string>, UserFetchError>> { | |
| // 信頼性: リトライ機構により、一時的な障害に対応 | |
| const maxRetries = 3; | |
| for (let attempt = 0; attempt <= maxRetries; attempt++) { | |
| // 信頼性: タイムアウトにより、ネットワーク遅延からアプリケーションを保護 | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 5000); | |
| try { | |
| const response = await deps.fetchClient( | |
| // スケーラビリティ: バッチ処理でリクエスト回数を最小化 | |
| // 保守性: URLのベタ書きを排除 | |
| `${deps.apiBaseUrl}/users/batch`, | |
| { | |
| method: 'POST', | |
| headers: { | |
| // 認証・認可: JWTでAPIを保護 | |
| Authorization: `Bearer ${await deps.jwtProvider()}`, | |
| 'Content-Type': 'application/json', | |
| }, | |
| // パフォーマンス: 複数ユーザーIDをまとめて取得することで、N+1問題を回避 | |
| // パフォーマンス: 必要なフィールドのみ取得してデータ転送量を最小化 | |
| body: JSON.stringify({ ids: userIds, fields: ['id', 'name'] }), | |
| signal: controller.signal, | |
| }, | |
| ); | |
| // エラーハンドリング: HTTPステータスを適切にハンドリング | |
| if (!response.ok) { | |
| if (response.status >= 400 && response.status < 500) { | |
| switch (response.status) { | |
| // 認証・認可: APIを保護 | |
| case 403: | |
| throw new NonRetryableError('UNAUTHORIZED', response.status); | |
| // スケーラビリティ: レート制限対応 | |
| case 429: | |
| throw new NonRetryableError('RATE_LIMITED', response.status); | |
| default: | |
| throw new NonRetryableError('INTERNAL_ERROR', response.status); | |
| } | |
| } | |
| throw response; | |
| } | |
| const users = await response.json(); | |
| // セキュリティ: レスポンスを厳密に検証 | |
| if (!isValidUsersResponse(users)) { | |
| throw new NonRetryableError('INTERNAL_ERROR'); | |
| } | |
| const results = new Map(users.map((user) => [user.id, user.name])); | |
| return { ok: true, value: results }; | |
| } catch (error) { | |
| // 可観測性: 構造化されたログ | |
| deps.logger.error('Failed to fetch users', { | |
| userIds, | |
| attempt, | |
| error, | |
| }); | |
| // 可観測性: メトリクス | |
| deps.metrics.increment('user_fetch_error', { | |
| status: error instanceof Response || error instanceof NonRetryableError ? error.status : undefined, | |
| }); | |
| if (error instanceof NonRetryableError) { | |
| return { ok: false, error: error.errorType }; | |
| } | |
| if (attempt < maxRetries) { | |
| // 信頼性: 指数バックオフ(1秒→2秒→4秒)によるリトライで過負荷を防止 | |
| const delay = 2 ** attempt * 1000; | |
| await new Promise((resolve) => setTimeout(resolve, delay)); | |
| } | |
| } finally { | |
| clearTimeout(timeoutId); | |
| } | |
| } | |
| return { ok: false, error: 'INTERNAL_ERROR' }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment