
生成AIの進化により、GAS(Google Apps Script)の開発スタイルは劇的に変化しました。
GAS(Google Apps Script)のようなローコードであれば、ほとんど全てをバイブコーディングしてる人も多いのではないでしょうか?
しかし、全てをAI任せにするのはとても危険です。
もし中身を分からずにコードを全てAIに任せると、本番環境でこんな痛い目に遭います。
- 誤った上書きや削除を気づかずに実行し、社内データを完全に破壊
- 誤動作を起こし、情報漏洩や誤送信を引き起こす
- エラーの原因が特定できないまま放置され、誰も直せないシステムが完成
AIは、コードの知識は豊富ですが、GAS特有の実行環境やルールまでは、深く考慮せずにコードを出力することが多々あります。
本記事では、AIが生成したコードを本番環境に適用する前に、人間が必ずレビューすべき5つの注意点を解説します。
MASA便利なAIを破壊兵器に変えず、共存する方法について学びましょう!
日付とタイムゾーンのズレ
AIに対して、
「今日の日付を取得して」
と頼むと、彼らは自信満々で標準的なJavaScriptのコードを書いてきます。
しかし、GASはJavaScriptをベースにしていますが、厳密には実行環境が異なります。
Webブラウザ向けの標準的なJavaScriptをGASでそのまま動かすと、環境の違いにより思わぬ不具合が発生します。
今回は、その環境の違いが、「時差」という形で問題を引き起こすのです。
AIの実装傾向
AIはWeb開発の知識が豊富なので、ブラウザで動くことを想定したコードを書きがちです。
// AIが書きがちなコード
function sendDailyReport() {
const today = new Date(); // 今の日付を取得
const dateString = today.toLocaleDateString(); // 文字列に変換
}JavaScript一見、何も問題なさそうに見えます。
実際、もしあなたが普段の作業中(10:00 ~ 19:00の間など)にブラウザから手動で実行ボタン(▷)を押すと、正しく今日の日付が表示されます。

よし、完璧!
とあなたは思います。
見落としのリスク
しかし、例えばこのコードを「毎朝8時に自動実行」するようにトリガーを設定したとします。
すると、今日の日付を記述したいシートになぜか昨日の日付が入ってしまいます。
こうなる原因は、GASが動いているサーバーの時間基準(タイムゾーン)にあります。
Googleのサーバーは世界中で動いているため、特定の国ではなく「協定世界時(UTC)」を基準にしています。
簡単に言うと、国際的に決まってる世界基準の時間ですね!
日本時間(JST)は、協定世界時(UTC)より 9時間進んで います。
| あなた(日本) | 協定世界時(UTC) | 結果 |
| 午後 15:00 | 午前 06:00 | 同じ日付(テスト成功) |
| 午前 08:00 | 前日の 23:00 | 日付ズレ発生(本番失敗) |
AIが書いた new Date() は、サーバー内部の時間をそのまま拾います。
つまりトリガーを実行する時間は協定世界時の時間を使うということです。
こうなると、日本の午前0時〜8時59分の間に実行されるスクリプトは、まだ昨日と判定され、前日の日付で処理が進んでしまうのです。
対策:Utilities.formatDate() で日本時間を強制する
この問題を解決するには、AIに
「JavaScript標準の日付操作を使うな!」
と教える必要があります。
GASには、タイムゾーンを指定して日付を整形する専用メソッド Utilities.formatDate() が用意されています。
それを踏まえて、以下のように修正します。
Session.getScriptTimeZone()で、スクリプトのタイムゾーン(日本時間)を取得する。Utilities.formatDate()で、そのタイムゾーンに基づいた日付文字列を生成する。
// 修正後のコード(AIに指示すべき書き方)
function sendDailyReport() {
const now = new Date();
// タイムゾーンを明示して日付を文字列化する
const dateString = Utilities.formatDate(
now,
Session.getScriptTimeZone(), // ここで 'Asia/Tokyo' (JST) を適用
"yyyy/MM/dd"
);
Logger.log(dateString); // これなら午前8時に実行しても、ちゃんと「今日」になる
}JavaScript日付を扱う際は、標準のDateメソッドではなく、必ず GASの Utilities.formatDate を使用してください。
その際、タイムゾーンは Session.getScriptTimeZone() を指定して、日本時間で計算されるようにしてください。
Browser.msgBoxの書き換え
AIにコードを書かせると、彼らは非常にユーザーフレンドリーな実装を好みます。
エラー時は、
エラーが発生しました!
処理がうまくいった時でも、
完了しました!
とBrowser.msgBoxを使ってポップアップで伝えたがるのです。
ちなみにBrowser.msgBoxとは、スプレッドシートにポップアップでメッセージを表示させる機能です。

