概要
ユーティリティ関数(Utility Functions)は、
「何度も使う処理」や、「1つの責務に特化した処理」をまとめ、コード全体の再利用性・可読性・テスト性を高めるための設計部品である。
本稿では、以下の観点からユーティリティ関数のあるべき姿を探る:
- DRY原則と再利用性の設計
- 副作用の最小化と“純粋性”
- 汎用性と限定性のバランス
- ユーティリティ関数の命名と格納戦略
- よくある実践例とアンチパターン
1. ユーティリティ関数の基本的条件
✅ 純粋関数であること(Pure Function)
function toUpper(str) {
return str.toUpperCase(); // 引数と返り値だけ
}
- ❌ グローバル変数の参照・更新をしない
- ❌ DOMやネットワークに依存しない
- ✅ 同じ入力に対して常に同じ出力
2. DRY原則と“意味の抽出”
// ❌ 重複
const fullName1 = user.first + ' ' + user.last;
const fullName2 = employee.first + ' ' + employee.last;
// ✅ 共通化
function getFullName({ first, last }) {
return `${first} ${last}`;
}
→ ✅ ロジックの“意味”を名前で表現する
3. 汎用性を高めすぎない:責務を限定する
// ❌ 汎用すぎて逆に使いにくい
function process(data, type, mode) {}
// ✅ 責務を明示
function formatDate(date: Date, format = 'YYYY-MM-DD') {}
→ ✅ 呼び出し時の直感的理解を優先
4. 副作用を切り離す:関数の「純度」と設計
// ❌ UIやロガーを巻き込む
function logAndFormatDate(date) {
console.log('called');
return formatDate(date);
}
→ ✅ 副作用はラッパー関数に分離する
5. 命名と格納:意味と関心の一致
✅ 命名ガイドライン
-
動詞 + 名詞 例:
getFullName
,isEmailValid
-
ブール値は
isXxx
,hasXxx
,canXxx
- 短すぎず、責務を明示的に伝える
✅ 格納場所の一例
/utils
├── string.ts
├── array.ts
├── date.ts
└── validation.ts
6. 再利用性の高い関数の具体例
✅ 値がnull/undefinedでないかを判定
export function isDefined(value) {
return value !== null && value !== undefined;
}
✅ 配列の重複排除
export function uniqueArray(arr) {
return [...new Set(arr)];
}
✅ オブジェクトの深いコピー(Shallow/Deep)
export function deepClone(obj) {
return JSON.parse(JSON.stringify(obj)); // 限定的だがシンプル
}
✅ 簡易なメールバリデーション
export function isEmailValid(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
7. テストしやすい設計を意識する
// ✅ 純粋関数はユニットテストが容易
test('isDefined', () => {
expect(isDefined(null)).toBe(false);
expect(isDefined(0)).toBe(true);
});
→ ✅ 入出力だけに依存する構造が最良
設計判断フロー
① 処理が2回以上登場する? → ユーティリティ化を検討
② 状態を変更している? → 純粋関数化できるか確認
③ 他の関数と責務が被ってない? → 関数を分割・再定義
④ 汎用すぎていないか? → 呼び出し側の明確さ優先
⑤ テスト容易か? → グローバル依存・副作用を排除
結語
ユーティリティ関数は“単なる小さな関数”ではない。
それは、再利用性・読みやすさ・責務の明確さを構造に刻む設計装置である。
- 名前に意味があり、
- 責務が明快であり、
- 副作用がなく、
- どこでも使えて、
- 壊れない
小さく設計されたユーティリティが、大きな信頼性を生む。
関数は“動作”ではなく、“構造”として設計すべきである。