概要
ユーティリティ関数は、共通処理を抽象化・再利用するための最小単位である。
しかし一方で、それが「ただの便利関数集」になると、
- スコープの不明瞭さ
- 副作用の混入
- 汎用性の破壊
といった問題を引き起こす。
本稿では、ユーティリティ関数を「アプリケーションを構造的に支える基盤」として設計するための原則と戦略を解説する。
1. ユーティリティ関数の定義と分類
// 計算系
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
// 判定系
const isNullish = (val) => val === null || val === undefined;
// 変換系
const toSnakeCase = (str) => str.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
- ✅ 純粋であること(副作用を持たない)
- ✅ 小さく、明確な責務を持つ
- ✅ 入力と出力が完全に制御可能
2. 共通処理として抽出すべき基準
判断基準 | 例 |
---|---|
同じ処理が3回以上現れる | 日付フォーマット、数値変換 |
条件分岐で頻繁に使うロジック | null判定、型判定 |
テストの独立単位にできる | 計算処理、正規化処理 |
複数の層(UI/ドメイン)で利用される | ID生成、トークン付加、形式変換 |
3. 責務分離と汎用性のトレードオフ
// ❌ 責務が混在した関数
function formatUser(user) {
if (!user) return 'N/A';
return `${user.firstName} ${user.lastName}`;
}
// ✅ 単一責務に分割
const formatName = (first, last) => `${first} ${last}`;
const isUserValid = (user) => user && user.firstName && user.lastName;
function safeFormatUser(user) {
if (!isUserValid(user)) return 'N/A';
return formatName(user.firstName, user.lastName);
}
- ✅ 汎用性を保つには「粒度の分離」が鍵
- ✅ ユーティリティ関数は低レイヤに位置付け、特定文脈に依存しないこと
4. 命名と責務の可視化
命名指針 | 意図 |
---|---|
isXxx |
判定関数(boolean返却) |
toXxx |
変換関数(形式変更) |
createXxx |
新しい構造の生成 |
formatXxx |
出力用の表現調整 |
getXxxFromYyy |
特定構造からの抽出 |
- ✅ 命名は責務そのものを示すものに統一
- ✅ 関数の型だけで使い方が想起できる設計にする
5. テストと再利用を前提とした構造
// dateUtils.ts
export const toISODate = (d: Date) => d.toISOString().split('T')[0];
export const isWeekend = (d: Date) => [0, 6].includes(d.getDay());
- ✅ モジュール単位で目的別に分類(e.g.
stringUtils
,arrayUtils
,dateUtils
) - ✅ 関数ごとの単体テストが簡単に書けるように副作用は排除
- ✅ 共通処理はドメイン層ではなく基盤層に配置する
設計判断フロー
① この処理は1つの目的だけに特化しているか? → YES → ユーティリティ候補
② 他の文脈(UI/サーバー)でも使えるか? → YES → 抽出すべき
③ 依存する状態や副作用はないか? → NO → 安全にメモ化・テスト可能
④ 再利用前提の命名になっているか? → YES → 読みやすさ・維持性が向上
⑤ モジュールごとに責務が整理されているか? → YES → スケーラビリティ確保
よくあるミスと対策
❌ 一度しか使わないロジックを「とりあえずutils.ts」へ放り込む
→ ✅ 再利用性が見込めないなら、ローカル関数で閉じるべき
❌ 複数の責務を1つのユーティリティ関数でまとめている
→ ✅ 構成単位ごとに分離し、責務を明示する
❌ 副作用のある処理(console.logやDOM操作)を含む
→ ✅ ユーティリティ関数は必ず「純粋関数」で設計する
結語
ユーティリティ関数とは「小さな便利関数」ではない。
それは**“アプリケーションのあらゆる層を横断して支える、責務と再利用の設計単位”**である。
- 再利用性と責務の分離は両立可能
- 命名、構造、スコープを戦略的に整えることで、コード全体の保守性が跳ね上がる
- 純粋で、予測可能で、明示的な「部品」が、堅牢なシステムを構成する
JavaScriptにおけるユーティリティ設計とは、
“小さくて強い機能を明確に分離し、全体構造に美しさを宿す戦略である。”