これが厄介なのが、「OK」ボタンを押すまで、ずっと処理が進まないことです。
分かるように教えてくれるのはありがたいですが、自動化の実行環境では、この上なく邪魔になります。
AIの実装傾向
AIは、画面越しに人間がいることを前提としたコードを書きます。
例えば、エラーハンドリングを例にすると、こんなコードを書きます。
// AIが書きがちなコード
function weeklyDataProcess() {
try {
// ... 重いデータ処理 ...
} catch (e) {
// 親切心でポップアップを出そうとする
Browser.msgBox("エラーが発生しました: " + e.message);
}
}JavaScript見落としのリスク
Browser.msgBoxの欠点は、「OK」ボタンを押さないと処理が終わらないことです。
例えばこんなケースが起こり得ます。
せっかく自動化の処理を成功させたのに、誰もOKボタンを押さなかったという理由だけでエラーになるという事態になりかねません。
対策:UIクラスを絶対に使わない
自動実行される関数では、Browser や SpreadsheetApp.getUi() といったUI操作クラスを絶対に使ってはいけません。
代わりに、「ログ」や「通知」に置き換えます。
// 修正後のコード
function weeklyDataProcess() {
try {
// ... データ処理 ...
} catch (e) {
// 対策1:ログに残す(管理画面で見られる)
console.error("エラー発生: " + e.message);
// 対策2:メールやチャットで管理者にだけ知らせる
MailApp.sendEmail("admin@example.com", "GASエラー通知", e.message);
}
}
JavaScriptこのスクリプトはトリガーで自動実行されるため、Browser.msgBox や SpreadsheetApp.getUi() などの対話型UIは絶対に使用しないでください。
ログ出力には console.log を、エラー通知には console.error (または MailApp)を使用してください。
Activeメソッドの取り扱い

手動でテストした時は完璧だったのに、夜中に勝手に動かしたらデータがめちゃくちゃになった!
そんな怪奇現象の原因No.1がこれです。
AIにコードを書かせると、彼らは、いま人間が画面を見ているという前提でコードを書きます。
具体的には、以下の2つのActiveメソッドを多用する傾向があります。
getActiveSheet()-
今開いているシート
getActiveRange()-
今カーソルがあるセル
これらはテスト環境では動くけど、本番環境では全く機能しない可能性が高いので取り扱い注意です。
AIの実装傾向
例えばあなたが、
「選択したデータをログシートに転記して、完了したら元のセルに『転記済』と入力して」
と指示したとします。
AIはその指示に対して忠実に、シートの内容を転記するためのコードを書いてくれます。
// AIが書いたコード(ボタンで実行するなら正解)
function archiveTask() {
// 「今アクティブなシート」を取得
const sheet = SpreadsheetApp.getActiveSheet();
// 「今選択しているセル」の値を取得
const range = sheet.getActiveRange();
const value = range.getValue();
// ログシートに転記(省略)...
// ★ここが危険!元の場所を「転記済」で上書きする
range.setValue("転記済");
}JavaScriptテスト実行でボタンを押すと、見事に選択された文字が転記され、あなたは「完璧だ」と思います。
見落としのリスク
しかし、
「これ便利だから、毎晩0時に自動で実行させよう」
と、このコードをこのままトリガーにセットした瞬間に悲劇が確定します。
深夜に自動実行される際、誰もファイルを開いていませんし、選択しているセルもありません。
この状態でスクリプトが動くと、GASは混乱して以下のような行動に出ます。
getActiveSheet-
とりあえず一番左のシートを対象にする
getActiveRange-
とりあえずA1セルを対象にする
結果として、全く関係ないところにデータを上書きしたりして、ファイルを破壊してしまうのです。
対策:場所と座標をご指名する
自動化するコードでは、「選択している場所」という曖昧な概念は捨ててください。
「どのシート」の「どの座標(何行目・何列目)」なのか、住所を特定するように記述する必要があります。
getActiveSheet()→getSheetByName("シート名")getActiveRange()→getRange(行, 列)
ファイル自体を取得する getActiveSpreadsheet() は使用しても問題ありません。
function archiveTasks() {
//getActiveSpreadsheet()は使ってOK!
const ss = SpreadsheetApp.getActiveSpreadsheet()
// 1. シートをご指名(名前で固定)
const logSheet = ss.getSheetByName("ログ");
// シートがない時の安全策
if (!logSheet) return;
// 2. 場所をご指名(カーソル位置ではなく、必ず「最終行の次の行」を計算させる)
const lastRow = logSheet.getLastRow();
// 座標(行, 列)を指定して書き込む
logSheet.getRange(lastRow + 1, 1).setValue("完了");
}JavaScript自動実行時の誤動作を防ぐため、getActiveSheet() や getActiveRange() などのActiveメソッドは使用しないでください。
for文の取り扱い(1行ずつの書き込み禁止)

