useMemoで複雑な処理を隠していないか?命名と責務設計
useMemo
はパフォーマンス最適化を目的に使われることが多いですが、複雑なロジックをそのまま閉じ込めてしまうことで、構造的な意図が不透明になるという副作用も持ちます。
本稿では、useMemoの中にロジックが詰め込まれている構造に対して、レビューアーが読み取り、判断、改善を提案するための視点を解説します。
典型的な構造:内部に複雑な処理があるuseMemo
const displayItems = useMemo(() => {
const filtered = data.filter(d => d.active && !d.deleted);
const sorted = filtered.sort((a, b) => b.score - a.score);
const formatted = sorted.map(d => ({
name: d.name.toUpperCase(),
score: d.score.toFixed(2),
}));
return formatted;
}, [data]);
@Reviewer複数の処理を段階的に詰め込んでおり、フィルタ・ソート・整形の各責務が一体化しています。命名や分割がされていないため、意図が読み取りづらくなっています。
このような構造では、displayItems
が「どのようなロジックで生成されているのか」をコードリーディングのたびに追いかける必要が生じ、責務の見通しと再利用性が著しく下がる原因となります。
責務単位での分割と命名の必要性
複雑な処理をそのまま useMemo に書くのではなく、責務ごとに関数に分けてからuseMemo内で組み立てる構造にすると、意図が明確になります。
const displayItems = useMemo(() => {
const visible = filterVisibleItems(data);
const sorted = sortByScoreDesc(visible);
return formatDisplayItems(sorted);
}, [data]);
function filterVisibleItems(data) { /* ... */ }
function sortByScoreDesc(items) { /* ... */ }
function formatDisplayItems(items) { /* ... */ }
@Reviewerフィルタ・ソート・整形が関数に分離されており、それぞれの責務と意図が明確です。再利用性やテスト性も高まり、レビュー時の構造把握が容易になります。
このように構造を整理すると、表示用のデータがどのように加工されているかを「名前で読める」設計となり、ロジックの把握コストが大幅に下がります。
命名が抽象的なuseMemoの問題点
const result = useMemo(() => compute(data), [data]);
このような構造では result
と compute
の両方が抽象的すぎて、何をしているのか全く読み取れない状態になっています。
レビューでは次のような観点で読み解く必要があります。
result
はUIにどのように使われるのか?compute
の中で行われている処理は1つなのか、複数か?useMemo
の目的はパフォーマンスなのか、ロジックの整理なのか?
処理の粒度をuseMemoで隠していないか
useMemoを「処理の隠し場所」にしてしまうと、結果的に処理がブラックボックス化し、次のようなレビュー上の支障が出ます。
- バグの原因が追いづらい(各処理が内包されている)
- 処理単位でのテストがしづらい
- 再利用のきっかけを阻害する
このような構造では、useMemoの外に一度責務を出すことを検討すべきです。
useMemoレビュー時のチェックリスト
- 複数の処理(filter、map、sortなど)が一体化していないか?
- 処理の名前が役割を示しているか?
- 再利用できる処理がuseMemo内に閉じ込められていないか?
- 依存配列に複雑な影響を与えるロジックが含まれていないか?
まとめ:useMemoは処理を“隠す場所”ではない
useMemoは計算結果のキャッシュであり、ロジックの抽象化やカプセル化のための道具ではないという原則を忘れてはなりません。
レビューアーとしては、「なぜuseMemoの中に書いたのか?」ではなく、「なぜ責務を分けなかったのか?」という問いを立てるべきです。
命名と分割によって処理の責務を明示することで、useMemoは本来の役割である最適化に集中し、コード全体の見通しと品質も保たれます。