マキモノ
Google Workspace無料✅ 公式検証済みv1.0.0 / 更新

google.script.run の null が undefined になる罠 — 本番だけ出る NaN 表示の自動検出

GASウェブアプリでサーバ側の null がブラウザで undefined になり、=== null の分岐が素通りして NaN が表示される。単体テストでもJSON fixtureでも再現しない理由と、null を落とした fixture で描き直して自動検出する方法。

出品者: seisaku-team@orgiast.jp1 DL📖 読込 約2,187トークン (約3円)💰 コスパ 16
トークン節約メーター88%節約
ゼロからAIに作らせた場合4万トークン
このMDを読ませた場合5,000トークン

3.5万トークンの節約 (API料金換算で約53円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

google.script.run の null が undefined になる罠 — 本番だけ出る NaN 表示の自動検出」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。GASウェブアプリでサーバ側の null がブラウザで undefined になり、=== null の分岐が素通りして NaN が表示される。単体テストでもJSON fixtureでも再現しない理由と、null を落とした fixture で描き直して自動検出する方法。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.5万トークン(API料金換算で約53円)・88%のトークンを節約できます。

カテゴリ
Google Workspace
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約4万トークン
この巻物使用時
約5,000トークン
節約量
約3.5万トークン (約53円)
更新日
2026-09-02

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/google-script-run-null-undefined-nan/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

google.script.run の null が undefined になる罠と、本番だけ出る NaN 表示の自動検出

Google Apps Script のウェブアプリで、サーバ側の null がブラウザ側で undefined になって届くことによる表示バグと、その再発を機械的に止める方法。単体テストでは原理的に検出できない種類の不具合を対象にする。

症状

画面に NaN が出る。よくある出方:

  • 集計見出しが 実績 NaN個 / 予定比 NaN%
  • 表のセルが NaN人 NaN円 NaN件

しかもサーバ側のデータを直接読むと、その項目は正しく null(=未入力)になっている。ログにもエラーが出ない。

原因

google.script.run の戻り値はブラウザへ渡る途中で独自にシリアライズされ、オブジェクトのプロパティが null のとき undefined として届くことがある。そのため次の分岐が素通りする。

// 危険: undefined は null と等しくないので else 側へ落ちる
`${v === null ? "—" : `${n(v)}個`}`   // n(undefined) → NaN

Number(undefined)NaN なので、そのまま画面に出る。

なぜテストで見つからないか

これが厄介なのは、同じデータでも取得経路によって現れ方が違う点にある。

経路null の届き方NaN が出るか
google.script.run(実ブラウザ)undefined になることがある出る
JSON.stringify を通す経路(コマンドキュー、Web API、ログ出力)null のまま出ない

サーバ側のロジックを配列で叩く単体テストはもちろん、サーバの戻り値を JSON で保存した fixture を画面に食わせるヘッドレス検証でも再現しない。実ブラウザで人が見るまで誰も気付かない。

対処

1. 数値表示を1か所に集約する

「数値として表示できる値だけを返し、それ以外は null に畳む」ヘルパーを1つ用意し、表示の分岐はこれを通す。

// 数値として表示できる値だけを返し、それ以外は null にする。
// google.script.run はサーバ側の null を undefined にして渡すことがあり、
// null 判定だけだと Number(undefined) が NaN になって画面に出る。
function num(v) {
  return v === null || v === undefined || v === "" || !Number.isFinite(Number(v))
    ? null
    : Number(v);
}

使い方:

// 修正前
`${v === null ? "—" : `${n(v)}個`}`
// 修正後
`${num(v) === null ? "—" : `${n(v)}個`}`

すでに ?? "" を通しているエスケープ関数は無害。 例えば esc(v) の実装が String(v ?? "") なら、undefined でも空文字になるので壊れない。危ないのは Number() を経由する表示だけ。既存コードを直すときは === null / !== null を全部拾い、そのうち数値表示に使われているものだけを num() へ寄せる。

2. 「null を undefined に落とした fixture」で描画して自動検出する

jsdom で画面を組み立てる検証を持っているなら、同じ fixture の null を再帰的に消してから描き直し、NaN が1つも出ないことを判定する。これで実ブラウザ固有の差を CI で捕まえられる。

// google.script.run はサーバ側の null を undefined にして渡すことがある。
// JSON 経由では null のまま届くため、この差は実ブラウザでしか現れない。
function dropNulls(value) {
  if (Array.isArray(value)) return value.forEach(dropNulls);
  if (!value || typeof value !== "object") return;
  for (const [key, item] of Object.entries(value)) {
    if (item === null) delete value[key];
    else dropNulls(item);
  }
}

const nullFreeFixture = JSON.parse(await fs.readFile(fixturePath, "utf8"));
dropNulls(nullFreeFixture);
await reloadWith(nullFreeFixture);   // fixture を差し替えて画面を描き直す

for (const tabName of ["<検証したい画面1>", "<検証したい画面2>"]) {
  document.querySelector(`#tabs button[data-t="${tabName}"]`).click();
  // NaN を含む最も内側の要素だけを挙げ、どの表示が壊れたか一目で分かるようにする。
  const found = [...document.querySelectorAll("#app *")]
    .filter((el) => el.textContent.includes("NaN")
      && ![...el.children].some((child) => child.textContent.includes("NaN")))
    .map((el) => `<${el.tagName.toLowerCase()}> ${el.textContent.replace(/\s+/g, " ").trim().slice(0, 60)}`);
  if (found.length) throw new Error(`${tabName} に NaN が${found.length}箇所: ${[...new Set(found)].slice(0, 3).join(" / ")}`);
}

失敗メッセージは「最も内側の要素」に絞ること。 画面全体のテキストを出すと数千文字のブロックになり、どこが壊れたのか読み取れない。上の形なら <td> NaN人 と出る。

fixture の差し替えと再描画

fixture をモジュールスコープの let にしておき、モックの応答関数がそれを返すようにしておくと差し替えられる。再描画の完了は、再描画前の要素が DOM から外れたことで判定するのが確実(内容が同じだと文字列比較では判定できない)。

async function reloadWith(next) {
  const stale = document.querySelector("#tabs button");
  fixture = next;
  window.reload();                       // アプリ側の再取得+再描画
  await waitFor(() => !document.contains(stale));
}

判定が終わったら元の fixture に戻してから画面ダンプを書き出す。差し替えたままだと、あとでダンプを読んだ人が実データと取り違える。

導入時に必ず起きること

この判定を入れると、その場で未発見の同型バグが出てくるのが普通。実例では、月見出しの NaN個 / NaN% を直した直後にこの判定を追加したところ、別の列(未入力の人数欄)が NaN人 になっていたことが即座に分かった。人が見ていた画面にずっと出ていたのに、誰も報告していなかった。

最初の実行で FAIL が出ても検証コードを疑わず、まず画面を疑うこと

チェックリスト

  • num() に相当する正規化ヘルパーを1つ用意した
  • === null / !== null を全部拾い、数値表示のものを num() に寄せた
  • null を落とした fixture で全画面を描き直す判定を追加した
  • 失敗メッセージが「最も内側の要素」に絞られている
  • 判定後に元 fixture へ戻してからダンプを書き出している
  • 初回実行で出た FAIL を、検証コードではなく画面の側で直した

よくある質問

「google.script.run の null が undefined になる罠 — 本番だけ出る NaN 表示の自動検出」とは何ですか?

GASウェブアプリでサーバ側の null がブラウザで undefined になり、=== null の分岐が素通りして NaN が表示される。単体テストでもJSON fixtureでも再現しない理由と、null を落とした fixture で描き直して自動検出する方法。

どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約4万トークンかかりますが、この巻物を使えば約5,000トークンで済みます。差し引き約3.5万トークン(API料金換算で約53円)・88%の節約です。

どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア