「さっきまで動いていたスクリプトが、いきなり動かなくなった」「ネットで見つけたコードをコピペしたのに全然動作しない」。Google Apps Script(GAS)を使っていると、こんな経験をした方は少なくないのではないでしょうか。
実はスプレッドシートのスクリプトが動かない原因は多岐にわたります。単純なスペルミスから、Googleアカウントの権限問題、さらには2026年1月末に予定されているRhinoランタイムの完全廃止まで、知っておくべきことは山ほどあります。この記事では、GASの専門家として、あらゆる原因を徹底解説し、確実に問題を解決できるノウハウをお伝えします。
- スプレッドシートのスクリプトが動かない15の原因と具体的な対処法を網羅的に解説
- 2026年1月に廃止されるRhinoランタイムからV8への移行方法と注意点
- エラーメッセージから原因を素早く特定するためのデバッグテクニック
- スプレッドシートのスクリプトが動かない主な原因を把握しよう
- エラーメッセージから原因を特定するデバッグ手法
- スクリプトエディタが開けないときの対処法
- doPost関数が動かないときに確認すべきポイント
- トリガーが突然動かなくなった場合の対処法
- 2026年1月のRhinoランタイム廃止に向けた対応
- デプロイが完了しないときの解決策
- GASの制限事項と回避策を理解しよう
- 複数ファイル構成のプロジェクトで発生する問題
- GCPプロジェクトとの連携で発生するエラー
- 情シス歴10年以上が語る現場で本当に起きるGASトラブルと対処法
- 現場で使える実践的なGASコード集
- トリガーの棚卸しと健全性チェックの方法
- スプレッドシートが重くなりすぎた時の対処法
- 複数人が同時編集するときのスクリプト競合を防ぐ
- ライブラリ依存で発生する厄介な問題
- GASプロジェクトのバックアップ戦略
- ぶっちゃけこうした方がいい!
- スプレッドシートのスクリプトが動かないに関する疑問解決
- 今すぐパソコンやスマホの悩みを解決したい!どうしたらいい?
- まとめ
スプレッドシートのスクリプトが動かない主な原因を把握しよう
GASが動かない原因は大きく分けて、コード自体の問題、環境設定の問題、そしてGoogle側の仕様や制限に起因する問題の3つに分類できます。まずは全体像を把握することで、効率的にトラブルシューティングを進められるようになりましょう。
コード関連の問題としては、文法エラー、変数の未定義、constへの再代入などが代表的です。これらは実行ログにエラーメッセージが表示されるため、比較的発見しやすいといえます。一方で厄介なのは、保存時にはエラーが出ないのに実行時だけエラーになるケースです。たとえば、複数のファイルで同じ名前の定数を定義してしまった場合、保存はできますが実行するとエラーになります。
環境設定の問題で最も多いのは、複数Googleアカウントでログインしている際の権限問題です。Chromeブラウザにログインしているアカウントとスプレッドシートにログインしているアカウントが異なる場合、スクリプトエディタが開けなくなったり、トリガーが正常に動作しなくなったりします。
Google側の仕様に関わる問題としては、APIクォータの制限、実行時間の上限、そしてランタイムの変更などがあります。特に2026年1月31日にRhinoランタイムが完全廃止されることは、多くの開発者が見落としがちな重大な問題です。
エラーメッセージから原因を特定するデバッグ手法
GASでスクリプトが動かないとき、最初にすべきことは実行ログの確認です。スクリプトエディタの左メニューにある「実行数」をクリックすると、過去の実行履歴とエラーメッセージを確認できます。エラーメッセージは英語で表示されることが多いですが、そのままGoogleで検索すれば解決策が見つかることがほとんどです。
エラーメッセージで検索しても解決策が見つからない場合は、検索ワードを工夫してみてください。たとえば「doPost エラー GAS」「トリガー 動かない Apps Script」のように、何をしようとしてエラーになったのかを含めて検索すると、より適切な情報にたどり着けます。
デバッグ機能を活用した原因特定
GASにはブレークポイントを設定してステップ実行できるデバッガ機能が搭載されています。スクリプトエディタで行番号の左側をクリックするとブレークポイントを設定でき、「デバッグ」ボタンを押すとその行で実行が一時停止します。この状態で変数の中身を確認できるため、どの時点でおかしな値になっているかを特定できます。
また、console.log()やLogger.log()を使って変数の値をログ出力する方法も有効です。ただし、doPost関数やdoGet関数など外部からのHTTPリクエストで実行される関数では、これらのログ出力が標準の実行ログには表示されません。この問題については後述する専用のセクションで詳しく解説します。
スクリプトエディタが開けないときの対処法
「現在、ファイルを開くことができません。アドレスを確認して、もう一度試してください。」というエラーメッセージが表示されてスクリプトエディタが開けない場合、ほぼ確実にGoogleアカウントの問題が原因です。
Googleには「デフォルトアカウント」という概念があり、複数のアカウントでログインしている場合、最初にログインしたアカウントがデフォルトアカウントになります。GASのスクリプトエディタは、このデフォルトアカウントの権限でアクセスを試みるため、スプレッドシートを作成したアカウントがデフォルトアカウントでない場合にエラーが発生します。
解決方法その1として全アカウントからログアウトする
最も確実な解決方法は、一度すべてのGoogleアカウントからログアウトし、スプレッドシートを作成したアカウントで最初にログインすることです。Googleドライブの右上に表示されるアカウントアイコンをクリックし、「すべてのアカウントからログアウトする」を選択します。その後、使用したいアカウントで再度ログインすれば、スクリプトエディタが正常に開けるようになります。
解決方法その2としてシークレットウィンドウを使用する
毎回ログアウトするのが面倒な場合は、Google Chromeのシークレットウィンドウを活用しましょう。シークレットウィンドウでは既存のログイン情報が引き継がれないため、必要なアカウントだけでログインして作業できます。ショートカットキーは「Ctrl+Shift+N」(Macの場合は「Command+Shift+N」)です。
解決方法その3としてChromeプロファイルを分ける
恒久的な対策としては、Chromeのプロファイル機能を使ってアカウントごとにブラウザ環境を分けることをおすすめします。これにより、アカウントの切り替えが容易になり、権限の混乱も防げます。
doPost関数が動かないときに確認すべきポイント
外部アプリとの連携でよく使われるdoPost関数は、独特のトラブルが発生しやすい関数です。doPost関数が動かない場合、以下の点を順番に確認していきましょう。
まず理解しておくべきなのは、doPost関数はGASエディタから直接実行してもエラーになるという点です。doPost関数は外部からのHTTPリクエストを受け取って実行される関数であり、引数のeにはリクエストの情報が格納されます。エディタから実行した場合、このeには何も入っていないため、必然的にエラーが発生します。
デプロイ設定の確認
doPost関数を外部から呼び出すには、プロジェクトをウェブアプリとしてデプロイする必要があります。デプロイ時の設定で「アクセスできるユーザー」が「自分のみ」になっていると、外部からのアクセスができません。外部アプリとの連携が目的であれば、「全員」または「Googleアカウントを持つ全員」に変更してください。
再デプロイの重要性
コードを修正したあとに忘れがちなのが再デプロイです。GASでは、コードを保存しただけではデプロイ済みのウェブアプリには反映されません。「デプロイ」→「デプロイを管理」から新しいバージョンとしてデプロイするか、「新しいデプロイ」で別のURLを発行する必要があります。
doPost関数のログを確認する方法
doPost関数はconsole.logを使っても通常の実行ログには出力されないため、デバッグが困難です。この問題を解決するには、Google Cloud Platform(GCP)と連携してログエクスプローラを使用する方法と、スプレッドシートにログを出力する方法の2つがあります。
GCPとの連携はやや手順が複雑ですが、一度設定すれば詳細なログ分析が可能になります。一方、スプレッドシートへのログ出力は手軽に実装でき、リアルタイムでログを確認できるメリットがあります。スプレッドシートにログを出力する場合は、doPost関数の冒頭で以下のようなコードを追加します。
ログ出力用のシートを用意し、関数の各処理ポイントで時刻とメッセージを書き込むことで、どこでエラーが発生しているかを特定できます。エラーが発生しても処理が中断されないよう、try-catch文で囲むことも重要です。
トリガーが突然動かなくなった場合の対処法
「昨日まで動いていたトリガーが、今日になったら動かなくなった」という状況は、GASユーザーなら誰もが経験する悩みの種です。トリガーが動かなくなる原因は複数考えられます。
権限の再認証が必要なケース
トリガーで実行されるスクリプトが新しいサービスを使用するようになった場合、権限の再認証が必要になります。トリガー経由での実行では権限承認のダイアログが表示されないため、スクリプトは権限エラーで失敗します。解決方法は、スクリプトエディタを開いて手動で関数を実行し、表示される権限承認ダイアログで承認することです。
トリガーの自動無効化
長期間使用されていないトリガーは、Googleによって自動的に無効化されることがあります。具体的には、9〜12ヶ月間トリガーが発火していないと、自動的に停止される可能性があります。定期的にトリガーが正常に動作しているか確認し、必要に応じて再設定することをおすすめします。
クォータ制限に抵触したケース
GASには日次クォータと呼ばれる使用量制限があります。たとえば、トリガーの実行回数、メール送信数、URL Fetch呼び出し回数などに上限が設定されています。これらの上限に達するとトリガーが実行されなくなります。クォータはGoogleアカウントの種類(個人用か Google Workspace か)によって異なります。
2026年1月のRhinoランタイム廃止に向けた対応
2025年2月20日にGoogleは公式に発表しました。Rhinoランタイムは2026年1月31日をもって完全に廃止され、以降はRhinoで動作しているスクリプトは実行できなくなります。これは、長年GASを使ってきたユーザーにとって非常に重要なお知らせです。
V8ランタイムは2020年にリリースされた新しいJavaScriptエンジンで、モダンなJavaScript構文(let、const、アロー関数、テンプレートリテラルなど)をサポートしています。ただし、RhinoからV8への移行には注意が必要な点があります。
V8移行時の互換性問題
V8への移行で特に注意すべき互換性の問題があります。for each…in文はV8ではサポートされていないため、for…of文に書き換える必要があります。また、Date.prototype.getYear()の動作がRhinoとV8で異なります。Rhinoでは1900〜1999年の間は2桁の年を返しますが、V8では常に「年 – 1900」を返すため、getFullYear()を使用するよう修正が必要です。
さらに、constで宣言した変数への再代入はRhinoでは黙認されていましたが、V8では厳密にエラーになります。該当箇所をletに変更するか、再代入しないようロジックを修正してください。
V8ランタイムへの移行手順
V8への移行は比較的簡単です。スクリプトエディタの左メニューから「プロジェクトの設定」を開き、「Chrome V8ランタイムを有効にする」にチェックを入れるだけです。ただし、有効化する前に上記の互換性問題がコード内に存在しないか確認し、必要な修正を行ってください。
移行後は必ず全機能のテストを行ってください。特にトリガーで実行される関数や、ウェブアプリとしてデプロイしている関数は、手動実行だけでなく実際の動作環境でテストすることが重要です。
デプロイが完了しないときの解決策
「デプロイを読み込んでいます」という表示のまま、いつまでもデプロイが完了しないことがあります。この問題の明確な原因は特定されていませんが、いくつかの解決方法が知られています。
まず試すべきは、スクリプトを保存してエディタを閉じ、再度開くことです。ブラウザのキャッシュが原因でデプロイ処理が正常に動作しないことがあるため、一度エディタを閉じることで解消される場合があります。
それでも解決しない場合は、新しいスクリプトファイルを作成し、そこにコードをコピーして貼り付ける方法を試してください。古いファイルに何らかの問題が発生している可能性があり、新規ファイルでは正常にデプロイできることがあります。
GASの制限事項と回避策を理解しよう
GASには様々な制限が設けられており、これらを超過するとスクリプトが正常に動作しなくなります。主要な制限を把握し、必要に応じて回避策を講じることが重要です。
| 制限項目 | 個人アカウント | Google Workspace |
|---|---|---|
| スクリプト実行時間 | 6分 | 6分 |
| 日次トリガー実行回数 | 20回/ユーザー | 20回/ユーザー |
| カスタム関数実行時間 | 30秒 | 30秒 |
| URL Fetch呼び出し | 20,000回/日 | 100,000回/日 |
6分の実行時間制限を回避する方法
大量のデータを処理する場合、6分の実行時間制限に抵触することがあります。この問題を回避するには、処理をバッチに分割する方法が有効です。具体的には、処理の進捗状況をスクリプトプロパティに保存し、タイムドリブントリガーを使って一定間隔で処理を再開させます。
また、スプレッドシートへの書き込み回数を減らすことも効果的です。セルに1つずつ値を書き込むのではなく、配列に値を格納してからsetValues()で一括書き込みすることで、処理時間を大幅に短縮できます。
複数ファイル構成のプロジェクトで発生する問題
GASプロジェクトが大きくなると、コードを複数のファイルに分割することがあります。しかし、ファイル分割には注意が必要です。
V8ランタイムでは、ファイルの解析順序が重要になります。あるファイルの関数が別のファイルで定義されたグローバル変数を参照する場合、変数が定義される前に参照されるとエラーになることがあります。この問題を避けるには、グローバル変数の使用を最小限に抑え、必要な値は関数の引数として渡すようにしましょう。
また、同じ名前の定数を複数ファイルで定義すると、実行時にエラーになります。定数名にはファイル名をプレフィックスとして付けるなど、名前の衝突を防ぐ工夫をしてください。
GCPプロジェクトとの連携で発生するエラー
スプレッドシートAPIやDrive APIなどの高度な機能を使用する場合、GASプロジェクトをGoogle Cloud Platform(GCP)プロジェクトと連携させることがあります。この連携を行っている場合、GCP側でAPIが有効化されていないとエラーが発生します。
エラーメッセージに「API has not been used」や「API is not enabled」と表示された場合は、GCPコンソールで該当するAPIを有効化してください。GCPコンソールにログインし、「APIとサービス」→「ライブラリ」から必要なAPIを検索して有効化します。
情シス歴10年以上が語る現場で本当に起きるGASトラブルと対処法
ここからは、企業の情報システム部門で10年以上の経験を持つ視点から、ネット上ではほとんど語られていない「現場で本当に起きる問題」とその解決法をお伝えします。これらは実際に何度も遭遇し、解決してきた生々しい経験に基づいています。
退職者が作ったトリガーが止まらない問題
組織でGASを運用していると必ず遭遇するのが、退職者が作成したトリガーの暴走です。トリガーは作成者のアカウントで実行されるため、退職してアカウントが無効化されると、トリガーがエラーを吐き続けたり、逆に完全に停止したりします。
最悪なのは、退職者のアカウントがすぐに削除された場合です。スクリプトの編集権限すら失われ、誰もトリガーを停止できない状況に陥ります。これを防ぐには、GASプロジェクトのオーナーを必ず共有グループアカウントまたは在職者に移譲する退職フローを確立しておくことが重要です。
具体的には、退職が決まった時点で以下の手順を踏みます。まずGoogle Apps Script ダッシュボード(script.google.com)にアクセスし、該当者が作成したすべてのプロジェクトを一覧表示します。各プロジェクトの「共有」設定から、後任者またはシステム管理用アカウントをオーナーとして追加します。その後、元のオーナー権限を削除し、トリガーを一度すべて削除してから、新しいオーナーアカウントで再作成します。
onEdit関数が動かない本当の理由
「onEditを書いたのに全然動かない」という相談を数え切れないほど受けてきました。実はこれ、シンプルトリガーとインストーラブルトリガーの違いを理解していないことが原因であることがほとんどです。
onEdit()という名前で関数を定義すると、シンプルトリガーとして自動的に登録されます。しかしシンプルトリガーには厳しい制限があり、外部サービスへのアクセス、メール送信、他のスプレッドシートの操作などはすべてできません。つまり、onEdit内でMailApp.sendEmail()を書いても、権限エラーで失敗するのです。
これを解決するには、インストーラブルトリガーを使用します。関数名をonEditではなく任意の名前(例handleEdit)に変更し、「トリガー」メニューから手動でトリガーを追加します。イベントの種類で「編集時」を選択すれば、権限のあるトリガーとして動作します。
カスタム関数が突然動かなくなるケース
スプレッドシートのセルに「=MYFUNCTION()」のようなカスタム関数を設定していると、ある日突然#ERROR!が表示されることがあります。昨日まで動いていたのに、何も変更していないのになぜか動かない。
この現象の多くは、Googleのサーバー側でのキャッシュ問題が原因です。カスタム関数の結果はキャッシュされており、引数が同じ場合は再計算されません。しかし、このキャッシュが破損したり、タイムアウトしたりすることがあります。
解決方法は意外と単純で、カスタム関数の引数にダミーの値を追加することです。例えば、「=MYFUNCTION(A1)」を「=MYFUNCTION(A1, NOW())」に変更すると、NOW()の値が毎分変わるため、キャッシュが効かなくなります。ただし、これは再計算の頻度が上がるため、パフォーマンスとのトレードオフになります。
現場で使える実践的なGASコード集
ここでは、長年の運用経験から生まれた、どの現場でもすぐに使える実践的なコードを紹介します。これらはコピペしてそのまま使えるよう設計していますが、必ずご自身の環境でテストしてから本番投入してください。
堅牢なエラーハンドリングとSlack通知
本番環境で動くスクリプトには、必ずエラーハンドリングとアラート通知を実装すべきです。以下のコードは、エラー発生時にSlackに通知を送る汎用的なラッパー関数です。
/**
* エラーハンドリング付きで関数を実行し、失敗時にSlack通知
* @param {Function} fn - 実行する関数
* @param {string} functionName - 関数名(ログ用)
*/
function executeWithErrorHandling(fn, functionName) {
const startTime = new Date();
try {
fn();
const duration = (new Date() - startTime) / 1000;
console.log(`${functionName} completed in ${duration}s`);
} catch (error) {
const errorInfo = {
functionName: functionName,
errorMessage: error.message,
stackTrace: error.stack,
timestamp: new Date().toISOString(),
spreadsheetUrl: SpreadsheetApp.getActiveSpreadsheet()?.getUrl() || 'N/A'
};
// スプレッドシートにエラーログを記録
logErrorToSheet(errorInfo);
// Slack通知(WebhookURLは環境に合わせて設定)
sendSlackNotification(errorInfo);
// エラーを再スロー(トリガーのエラーメールも送られる)
throw error;
}
}
function logErrorToSheet(errorInfo) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
let logSheet = ss.getSheetByName('ErrorLog');
if (!logSheet) {
logSheet = ss.insertSheet('ErrorLog');
logSheet.appendRow);
}
logSheet.appendRow[
errorInfo.timestamp,
errorInfo.functionName,
errorInfo.errorMessage,
errorInfo.stackTrace,
errorInfo.spreadsheetUrl
]);
}
function sendSlackNotification(errorInfo) {
const webhookUrl = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_URL');
if (!webhookUrl) {
console.warn('Slack Webhook URL not configured');
return;
}
const payload = {
text: `\u\U0001f6a8 GASエラー発生`,
attachments: [{
color: 'danger',
fields: [
{ title: '関数名', value: errorInfo.functionName, short: true },
{ title: '発生時刻', value: errorInfo.timestamp, short: true },
{ title: 'エラー内容', value: errorInfo.errorMessage, short: false },
{ title: 'スプレッドシート', value: errorInfo.spreadsheetUrl, short: false }
]
}]
};
UrlFetchApp.fetch(webhookUrl, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
});
}
実行時間を監視して安全に中断するユーティリティ
6分の実行時間制限に引っかからないよう、処理の途中で残り時間をチェックし、安全に中断するためのユーティリティです。
/**
* 実行時間監視クラス
* 使用例
* const timer = new ExecutionTimer(5.5); // 5分30秒で警告
* while (hasMoreData && !timer.shouldStop()) { ... }
*/
class ExecutionTimer {
constructor(maxMinutes = 5.5) {
this.startTime = new Date();
this.maxMilliseconds = maxMinutes * 60 * 1000;
}
getElapsedSeconds() {
return (new Date() - this.startTime) / 1000;
}
getRemainingSeconds() {
return (this.maxMilliseconds - (new Date() - this.startTime)) / 1000;
}
shouldStop() {
return (new Date() - this.startTime) >= this.maxMilliseconds;
}
logProgress(processedCount, totalCount) {
const elapsed = this.getElapsedSeconds().toFixed(1);
const remaining = this.getRemainingSeconds().toFixed(1);
const percent = ((processedCount / totalCount) * 100).toFixed(1);
console.log(`Progress: ${processedCount}/${totalCount} (${percent}%) | Elapsed: ${elapsed}s | Remaining: ${remaining}s`);
}
}
/**
* バッチ処理のサンプル実装
* 処理位置を保存し、次回実行時に続きから再開
*/
function batchProcessWithResume() {
const BATCH_SIZE = 100;
const timer = new ExecutionTimer(5);
const props = PropertiesService.getScriptProperties();
const lastProcessedRow = parseInt(props.getProperty('lastProcessedRow') || '1');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const lastRow = sheet.getLastRow();
let currentRow = lastProcessedRow;
while (currentRow <= lastRow && !timer.shouldStop()) {
const endRow = Math.min(currentRow + BATCH_SIZE - 1, lastRow);
const range = sheet.getRange(currentRow, 1, endRow - currentRow + 1, sheet.getLastColumn());
const data = range.getValues();
// ここで実際の処理を行う
data.forEach((row, index) => {
processRow(row, currentRow + index);
});
currentRow = endRow + 1;
props.setProperty('lastProcessedRow', currentRow.toString());
timer.logProgress(currentRow - 1, lastRow);
}
if (currentRow > lastRow) {
props.deleteProperty('lastProcessedRow');
console.log('All rows processed successfully!');
} else {
console.log(`Stopped at row ${currentRow}. Will resume on next execution.`);
}
}
function processRow(row, rowIndex) {
// 各行の処理をここに実装
Utilities.sleep(10); // 処理のシミュレーション
}
外部API呼び出しのリトライ処理
外部APIとの連携では、一時的なネットワークエラーやレート制限に遭遇することがあります。以下は指数バックオフを実装したリトライ関数です。
/**
* 指数バックオフ付きリトライ関数
* @param {Function} fn - 実行する関数
* @param {number} maxRetries - 最大リトライ回数
* @param {number} initialDelay - 初回待機時間(ミリ秒)
*/
function retryWithBackoff(fn, maxRetries = 3, initialDelay = 1000) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return fn();
} catch (error) {
lastError = error;
// リトライ不可能なエラーは即座に再スロー
if (isNonRetryableError(error)) {
throw error;
}
if (attempt < maxRetries) {
const delay = initialDelay * Math.pow(2, attempt);
console.log(`Attempt ${attempt + 1} failed. Retrying in ${delay}ms...`);
Utilities.sleep(delay);
}
}
}
throw new Error(`All ${maxRetries + 1} attempts failed. Last error: ${lastError.message}`);
}
function isNonRetryableError(error) {
const nonRetryablePatterns = [
'Invalid argument',
'Permission denied',
'Not found',
'Bad request'
];
return nonRetryablePatterns.some(pattern =>
error.message.toLowerCase().includes(pattern.toLowerCase())
);
}
// 使用例
function callExternalApi() {
const result = retryWithBackoff(() => {
const response = UrlFetchApp.fetch('https://api.example.com/data', {
muteHttpExceptions: true
});
const code = response.getResponseCode();
if (code === 429) {
throw new Error('Rate limited');
}
if (code >= 500) {
throw new Error(`Server error: ${code}`);
}
if (code !== 200) {
throw new Error(`Non-retryable error: ${code}`);
}
return JSON.parse(response.getContentText());
}, 3, 2000);
return result;
}
トリガーの棚卸しと健全性チェックの方法
組織でGASを長期運用していると、いつの間にかトリガーが増殖し、誰が何のために作ったのかわからないトリガーだらけになることがあります。年に一度はトリガーの棚卸しを行うことを強くおすすめします。
全トリガーを一覧出力するスクリプト
以下のスクリプトを実行すると、現在のプロジェクトに設定されているすべてのトリガー情報をスプレッドシートに出力できます。
/**
* プロジェクト内の全トリガーをスプレッドシートに出力
*/
function exportAllTriggers() {
const triggers = ScriptApp.getProjectTriggers();
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName('TriggerInventory');
if (sheet) {
sheet.clear();
} else {
sheet = ss.insertSheet('TriggerInventory');
}
// ヘッダー行
sheet.appendRow[
'トリガーID',
'関数名',
'イベントタイプ',
'実行頻度',
'ソースタイプ',
'次回実行予定',
'最終更新者'
]);
triggers.forEach(trigger => {
const handlerFunction = trigger.getHandlerFunction();
const eventType = trigger.getEventType();
const triggerSource = trigger.getTriggerSource();
let frequency = 'N/A';
if (eventType === ScriptApp.EventType.CLOCK) {
// 時間ベースのトリガーの場合
frequency = getTimeTriggerFrequency(trigger);
}
sheet.appendRow[
trigger.getUniqueId(),
handlerFunction,
eventType.toString(),
frequency,
triggerSource.toString(),
'', // 次回実行予定は直接取得できないため空欄
Session.getActiveUser().getEmail()
]);
});
// 書式設定
sheet.getRange(1, 1, 1, 7).setFontWeight('bold').setBackground('#4285f4').setFontColor('white');
sheet.autoResizeColumns(1, 7);
console.log(`Exported ${triggers.length} triggers to TriggerInventory sheet`);
}
function getTimeTriggerFrequency(trigger) {
// トリガーの種類を判定(完全な情報は取得できないため推定)
try {
const source = trigger.getTriggerSource();
if (source === ScriptApp.TriggerSource.CLOCK) {
return '時間ベース(詳細は設定画面で確認)';
}
} catch (e) {
return '不明';
}
return 'N/A';
}
/**
* 不要なトリガーを一括削除(関数名で指定)
* @param {string} functionName - 削除するトリガーの関数名
*/
function deleteTriggersByFunctionName(functionName) {
const triggers = ScriptApp.getProjectTriggers();
let deletedCount = 0;
triggers.forEach(trigger => {
if (trigger.getHandlerFunction() === functionName) {
ScriptApp.deleteTrigger(trigger);
deletedCount++;
}
});
console.log(`Deleted ${deletedCount} triggers for function: ${functionName}`);
}
スプレッドシートが重くなりすぎた時の対処法
GASを長期運用していると、ログやデータが蓄積されてスプレッドシートが巨大化し、スクリプトの実行が極端に遅くなることがあります。「昔は1分で終わっていた処理が、今は5分かかる」という状況です。
データ量を診断するスクリプト
まずは現状を把握することが重要です。以下のスクリプトでスプレッドシートの健全性をチェックできます。
/**
* スプレッドシートの健全性診断
*/
function diagnoseSpreadsheetHealth() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheets = ss.getSheets();
console.log('=== スプレッドシート健全性診断 ===');
console.log(`ファイルサイズ目安: ${getApproximateFileSize(ss)}`);
console.log(`シート数: ${sheets.length}`);
console.log('');
let totalCells = 0;
let totalFormulas = 0;
sheets.forEach(sheet => {
const name = sheet.getName();
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
const maxRows = sheet.getMaxRows();
const maxCols = sheet.getMaxColumns();
const usedCells = lastRow * lastCol;
const totalSheetCells = maxRows * maxCols;
const usagePercent = ((usedCells / totalSheetCells) * 100).toFixed(1);
// 数式の数をカウント(大きいシートでは時間がかかる)
let formulaCount = 0;
if (lastRow > 0 && lastCol > 0 && lastRow * lastCol < 100000) {
const formulas = sheet.getRange(1, 1, lastRow, lastCol).getFormulas();
formulaCount = formulas.flat().filter(f => f !== '').length;
}
totalCells += usedCells;
totalFormulas += formulaCount;
// 警告判定
let warnings = ;
if (lastRow > 50000) warnings.push('⚠️ 行数が多い');
if (maxRows - lastRow > 10000) warnings.push('\u\U0001f4a1 未使用行が多い');
if (formulaCount > 10000) warnings.push('⚠️ 数式が多い');
console.log(`【${name}】`);
console.log(` 使用範囲: ${lastRow}行 x ${lastCol}列 (${usedCells.toLocaleString()}セル)`);
console.log(` シートサイズ: ${maxRows}行 x ${maxCols}列`);
console.log(` 使用率: ${usagePercent}%`);
console.log(` 数式数: ${formulaCount.toLocaleString()}`);
if (warnings.length > 0) {
console.log(` 警告: ${warnings.join(', ')}`);
}
console.log('');
});
console.log('=== サマリー ===');
console.log(`総使用セル数: ${totalCells.toLocaleString()}`);
console.log(`総数式数: ${totalFormulas.toLocaleString()}`);
if (totalCells > 500000) {
console.log('\u\U0001f6a8 データ量が多いです。シートの分割を検討してください。');
}
if (totalFormulas > 50000) {
console.log('\u\U0001f6a8 数式が多いです。値として貼り付けることを検討してください。');
}
}
function getApproximateFileSize(ss) {
// 正確なファイルサイズは取得できないため、概算
const sheets = ss.getSheets();
let totalChars = 0;
sheets.forEach(sheet => {
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
if (lastRow > 0 && lastCol > 0 && lastRow * lastCol < 50000) {
const values = sheet.getRange(1, 1, lastRow, lastCol).getValues();
totalChars += JSON.stringify(values).length;
}
});
const sizeKB = totalChars / 1024;
if (sizeKB < 1024) {
return `約 ${sizeKB.toFixed(0)} KB`;
} else {
return `約 ${(sizeKB / 1024).toFixed(1)} MB`;
}
}
古いデータをアーカイブするスクリプト
診断の結果、データ量が多すぎる場合は、古いデータを別のスプレッドシートにアーカイブすることで改善できます。
/**
* 指定日より古いデータを別ファイルにアーカイブ
* @param {string} sheetName - 対象シート名
* @param {number} dateColumnIndex - 日付列のインデックス(1始まり)
* @param {number} daysToKeep - 保持する日数
*/
function archiveOldData(sheetName, dateColumnIndex, daysToKeep) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(sheetName);
if (!sheet) {
throw new Error(`Sheet "${sheetName}" not found`);
}
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const lastRow = sheet.getLastRow();
const lastCol = sheet.getLastColumn();
if (lastRow <= 1) {
console.log('No data to archive');
return;
}
const data = sheet.getRange(2, 1, lastRow - 1, lastCol).getValues();
const headers = sheet.getRange(1, 1, 1, lastCol).getValues();
const rowsToArchive = ;
const rowsToKeep = ;
data.forEach((row, index) => {
const dateValue = row;
if (dateValue instanceof Date && dateValue < cutoffDate) {
rowsToArchive.push(row);
} else {
rowsToKeep.push(row);
}
});
if (rowsToArchive.length === 0) {
console.log('No rows to archive');
return;
}
// アーカイブファイルを作成または開く
const archiveFileName = `${ss.getName()}_Archive_${Utilities.formatDate(new Date(), 'JST', 'yyyyMM')}`;
let archiveFile = getOrCreateArchiveFile(archiveFileName);
let archiveSheet = archiveFile.getSheetByName(sheetName);
if (!archiveSheet) {
archiveSheet = archiveFile.insertSheet(sheetName);
archiveSheet.appendRow(headers);
}
// アーカイブに追記
const archiveLastRow = archiveSheet.getLastRow();
archiveSheet.getRange(archiveLastRow + 1, 1, rowsToArchive.length, lastCol).setValues(rowsToArchive);
// 元シートを更新
sheet.getRange(2, 1, lastRow - 1, lastCol).clearContent();
if (rowsToKeep.length > 0) {
sheet.getRange(2, 1, rowsToKeep.length, lastCol).setValues(rowsToKeep);
}
console.log(`Archived ${rowsToArchive.length} rows. Kept ${rowsToKeep.length} rows.`);
console.log(`Archive file: ${archiveFile.getUrl()}`);
}
function getOrCreateArchiveFile(fileName) {
const files = DriveApp.getFilesByName(fileName);
if (files.hasNext()) {
return SpreadsheetApp.open(files.next());
}
return SpreadsheetApp.create(fileName);
}
複数人が同時編集するときのスクリプト競合を防ぐ
共有スプレッドシートでonEditトリガーを使っていると、複数人が同時に編集した際にスクリプトが競合し、予期しない動作を引き起こすことがあります。
ロック機構を使った排他制御
GASにはLockServiceというロック機構が用意されています。これを使って、同時実行を防ぐことができます。
/**
* 排他制御付きの編集ハンドラ
*/
function handleEditWithLock(e) {
const lock = LockService.getScriptLock();
try {
// 最大30秒待ってロック取得を試みる
const hasLock = lock.tryLock(30000);
if (!hasLock) {
console.log('Could not obtain lock. Another process is running.');
return;
}
// ここに実際の処理を記述
processEdit(e);
} catch (error) {
console.error('Error in handleEditWithLock:', error);
throw error;
} finally {
// 必ずロックを解放
lock.releaseLock();
}
}
function processEdit(e) {
const sheet = e.source.getActiveSheet();
const range = e.range;
// 編集内容に応じた処理
console.log(`Edit detected: ${sheet.getName()} - ${range.getA1Notation()}`);
}
ライブラリ依存で発生する厄介な問題
GASでは外部ライブラリを追加して機能を拡張できますが、これが思わぬトラブルの原因になることがあります。
ライブラリのバージョン固定の重要性
ライブラリを追加する際、「HEAD(開発モード)」と「バージョン番号」のどちらかを選択できます。HEADを選ぶと常に最新版が読み込まれますが、ライブラリ作者の更新で突然スクリプトが動かなくなるリスクがあります。本番環境では必ずバージョン番号を固定してください。
ライブラリがアクセス不能になった場合の対処
まれに、ライブラリの作者がプロジェクトを削除したり、共有設定を変更したりして、ライブラリにアクセスできなくなることがあります。この場合、スクリプト全体が動かなくなります。
予防策として、重要なライブラリはソースコードをコピーして自前でホストすることをおすすめします。appsscript.jsonを編集してライブラリへの依存を削除し、ライブラリのコードを直接プロジェクトにコピーします。ライセンスに注意が必要ですが、業務で使用する重要なスクリプトでは検討すべき選択肢です。
GASプロジェクトのバックアップ戦略
スプレッドシートはGoogleドライブにあるため自動的にバックアップされますが、GASのコード自体はバージョン管理されていません。うっかりコードを消してしまったり、動いていたバージョンに戻したいときに困ることがあります。
定期的なコードバックアップスクリプト
/**
* GASプロジェクトのコードをGoogleドライブにバックアップ
* 週次トリガーで実行することを推奨
*/
function backupGasCode() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const projectName = ss.getName();
// バックアップ用フォルダを取得または作成
const backupFolderName = 'GAS_Backups';
let backupFolder = getOrCreateFolder(backupFolderName);
// プロジェクト固有のサブフォルダ
let projectFolder = getOrCreateSubFolder(backupFolder, projectName);
// 現在のコードを取得(boundスクリプトの場合)
const scriptId = ScriptApp.getScriptId();
const timestamp = Utilities.formatDate(new Date(), 'JST', 'yyyyMMdd_HHmmss');
// 注意: 実際のソースコード取得にはApps Script APIの有効化が必要
// ここでは簡易的にスクリプトIDと更新日時を記録
const backupInfo = {
scriptId: scriptId,
projectName: projectName,
backupDate: timestamp,
spreadsheetUrl: ss.getUrl(),
note: 'Full code backup requires Apps Script API'
};
const fileName = `backup_${timestamp}.json`;
projectFolder.createFile(fileName, JSON.stringify(backupInfo, null, 2), MimeType.PLAIN_TEXT);
console.log(`Backup created: ${fileName}`);
// 古いバックアップを削除(30日以上前のもの)
cleanupOldBackups(projectFolder, 30);
}
function getOrCreateFolder(folderName) {
const folders = DriveApp.getFoldersByName(folderName);
if (folders.hasNext()) {
return folders.next();
}
return DriveApp.createFolder(folderName);
}
function getOrCreateSubFolder(parentFolder, folderName) {
const folders = parentFolder.getFoldersByName(folderName);
if (folders.hasNext()) {
return folders.next();
}
return parentFolder.createFolder(folderName);
}
function cleanupOldBackups(folder, daysToKeep) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const files = folder.getFiles();
let deletedCount = 0;
while (files.hasNext()) {
const file = files.next();
if (file.getDateCreated() < cutoffDate) {
file.setTrashed(true);
deletedCount++;
}
}
if (deletedCount > 0) {
console.log(`Cleaned up ${deletedCount} old backup files`);
}
}
ぶっちゃけこうした方がいい!
ここまで様々なトラブルシューティングの方法や実践的なコードを紹介してきましたが、正直なところ、GASでトラブルに遭わないための最良の方法は「トラブルが起きにくい設計」を最初からすることです。
10年以上情シスをやってきて断言できるのは、後から直すより最初からちゃんと作る方が100倍楽だということ。「とりあえず動いたからOK」で本番投入すると、半年後に必ず痛い目を見ます。
具体的にどうすればいいかというと、まずtry-catchとログ出力は最初から必ず入れること。「テスト環境では動いたから」と言ってエラーハンドリングをサボると、本番で問題が起きたとき原因特定に何時間もかかります。ログがあれば5分で終わることが、ログがないと丸一日潰れることもザラです。
次に、トリガーは「誰が」「いつ」「何のために」作ったかを必ず記録してください。Googleスプレッドシートでもなんでもいいので、トリガー管理台帳を作ること。退職者が作ったトリガーが暴走して、金曜の夜に呼び出されたことが何度あったか。あの時、台帳があれば即座に対応できたのにと何度後悔したことか。
そして意外と見落とされがちなのが、GASのコードも「資産」として管理するという意識です。スプレッドシートは共有フォルダに整理するくせに、紐づいているGASのコードは誰も把握していない。これ、かなり危険な状態です。重要なスクリプトはGitHubにpushするか、最低でも定期的にコードをテキストファイルでエクスポートしておきましょう。
最後に、これが一番大事なんですが、「動いているものは触るな」という考えを捨てること。V8移行のように、Googleは定期的に大きな変更を加えます。「うちのスクリプトは3年前から動いてるから大丈夫」なんて思っていると、ある日突然すべてが止まります。年に一度は全スクリプトの棚卸しをして、古い書き方がないか、非推奨のAPIを使っていないかチェックする習慣をつけてください。
ぶっちゃけ、GASは便利ですが「作りっぱなし」が許される環境ではありません。継続的なメンテナンスコストを最初から計算に入れておくこと。これができていれば、この記事で紹介したトラブルの大半は未然に防げます。そして万が一トラブルが起きても、ログと記録があれば冷静に対処できる。それが、長年GASと向き合ってきた私の結論です。
スプレッドシートのスクリプトが動かないに関する疑問解決
コピペしたコードなのに動かないのはなぜですか?
ネットで見つけたコードをコピペしても動かない場合、いくつかの原因が考えられます。まず、ランタイムの違いです。古い記事のコードはRhinoランタイム用に書かれており、V8では動作しないことがあります。特にfor each...in文やgetYear()を使用しているコードは修正が必要です。次に、シート名やカラム名の違いです。コードが特定のシート名やカラム名をハードコーディングしている場合、ご自身の環境に合わせて修正する必要があります。また、権限の未承認も原因となります。初めてスクリプトを実行する際は権限承認が必要であり、トリガー経由での初回実行では権限エラーになります。
「Exceeded maximum execution time」と表示されたらどうすればよいですか?
このエラーは、スクリプトの実行時間が6分の上限を超過したことを示しています。解決策としては、処理をバッチに分割する、ループ内でのAPI呼び出しを減らす、配列を活用して一括処理するなどの方法があります。特にスプレッドシートへの読み書きは時間がかかるため、getValue()やsetValue()の呼び出し回数を最小限に抑えることが重要です。
トリガーからはエラーメールが来るが原因がわからない場合はどうしますか?
トリガーで実行されるスクリプトがエラーを起こすと、「Summary of failures for Google Apps Script」という件名のメールが届きます。メールにはエラーの概要とスクリプトへのリンクが含まれています。原因を特定するには、スクリプトエディタの「実行数」から該当するエラーを探し、詳細を確認してください。それでもわからない場合は、スクリプト内にtry-catch文を追加してエラー内容をメールやスプレッドシートに出力するようにすると、より詳細な情報を得られます。
共有されたスプレッドシートのスクリプトを編集できないのですが?
共有されたスプレッドシートに紐づくGASスクリプト(コンテナバインドスクリプト)を編集するには、スプレッドシートの編集権限だけでなく、Chromeブラウザのログインアカウントがスプレッドシートのログインアカウントと一致している必要があります。複数アカウントでログインしている場合は、一度すべてログアウトしてから、権限を持つアカウントでログインし直してください。
今すぐパソコンやスマホの悩みを解決したい!どうしたらいい?
いま、あなたを悩ませているITの問題を解決します!
「エラーメッセージ、フリーズ、接続不良...もうイライラしない!」
あなたはこんな経験はありませんか?
✅ ExcelやWordの使い方がわからない💦
✅ 仕事の締め切り直前にパソコンがフリーズ💦
✅ 家族との大切な写真が突然見られなくなった💦
✅ オンライン会議に参加できずに焦った💦
✅ スマホの重くて重要な連絡ができなかった💦
平均的な人は、こうしたパソコンやスマホ関連の問題で年間73時間(約9日分の働く時間!)を無駄にしています。あなたの大切な時間が今この悩んでいる瞬間も失われています。
LINEでメッセージを送れば即時解決!
すでに多くの方が私の公式LINEからお悩みを解決しています。
最新のAIを使った自動応答機能を活用していますので、24時間いつでも即返信いたします。
誰でも無料で使えますので、安心して使えます。
問題は先のばしにするほど深刻化します。
小さなエラーがデータ消失や重大なシステム障害につながることも。解決できずに大切な機会を逃すリスクは、あなたが思う以上に高いのです。
あなたが今困っていて、すぐにでも解決したいのであれば下のボタンをクリックして、LINEからあなたのお困りごとを送って下さい。
ぜひ、あなたの悩みを私に解決させてください。
まとめ
スプレッドシートのスクリプトが動かない原因は実に多岐にわたりますが、この記事で解説した内容を順番にチェックしていけば、ほとんどの問題は解決できるはずです。
特に重要なのは、2026年1月31日のRhinoランタイム廃止への対応です。まだV8に移行していないプロジェクトがあれば、今すぐ対応を始めてください。移行自体は難しくありませんが、コードの互換性確認とテストには時間がかかることがあります。
GASは確かに手軽に始められるプログラミング環境ですが、本格的に使いこなすにはある程度の知識と経験が必要です。エラーに遭遇しても諦めず、この記事を参考にしながら一つずつ問題を解決していってください。そして、解決できた経験は必ず次のトラブルシューティングに活きてきます。
もし解決できない問題に遭遇した場合は、公式ドキュメントの確認、Stack OverflowやRedditのApps Scriptコミュニティでの質問、またはGoogleのIssue Trackerでのバグ報告も検討してみてください。GASコミュニティは活発で、多くの開発者が助け合っています。





コメント