はじめに
業務でレガシーなシステム(jQuery 1.x系など)のメンテナンスをしていると、「条件分岐が複雑すぎて読めない」「値は画面上と合っているはずなのに、なぜか if 文の判定をすり抜ける」といった壁にぶつかることがよくあります。
今回は、実務のコードレビューやリファクタリングで特に効果の高かった「可読性を爆上げする設計」と「jQuery特有のデータの罠」について、初心者向けに分かりやすく解説します!
1. 読めない if (!A || !B) からの脱却!「エラー検知型」リファクタリング
🚨 よくある「アンチパターン」
フォームのバリデーションなどで、以下のように「関数が正常(true)に終わらなかったらエラー」という条件式をよく見かけます。
// 【アンチパターン】
// プランが変更可能か、変更不可の選択肢が外されていないか、カテゴリ名が変更されているか
if (!_self.validatePlanBeforeSubmit() || !_self.validateLockedOptionsBeforeSubmit() || _self.hasChangedCategoryValues()) {
alert("変更できない値が含まれています");
return false;
}
何が辛いのか?
-
!(否定)と||(または)が組み合わさることで、「何が成功で、何が失敗(エラー)のときにアラートが出るのか」が一瞬で脳内変換できません。 -
!validate()という「成功の否定」は、チェック項目が増えるほど仕様変更の際にバグの温床になります。
💡 改善策:主語を「エラー(異常)があるか?」に変える
関数自体の役割を「バリデーション(成功か?)」から 「エラー検知(異常があるか?)」 に180度変えてみましょう。
// 1. 各チェック関数を「エラーなら true」を返すように定義
hasErrorPlanChange: function () {
var isUnsetSelected = $('#plan_setting_unset').prop('checked');
var isUnsetForbidden = (is_disabled_flg === '1');
// 「未設定への変更禁止」かつ「未設定を選択中」なら【エラーがある(true)】
return isUnsetForbidden && isUnsetSelected;
},
hasErrorLockedOptions: function () {
// ロックされた選択肢が変更されていたら【エラーがある(true)】
// (変更がなければ false を返す)
}
このように定義しておくと、メインの判定部分から !(びっくりマーク)が完全に消滅します。
// どれか一つでも「エラーがある(true)」なら実行
var isPlanError = _self.hasErrorPlanChange();
var isOptionError = _self.hasErrorLockedOptions();
var isCategoryError = _self.hasChangedCategoryValues();
if (isPlanError || isOptionError || isCategoryError) {
alert("変更できない値が含まれています");
return false; // 処理を中断(早期リターン)
}
「エラーがあったらアラートを出す」 という、人間の直感的な思考とコードのロジックが完全に一致するため、チーム開発でも格段に読みやすくなります。
2. 連想配列・オブジェクトの空チェックは $.isEmptyObject() が最強
JSONをパースしたオブジェクトや連想配列 {} の中身が空かどうかを判定するとき、if (obj) だけでチェックしようとするのはNGです。なぜなら、JavaScriptにおいて {}(空のオブジェクト)は true と判定されてしまうから です。
早期リターン(ガード句)を使ってスッキリ書くなら、jQueryの便利メソッドを使いましょう。
function processSettings(settingsObj) {
// settingsObj が null/undefined、または空オブジェクト「{}」なら即リターン
if (!settingsObj || $.isEmptyObject(settingsObj)) {
return false;
}
// ここから下は「確実に中身(キーや値)がある」状態
// ネストを深くせずにメイン処理が書ける
console.log("設定が存在するので処理を続行します");
}
💡 覚えておきたい挙動
$.isEmptyObject() は、null や undefined が渡されてもエラーにならず、安全に true(空である)を返してくれます。レガシー環境(古いjQuery環境など)でもブラウザの挙動に左右されず安全に動くため、実務で非常に重宝します。
3. 実務の罠:.data() と .attr() の決定的な違い
HTMLの data- 属性から値を取得するとき、なんとなく .data() を使っていませんか?
ここが実務で最もバグを生み出しやすいポイントです。
🚨 動かないコードの例
以下のようなHTMLがあるとします。
<div class="target-block" data-item-type="0"></div>
これをJS側で受け取って、サーバーから受け取ったJSONデータのキーと照合しようとします。
const type = $_targetElm.data('item-type'); // 数値の 0 が取れる
// JSONデータのキーは「文字列」
const configData = { "0": ["Premium"] };
// 型まで厳密に比較(===)するロジックの中で...
if (type === "0") { // 0 === "0" は false になる!!
// ここを通らない!
}
🧐 なぜ起きた?
原因は、jQueryの 「自動的な型変換」 です。
| メソッド | 取得先 | 戻り値の型 |
data-item-type="0" のとき |
|---|---|---|---|
.attr('data-item-type') |
HTMLの属性を直接見る | 常に 文字列 (String) |
"0"(文字列) |
.data('item-type') |
jQueryのメモリ内キャッシュ | 自動で 最適な型に変換 |
0(数値) |
.data() は「あ、これ数字の0だな」「true って書いてあるから真偽値(Boolean)だな」と気を利かせて勝手に型を変えてしまいます。そのため、JSONのキー(常に文字列)と比較したり、$.inArray() で厳密に配列内を検索したりするときに、「値は合っているのに型が違って不一致になる」 という怪奇現象が起きます。
💡 解決策
画面のHTMLに書いてある文字をそのまま、手堅く文字列として扱いたいバリデーションなどの処理では、最初から .attr() を使うのが一番安全です。
// HTMLに書いてある文字をそのまま文字列 "0" として取得する
const type = $_targetElm.attr('data-item-type') || '';
まとめ
-
if文の中に!(否定)が増えてきたらリファクタリングのサイン。
「〜に成功したか?」ではなく「〜のエラーがあるか?」に思考を切り替えて関数を設計してみよう。 -
オブジェクトの空判定は
$.isEmptyObject()を使う。
{}はif文をスルーしてしまう罠を回避しよう。 -
data-属性を扱うときは「型」を意識する。
jQueryにお節介な型変換をされたくない時は.attr('data-...')を使おう。
ちょっとした意識の差で、デバッグの時間もコードの綺麗さも劇的に変わります。ぜひ実務のコードで試してみてください!