1 . 次のコードは、画面を破棄したあともイベントリスナーが残り続け、メモリリークやハンドラの二重実行を引き起こす可能性があります。安定性・保守性の観点から、もっともよい改善はどれでしょうか?
function attach() {
const btn = document.getElementById('save');
btn.addEventListener('click', () => {
save();
});
}
A.
コールバックを無名関数ではなく名前付き関数にしておく
B.
addEventListener のオプションに { passive: true } を追加する
C.
コンポーネントのライフサイクルに合わせて removeEventListener する、または1度きりのリスナーにするなど、クリーンアップの仕組みを組み込む
D.
save 関数の中で現在のクリック回数をログに残す
解説: ここでのリスクは、画面を開くたびにリスナーが積み上がり、メモリと挙動が複雑になることです。コールバックの書き方やログの有無では、この根本原因は解消されません。画面のマウント・アンマウントなどライフサイクルと連動してリスナーを登録・解除する、あるいは1回だけ動作するリスナーにするなど、クリーンアップまで含めた設計にする必要があります。
2 . 次のコードは、すべてのジョブが毎分ぴったりのタイミングで重い処理を走らせる設計になっています。他のサービスも同じタイミングで動くと、一時的な負荷スパイクを招く可能性があります。非機能要件(安定性・スループット)の観点から、もっともよい改善はどれでしょうか?
// 毎分0秒に実行される
setInterval(runJob, 60 * 1000);
function runJob() {
// 重い集計処理
}
A.
実行間隔を短くし、1回あたりの仕事量を小さくする
B.
runJob の処理を別スレッドに移し、メインスレッドを空けておく
C.
実行タイミングにランダムなジッタを持たせるか、ジョブ管理基盤側で実行タイミングを分散させる
D.
runJob の前後でログを出しておき、後からピークを分析できるようにする
解説: 重い処理を同じタイミングで一斉に走らせると、CPUやI/Oに大きなスパイクが生じ、他の処理にも悪影響を与えます。実行間隔を調整したりログを出すだけでは、ピークの集中は解消されません。実行タイミング自体にゆらぎを持たせたり、ジョブスケジューラ側で分散させることで、負荷を平準化しやすくなります。
3 . 次のコードでは、ログを永続化せずメモリ上の配列に貯め続けています。長時間稼働時の安定性を高めるために、もっとも重要な改善はどれでしょうか?
const logs = [];
function logEvent(event) {
logs.push({
at: new Date().toISOString(),
event
});
}
A.
ログのタイムスタンプを UNIX時間にする
B.
logs をオブジェクトではなく Map に変更する
C.
保持件数に上限を設け、超えた分を外部に吐き出すか破棄する仕組みを入れて、メモリ使用量を制御する
D.
logEvent 内でログ件数を監視し、一定件数ごとに警告ログを出す
解説: 問題は、配列が増え続けることでメモリを圧迫し、いずれプロセス全体の安定性を損なう点です。フォーマットを工夫したり警告を出したりするだけでは、メモリが増え続ける構造は変わりません。上限件数やローテーション、外部ストレージへの退避などを導入し、メモリに保持する量を制御する設計に変えることが、非機能要件として重要になります。
4 . 次のコードは、ダッシュボード表示のたびに重い集計処理を実行しています。性能とスケーラビリティの観点から、もっとも有効な改善はどれでしょうか?
function renderDashboard(userId) {
const stats = calculateAllStats(userId); // 重い処理
return buildHtml(stats);
}
A.
calculateAllStats の中でログを出し、どこが遅いか把握できるようにする
B.
buildHtml の処理を最適化し、テンプレートエンジンを高速なものに変える
C.
一定期間ごとに集計結果をバッチ処理で更新してキャッシュし、画面表示時にはキャッシュ済みデータを参照する
D.
ダッシュボードの表示頻度を制限する
解説: 根本の問題は、「リクエストのたびに重い集計をリアルタイムで行っていること」です。テンプレートの最適化やログ計測は補助的には有効ですが、集計コストを根本的には減らせません。利用パターンに応じて集計タイミングをずらし、バッチ処理とキャッシュで対応することで、ピーク時の負荷を抑えつつ快適に表示できます。
5 . 次のコードは、ユーザー入力に対するバリデーションがなく、異常値で例外や異常動作を起こしやすくなっています。信頼性の観点から、もっとも重要な改善はどれでしょうか?
function calculate(pageSize) {
return 1000 / pageSize;
}
A.
計算結果を Math.floor で丸める
B.
pageSize が 0 や負の値、極端に大きい値などの場合はエラーとして扱い、安全な経路で呼び出し元に伝える
C.
1000 という値を定数に切り出す
D.
計算結果が Infinity や NaN のときだけログに出す
解説: ここで問題なのは、「不正な入力値がそのまま計算に使われてしまうこと」です。丸めたり定数を切り出したりするだけでは、ゼロ割りや異常値は防げません。入力の範囲や型を明示し、許容されない値については早期にエラーとして扱うことで、システム全体の異常を予防しやすくなります。
6 . 次のコードは、外部APIがハングした場合でもいつまでも待ち続けてしまい、スレッドや接続を長時間専有してしまう可能性があります。非機能要件(応答性・信頼性)の観点から、もっとも優先して行うべき改善はどれでしょうか?
async function fetchProfile(userId) {
const res = await fetch(`https://api.example.com/users/${userId}`);
if (!res.ok) {
throw new Error('failed');
}
return res.json();
}
A.
API呼び出しの前後でログを詳細に出力し、失敗時に原因を追いやすくする
B.
エラーメッセージを状況に応じて分かりやすく書き分ける
C.
タイムアウトと上限付きリトライを導入し、一定時間で処理を打ち切って呼び出し元に制御を戻す
D.
同時に呼び出せる fetchProfile の数を制限するキューを用意する
解説: ここで致命的なのは、外部APIが応答しないときにいつまでも待ち続け、スレッドや接続が解放されないことです。ログの改善や同時実行数の制限も有用ですが、「応答が来ない状態をいつまでも放置しない」仕組みを入れない限り、この問題は解決しません。タイムアウトと上限付きリトライを導入して、一定時間で処理を諦めて上位層に制御を返す設計が、非機能要件として最優先の対処になります。
7 . 次のコードは、典型的なN+1クエリ問題を抱えています。性能とスケーラビリティの観点から、もっともインパクトの大きい改善はどれでしょうか?
async function listOrdersWithUser(db) {
const orders = await db.query('SELECT * FROM orders');
for (const order of orders) {
const rows = await db.query(
'SELECT * FROM users WHERE id = ?',
[order.user_id]
);
order.user = rows[0] ?? null;
}
return orders;
}
A.
ループの中で await せず、Promise.all でまとめて待つようにする
B.
users の取得を別の関数に切り出して可読性を上げる
C.
JOIN や IN 句などを使って、必要なユーザー情報をまとめて取得するクエリに書き換える
D.
クエリの実行時間をログ出力し、遅い場合にアラートを上げる
解説: 問題は「注文の件数分だけユーザーテーブルに問い合わせていること」であり、アクセスパターンによっては致命的に遅くなります。ログや構造化は原因の把握には役立ちますが、クエリ回数は減りません。テーブルを結合する、あるいはIN句でまとめて取得するなど、データアクセスをまとめる設計に変えることで、根本的にスケーラビリティを改善できます。
8 . 次のコードは、重い処理のたびに詳細なログを出しており、高負荷時にログI/O自体がボトルネックになる恐れがあります。性能と運用性のバランスをとるために、もっとも適切な改善はどれでしょうか?
function processItem(item) {
console.log('START PROCESS', item);
doHeavyWork(item);
console.log('END PROCESS', item);
}
A.
ログに出す項目を減らし、メッセージを短くする
B.
ログ出力を非同期にし、処理本体とは別スレッドで書き込む
C.
ログレベル・サンプリング・環境設定などを導入し、通常運用では出力量を抑えつつ、必要なときだけ詳細ログを出せるようにする
D.
doHeavyWork の前後で process.hrtime を使って正確な時間を計測する
解説: 重い処理のたびに同じレベルで詳細なログを出していると、本来の処理よりログ出力が支配的になることもあります。完全にログをやめるのではなく、レベルやサンプリングで粒度を調整し、環境や状況に応じて出力量をコントロールできるようにすることで、性能と可観測性のバランスを取りやすくなります。
9 . 次のコードは、送金処理にトランザクションを使っておらず、途中でエラーが起きたときに残高が不整合になる危険があります。信頼性(一貫性)の観点から、もっとも重要な改善はどれでしょうか?
async function transfer(db, fromId, toId, amount) {
await db.execute(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromId]
);
await db.execute(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toId]
);
}
A.
ログに送金の開始と終了を必ず記録する
B.
fromId と toId が同じ場合は何もしないようにする
C.
2つの更新を1つのトランザクションで実行し、途中で失敗した場合にはロールバックされるようにする
D.
UPDATE 文の前に、残高がマイナスにならないことをチェックする
解説: このままでは片方だけ更新された状態で停止する可能性があり、残高不整合という重大な障害につながります。チェックやログも重要ですが、「両方成功するか、どちらも反映されないか」の原子性を保証しない限り問題は解決しません。トランザクションを使って一連の処理を1単位にまとめることで、一貫性の高い送金処理を実現できます。
10 . 次のコードは、リトライ戦略が単純で、ネットワークが一時的に不安定な場合と恒久的に落ちている場合を区別していません。非機能要件(信頼性)の観点から、より良い改善はどれでしょうか?
async function fetchWithRetry(url) {
for (let i = 0; i < 2; i++) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error('bad status');
return res;
} catch (e) {
await new Promise(r => setTimeout(r, 100));
}
}
throw new Error('failed after 2 retries');
}
A.
リトライ回数を大幅に増やして、諦めるまで長く粘るようにする
B.
エラーの種類(タイムアウト・一時的エラー・4xxなど)に応じてリトライ可否と回数・間隔を変える
C.
エラー時には常に同じステータスコードを返すようにする
D.
リトライのたびにログを出しておき、後から原因を分析しやすくする
解説: 一律に回数だけ増やしても、恒久的な障害時には無駄に負荷をかけることになります。また、全てのエラーを同じ扱いにしてしまうと、クライアント側の挙動も最適化しづらくなります。一時的なネットワークエラーなどはリトライの対象にし、明らかな4xxエラーは即座に失敗扱いにするなど、エラーの性質に合わせて戦略を変えることが信頼性向上に直結します。