Day 78:リファクタリング
Day 78では、プログラムの動作を変えずに、コードを読みやすく、修正しやすい形へ整える「リファクタリング」を学びます。今回のテーマは、重複コードの削除、関数分割、変数名の改善です。
リファクタリングは、新しい機能を追加する作業ではありません。まず今の動作を確認し、その動作を保ったまま内部の書き方を整えます。見た目がすっきりしても、結果が変わってしまったら意図したリファクタリングとは言えません。そのため、「変更前と変更後で同じ入力に同じ結果が返るか」を確かめながら進めることが重要です。
リファクタリングとは
リファクタリングとは、プログラムの外から見える動作は保ちながら、内部のコード構造を改善することです。たとえば、同じ処理を何度も書いているなら共通関数にまとめる、長すぎる関数を役割ごとに分ける、わかりにくい変数名を具体的な名前に変える、といった作業が含まれます。
- 利用者から見える機能や結果は変えません。
- コードの重複や読みにくさを減らします。
- 変更後に、以前と同じ動作をするか確認します。
作業前に動作を確かめる
リファクタリングを始める前に、まず現在の動作を確認します。何が正常な動作なのか決めておかないと、改善後に不具合を入れてしまっても気づきにくくなります。
たとえばユーザー検索アプリなら、次のようなケースを確認します。
- 名前に一致するキーワードを入力すると、該当ユーザーが表示されます。
- 一致するものがない場合は、0件のメッセージが表示されます。
- 空欄の場合は、アプリの仕様に沿った案内が表示されます。
Day 75以降でGitを使っている場合は、作業前にgit statusで状態を確認し、必要なら変更をコミットしておくと、リファクタリング前の状態へ戻しやすくなります。
ステップ1:重複しているコードを見つける
次の例では、検索成功時と検索失敗時に、メッセージ要素の文字列やクラスを別々に変更しています。
// 検索成功時の表示
message.textContent = "検索が完了しました。";
message.className = "success";
// 検索結果がない場合の表示
message.textContent = "該当するデータがありません。";
message.className = "info";
// エラー時の表示
message.textContent = "検索に失敗しました。";
message.className = "error";JavaScript同じように「メッセージ要素の文字列とクラスを設定する処理」が何度も出てきています。このような重複は、表示方法を変更するときに修正漏れが起きる原因になります。
ステップ2:重複処理を関数にまとめる
メッセージ更新の処理を一つの関数にまとめます。
// メッセージの内容と種類をまとめて設定する
function setMessage(text, type = "") {
message.textContent = text;
message.className = type;
}
// 検索成功時
setMessage("検索が完了しました。", "success");
// 検索結果がない場合
setMessage("該当するデータがありません。", "info");
// エラー時
setMessage("検索に失敗しました。", "error");JavaScriptこれで、メッセージを表示する方法が一か所にまとまりました。あとから表示方法を調整したいときも、setMessage()を確認すればよくなります。
重複を見つけるときの注意
コードが似ているからといって、必ず共通関数にすればよいとは限りません。たまたま似ているだけで、今後の役割や変更理由が異なる処理を無理にまとめると、関数に条件分岐が増えてかえって読みにくくなることがあります。
- 処理の目的が同じか確認します。
- 入力や出力のルールが共通しているか確認します。
- 共通化した結果、関数の使い方が複雑にならないか考えます。
ステップ3:長い処理を役割ごとに分ける
次は、APIからデータを取得し、検索し、画面に表示する処理が一つの関数に入っている例です。
// 学習用の例:複数の役割が一つの関数に入っている
async function loadAndSearchUsers() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
if (!response.ok) {
message.textContent = "データを取得できませんでした。";
return;
}
const users = await response.json();
const keyword = keywordInput.value.trim().toLowerCase();
const matchedUsers = users.filter(function (user) {
return user.name.toLowerCase().includes(keyword);
});
resultList.replaceChildren();
matchedUsers.forEach(function (user) {
const item = document.createElement("li");
item.textContent = user.name;
resultList.appendChild(item);
});
message.textContent = `${matchedUsers.length}件見つかりました。`;
}JavaScriptこの関数には、API通信、検索条件の処理、画面の更新が含まれています。どれか一つを変更したいときにも関数全体を読む必要があります。Day 72で学んだ役割分担を思い出し、それぞれを関数に分けます。
ステップ4:役割ごとに関数を作る
API取得、検索、表示を分けた例です。
// APIからユーザー一覧を取得する
async function fetchUsers() {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users"
);
// fetchはHTTPエラーでは自動的に例外を投げないため確認する
if (!response.ok) {
throw new Error("ユーザー情報を取得できませんでした。");
}
// JSONデータを読み取って呼び出し元へ返す
return response.json();
}
// キーワードに一致するユーザーを返す
function filterUsers(users, keyword) {
const normalizedKeyword = keyword.trim().toLowerCase();
// キーワードが空なら、すべてのユーザーを返す
if (normalizedKeyword === "") {
return users;
}
// 名前を小文字にそろえて検索する
return users.filter(function (user) {
return user.name.toLowerCase().includes(normalizedKeyword);
});
}
// ユーザー一覧を画面に表示する
function renderUsers(users) {
// 古い結果を消す
resultList.replaceChildren();
users.forEach(function (user) {
const item = document.createElement("li");
// 外部から取得した文字列はHTMLとして解釈させずに表示する
item.textContent = user.name;
resultList.appendChild(item);
});
}
// 検索結果の案内を更新する
function showResultMessage(count) {
if (count === 0) {
setMessage("該当するデータがありません。", "info");
return;
}
setMessage(`${count}件見つかりました。`, "success");
}
// API取得から画面表示までの流れを管理する
async function loadAndSearchUsers() {
try {
setMessage("データを読み込み中です。", "loading");
const users = await fetchUsers();
const keyword = keywordInput.value;
const matchedUsers = filterUsers(users, keyword);
renderUsers(matchedUsers);
showResultMessage(matchedUsers.length);
} catch (error) {
// 利用者向けには簡潔な案内を表示する
setMessage("データを読み込めませんでした。再度お試しください。", "error");
// 詳細は開発者が確認できるようコンソールに記録する
console.error("ユーザー検索のエラー:", error);
}
}JavaScriptこの形では、それぞれの関数の目的がはっきりしています。
- `fetchUsers()`は、APIからデータを取得します。
- `filterUsers()`は、データを検索条件で絞り込みます。
- `renderUsers()`は、画面に一覧を描画します。
- `showResultMessage()`は、件数に応じた案内を表示します。
- `loadAndSearchUsers()`は、処理全体の順番を管理します。
ステップ5:変数名をわかりやすくする
変数名は、値の意味や役割が伝わるものにします。次のような名前は、短くても意味が読み取りにくい場合があります。
// 役割が分かりにくい名前の例
const d = await response.json();
const x = d.filter(function (u) {
return u.name.includes(q);
});JavaScript内容に合わせて名前を付けると、処理を読みやすくなります。
// 値の役割がわかる名前を使う
const users = await response.json();
const matchedUsers = users.filter(function (user) {
return user.name.includes(keyword);
});JavaScript変数名は、長ければよいわけではありません。短すぎて意味が不明になるのも、長すぎて読みにくくなるのも避け、役割が伝わる長さを選びます。
- `d`より`users`のように、何のデータかを示します。
- `x`より`matchedUsers`のように、どんな状態のデータかを示します。
- `doStuff()`のような曖昧な関数名より、`renderUsers()`のように処理を示します。
ステップ6:変更前後の動作を比べる
コードを整理したら、最初に確認した操作をもう一度実行します。リファクタリングでは、コードが短くなったかだけでなく、以前と同じ結果になることを確認します。
- 通常のキーワードで検索します。
- 一致するものがないキーワードで検索します。
- 入力欄が空のときの動作を確認します。
- 通信エラー時の表示を確認します。
確認する項目を先に書き出しておけば、変更後のチェック漏れを防ぎやすくなります。小さな関数であれば、入力と期待する戻り値を手作業で確認するだけでも役立ちます。
リファクタリングの進め方
初心者の方は、一度にコード全体を書き換えるのではなく、小さな変更を一つずつ行うと安全です。
- 現在の動作を確認します。
- 重複コードを一つ見つけて関数にまとめます。
- その変更後に動作を確認します。
- 次に長い関数を役割ごとに分けます。
- 最後に変数名や関数名を見直します。
一度に大きく書き換えると、問題が起きたときに原因を特定しにくくなります。小さく変更して、その都度確認するのが基本です。
よくある注意点
リファクタリングで気をつけたいのは、整理のつもりで機能まで変えてしまうことです。たとえば、空欄検索時に「全件表示」だったものを「0件表示」に変えると、コードの整理ではなく動作変更も含まれます。
- 関数を分ける際に、処理の順番を変えすぎないようにします。
- 条件分岐を整理するときは、境界値の動作も確認します。
- 変数名を変更するときは、使用している箇所も漏れなく直します。
- 画面表示を変更した場合は、利用者から見える結果も確認します。
リファクタリングと機能追加を同時に進めると、問題の原因が分かりにくくなります。できるだけ別々の作業として扱いましょう。
安全性にも配慮する
コードを整理するときも、外部データの扱いには注意します。APIから取得した名前などを画面に表示する場合は、今回の例のようにtextContentを使うのが基本です。コードを短くしようとして、外部データをそのままHTML文字列へ埋め込む形に変えないようにしましょう。
また、エラー表示を整理するときは、内部の詳細情報をそのまま利用者向け画面に出すのではなく、利用者向けの案内と開発者向けのログを分けます。
Day 78の練習課題
次の課題で、今回の3つのテーマを練習できます。
- 同じメッセージ設定が複数あるコードを、共通関数にまとめます。
- API取得・検索・描画が一つに入った関数を、役割ごとに分けます。
- `a`や`temp`などの変数名を、値の意味がわかる名前に変更します。
- 変更前に確認した操作を再実行し、動作が変わっていないか確かめます。
Day 78のゴール
Day 78では、次のことができるようになっていれば十分です。
- 重複している処理を見つけ、適切な場合に関数へまとめられます。
- 長い処理を、役割が伝わる関数に分けられます。
- 変数名や関数名から、内容を想像できるようにできます。
- 変更前後で動作が同じか確認できます。
- リファクタリングと機能追加を区別できます。
まとめ
Day 78では、プログラムの動作を保ちながら内部のコードを改善するリファクタリングを学びました。重複コードは共通関数にまとめ、長い処理は役割ごとに分け、変数名は意味が伝わるものにします。
重要なのは、一度に大きく書き換えず、変更のたびに動作を確認することです。最初に正常な動きを確かめ、少しずつ整理し、最後に同じ操作を再実行する。この習慣を続けることで、読みやすく、修正しやすく、安全性も確認しやすいJavaScriptコードに近づけられます。