テストデータ10件で試したら無事成功した!
そう思って本番データ(3,000件)を流した途端、処理がいつまでも終わらず、最後には
「Exceeded maximum execution time」
という無慈悲なエラーで強制終了する。
これは、GAS初心者が必ず通る道であり、AIが最も苦手とする規模の予測の欠如によるものです。
AIの実装傾向
例えば、
「A列に1から3000までの連番を振って」
とAIに頼むと、彼らは非常に素直なコードを書きます。
for文の中で、「1行ずつ数字を書き込む」など、一つずつの処理をfor文の中で実行しようとします。
// AIが書きがちなコード(少量なら動くが、大量だと死ぬ)
function writeNumbers() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("シート1");
// 1から3000まで繰り返す
for (let i = 1; i <= 3000; i++) {
// 【危険】1回ずつスプレッドシートに書き込みに行っている
sheet.appendRow([i]);
}
}JavaScriptテスト段階の少量のデータでやってみたらこれは成功します。
見落としのリスク
しかし、GASにおいて「スプレッドシートへの書き込み処理(appendRow や setValue)」は、非常に遅い処理です。
GASのプログラムとスプレッドシートは、実は離れた場所にあります。
GASからスプレッドシートに書き込む処理は1回につき約0.2秒です。
もしその処理が1度に3,000回繰り返すとどうなるでしょうか?
0.2秒 × 3000回 = 600秒(10分)
GASには、1回の実行は最大6分(360秒)まで、というルールがあるので、処理の途中でタイムアウトし、強制終了します。
対策:配列にまとめて一気に書き込む
この問題を解決するには、荷物をトラック(配列)に全部積んでから、1回で運ぶように書き換える必要があります。
つまり、for文の中でappendRow(1行追加)を3000回やるのではなく、for文の中でデータを配列にまとめてsetValuesを1回だけやります。
// 修正後のコード(高速・大量データ対応)
function writeNumbers() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("シート1");
// 1. トラック(配列)を用意する
const data = [];
// 2. ループ内では「配列に追加する」だけにする(これはメモリ上の処理なので爆速)
for (let i = 1; i <= 3000; i++) {
data.push([i]);
}
// 3. 最後に1回だけ書き込む(通信は1回だけ!)
if (data.length > 0) {
// A1セルから、データの数だけ範囲を確保して書き込む
sheet.getRange(1, 1, data.length, 1).setValues(data);
}
}JavaScriptこれなら、3000件のデータでも数秒で処理が完了して、タイムアウトに遭遇する確率もガクっと減らせます。
スプレッドシートへの書き込みを行う際は、for文の中で appendRow や setValue を繰り返さないでください。
実行速度制限を回避するため、必ず処理結果を二次元配列に格納し、最後に setValues を使って一括で書き込むコードにしてください。
同時実行の制御(LockService)

