Skip to content

Instantly share code, notes, and snippets.

@kobitoDevelopment
Last active August 23, 2025 13:50
Show Gist options
  • Select an option

  • Save kobitoDevelopment/6e72461dd9d86c59e574e04b95a353b4 to your computer and use it in GitHub Desktop.

Select an option

Save kobitoDevelopment/6e72461dd9d86c59e574e04b95a353b4 to your computer and use it in GitHub Desktop.
// 改善前のコード
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