【15日目】『リーダブルコード』を意識してTypescriptをリファクタリングしてみた
技術ブログ15日目。
本日は、Typescriptの読みやすさを追求します。
開発環境:Vanilla TypeScript
〇課題
マルチデバイス通知送付システム
ユーザーの設定(メール、SMS、プッシュ通知)に合わせて、メッセージの優先度に応じた送付方法を決定する関数です。 今のコードは、送付手段が増えるたびに if 文が巨大化し、どこで何を判定しているのか非常に分かりにくい状態です。
〇修正前コード(動くけれど読みにくいコード)
// 開発環境: Vanilla TypeScript
function notify(u: any, m: any, t: any): any {
let res = { s: false, log: "" };
if (u !== null && u !== undefined) {
if (u.active === true) {
// 優先度が緊急(1)の場合
if (t === 1) {
console.log("緊急通知を発信します");
if (u.email !== "") {
res.log += "Email送信完了: " + m;
res.s = true;
}
if (u.phone !== "") {
res.log += " / SMS送信完了: " + m;
res.s = true;
}
} else {
// 通常通知(2)の場合
if (u.pref === "EMAIL") {
res.log = "Emailで送信しました: " + m;
res.s = true;
} else if (u.pref === "PUSH") {
res.log = "プッシュ通知で送信しました: " + m;
res.s = true;
} else {
res.log = "通知設定が未設定です";
}
}
} else {
res.log = "ユーザーが非アクティブです";
}
} else {
res.log = "ユーザー情報エラー";
}
return res;
}
〇リファクタリングの指針
型定義の徹底
通知の優先度や送付手段に名前を付け、Union型で定義することで、マジックナンバー(意味不明な数字)を排除します。
早期リターンの適用
例外ケースを関数の冒頭で排除し、メインロジックのネスト(階層)を浅くします。
関心の分離(関数の抽出)
「緊急時のルール」と「通常時のルール」を別の関数に切り出し、役割を明確にします。
文字列構築の改善
+= による複雑な結合をやめ、テンプレートリテラルと配列の結合を活用します。
〇修正後コード
// 1. 型定義:意味のある名前をつける
type NotificationPriority = "EMERGENCY" | "NORMAL";
type NotificationMethod = "EMAIL" | "PUSH";
interface User {
id: string;
name: string;
isActive: boolean;
email: string;
phoneNumber: string;
preference: NotificationMethod;
}
interface NotificationResult {
isSuccess: boolean;
log: string;
}
// 2. メイン処理:条件分岐をスッキリさせる
function sendNotification(
user: User | null | undefined,
message: string,
priority: NotificationPriority
): NotificationResult {
// ガード節1:ユーザーの存在確認
if (!user) {
return { isSuccess: false, log: "ユーザー情報エラー" };
}
// ガード節2:アクティブ状態の確認
if (!user.isActive) {
return { isSuccess: false, log: "ユーザーが非アクティブです" };
}
// 優先度によって処理を完全に分ける(塊で捉える)
if (priority === "EMERGENCY") {
return sendEmergencyNotification(user, message);
}
return sendNormalNotification(user, message);
}
// 3. サブ処理:専門の送付ロジック(関数の抽出)
/** 緊急時の通知(利用可能なすべての手段で送る) */
function sendEmergencyNotification(user: User, message: string): NotificationResult {
const sentMethods: string[] = [];
if (user.email) {
sentMethods.push(`Email送信完了: ${message}`);
}
if (user.phoneNumber) {
sentMethods.push(`SMS送信完了: ${message}`);
}
if (sentMethods.length === 0) {
return { isSuccess: false, log: "緊急通知の送付手段がありません" };
}
return { isSuccess: true, log: sentMethods.join(" / ") };
}
/** 通常時の通知(ユーザーの好みに合わせて送る) */
function sendNormalNotification(user: User, message: string): NotificationResult {
if (user.preference === "EMAIL") {
return { isSuccess: true, log: `Emailで送信しました: ${message}` };
}
if (user.preference === "PUSH") {
return { isSuccess: true, log: `プッシュ通知で送信しました: ${message}` };
}
return { isSuccess: false, log: "通知設定が未設定です" };
}
〇主な修正ポイント
-
マジックナンバーをなくし、Union型へ
修正前は t === 1 という数字を使っていましたが、"EMERGENCY" | "NORMAL" という型を定義しました。これにより、コードを読むだけで「緊急時の処理」であることが一瞬でわかるようになります。 -
ネストを解消するガード節
if文の中にif文が重なるピラミッド構造を解消しました。不適切な状態を関数の最初で追い返すことで、メインの送付ロジックが非常に読みやすくなっています。 -
関心の分離
緊急時と通常時のロジックを別々の関数に切り出しました。これにより、例えば「通常通知に新しいSNS連携を追加したい」といった場合でも、緊急通知側のコードを壊す心配がなくなります。 -
文字列構築のクリーン化
配列にメッセージを溜めて最後にjoinする方法を採用しました。区切り文字( / )の扱いや、条件によるメッセージの増減が非常にシンプルに管理できるようになっています。
〇参照先
▼公式ドキュメント
▼書籍
リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック
以上
Discussion