問い合わせ番号(No.001、No.002…)を自動で採番して、重複しないように管理したい!
これは実務で非常によくある要件です。
テスト段階のような、自分ひとりしかアクセスしていない環境では完璧に連番が振られます。
しかし、社員みんなで使い始めた途端、

AさんとBさんの番号が、なぜか両方とも『No.100』になっている!
という怪奇現象が発生します。
これが起こる原因は、「データの読み取り」と「書き込み」の間に、わずかなタイムラグが必ず発生してしまうからです。
AIの実装傾向
AIに
「最終行の番号を取得して、+1 した番号を振って」
と頼むと、以下のようなロジックを組みます。
// AIが書きがちなコード(排他制御なし)
function issueId() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("管理表");
// 1. 今の一番新しい番号を見る(例:100)
const currentId = sheet.getRange(sheet.getLastRow(), 1).getValue();
// 2. 新しい番号を作って書き込む(101)
sheet.appendRow([currentId + 1]);
}JavaScriptこのコードでは、アクセスのタイミングが重なった瞬間に、複数のユーザーに対して同じ連番を渡してしまいます。
対策:LockServiceで同時に実行を制御
この事故を防ぐには、データの読み書きを行っている間、他のユーザーによる割り込みをブロックする仕組みが必須です。
具体的には、誰かが処理を実行している間は、後から来た人を一時停止させる処理を実行します。
これにより、タイミングが重なって同じ番号を読み取ってしまう事故を物理的に防ぐことが出来ます。
function issueId() {
// 順番待ちシステムを用意
const lock = LockService.getScriptLock();
// 1. 他の人が使っていたら、終わるまでここで待機する(最大30秒)
lock.waitLock(30000);
try {
// ==== ここから先は、自分一人しか実行していない状態 ====
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("管理表");
const currentId = sheet.getRange(sheet.getLastRow(), 1).getValue();
// 邪魔されずに連番を発行
sheet.appendRow([currentId + 1]);
// 変更を即座に確定させる
SpreadsheetApp.flush();
// ==== ここまで ====
} catch (e) {
// 2.もし途中でエラーが起きたら、メッセージを表示する
console.log("エラーが発生しました。処理を中断します。\\n理由: " + e.message);
}finally {
// 3. 処理が終わったので、次の人のために待機を解除する
lock.releaseLock();
}
}
JavaScript簡単なコード解説
waitLock の行に来た瞬間、プログラムは、Google本部へ問い合わせを行います。
あなた処理を始めたいので、鍵をください!(waitLockを実行)
Google本部あ、今Bさんが使ってるから、そこで待ってて!
ここで、あなたのプログラムの処理は、一旦停止します。
その停止時間が「30000ミリ秒」つまり「30秒」ですね。
もし30秒以内に前の人(Bさん)がデータを確定させ(flush())、鍵を返却すれば(releaseLock())、
Google本部はい、おまたせ。次はあなたの番ですよ
と、あなたのプログラムが再開します。
MASAこうやって処理を実行するタイミングを図ることでデータの重複を防ぎます!
データの重複を防ぐため、LockService を使用して排他制御を行ってください。
処理中は lock.waitLock() でロックし、try...finally 構文を使って、処理終了後には必ず lock.releaseLock() でロックを解除するようにしてください。
まとめ
生成AIの登場でGAS開発は驚くほど簡単になりました。
しかし、AIは指示されたことしか理解できず、物事の背景までは理解できません。
「どれくらいの規模で行う処理なのか」
「どんな環境で動くのか」
「誰が使うものなのか」
そうした複雑な事情をコードに落とし込めるのは、AIではなく、現場を知る人間だけです。
AIのスピードを活かしつつ、人間がGAS特有の落とし穴を冷静に回避することこそが、最強のバイブコーディングと言えます。