持続可能なGASトリガー管理のベストプラクティス

に公開

こんにちは、luthです。

GAS(Google Apps Script)は、生成AIでもパっとコードを出力してくれ、手軽に業務効率化ができる素晴らしいツールです。
しかし、その手軽さゆえに、作ったツールが「個人の持ち物」のまま運用され、作成者の異動や退職とともに「負の遺産」になってしまうことが多々あります。

特に、「トリガー(自動実行)」の管理は、チーム開発において最もボトルネックになりやすいポイントです。
個人ツールや、一瞬しか利用しない短期ツールであれば問題になりませんが、業務やチームで利用するのであれば、数ヶ月・数年の単位で稼動する必要があります。

今回は、未来のチームメンバーが安心して引き継げる、「持続可能なトリガー管理」の設計パターンをご紹介します。

🐣 なぜ「トリガー」の管理設計が重要なのか

GASにおけるトリガー(時間主導型やイベント主導型の自動実行設定)には、チーム運用を考える上で避けて通れない仕様があります。

1. トリガーは「作成者のアカウント」と紐付いている

トリガーは、設定したユーザーのアカウントに紐付きます。
もし、設定者が退職してアカウントが削除されると、そのトリガーは「無効」状態となり、ある日突然、人知れず停止します。
「あれ?今朝のレポートが来ていない?」「なんかフォーム投稿されても、Slackに通知が来ない」と気づいた時には、重要な業務プロセスが既に止まってしまっています。

2. 「見えない・触れない」アクセス権の壁

逆に、アカウントは残っているものの、異動などで「ファイルの編集権限」だけが外れた場合、トリガーは動き続けますが、スクリプトがエラーを吐き続ける状態(いわゆるゾンビトリガー)になります。

さらに厄介なのが、**「他人のトリガーは、管理画面から削除・編集ができない」**という点です。
管理者は「誰かのトリガーが動いている」ことは分かっても、「具体的に誰のアカウントか」を特定したり、それを停止させたりすることができません。

3. 「二重稼働」のリスク

エラーを止める手段がないため、後任者がやむを得ず「自分のアカウントで新しいトリガー」を設定すると、「エラーを出し続ける前任者のトリガー」と「正常な後任者のトリガー」が重複して動くことになります。


💡 解決策:プログラムによる「セルフクリーニング」機能

管理画面から制御できないなら、コードの中に「自律的に停止する機能」を組み込んでおくのが、スマートな解決策です。

具体的には、ScriptProperties(スクリプトプロパティ) を「フラグ管理ストア」として活用します。
外部からフラグを立てることで、古いトリガーが自らその役目を終える(削除される)仕組みを作ります。

この設計を入れておくことで、前任者がいなくなってもスムーズに「後任者による運用設定の上書き」ができるようになります。

運用のイメージ図


🛠️ 実装パターン:チームのためのコード

このロジックは非常にシンプルです。
既存のスクリプトの冒頭に、以下のチェック処理を追加するだけです。

1. セルフクリーニング機能付きのメイン処理

関数の実行時に「停止命令が出ていないか」を確認し、出ている場合はそのまま自身のトリガーを削除します。

/**
 * トリガー設定対象の関数
 */
function mainTask() {
  // 1. 共有プロパティからステータスを確認
  const props = PropertiesService.getScriptProperties();
  const resetFlag = props.getProperty('TRIGGER_RESET_FLAG');

  // 2. リセットフラグが立っていた場合、クリーンアップを実行
  if (resetFlag === 'true') {
    console.warn('リセット命令を検知しました。旧トリガーを削除し、運用を終了します。');
    cleanUpTriggers('mainTask'); // 自身の関数名を指定
    clearResetFlag(); // 後述のフラグ削除の関数。再設定可能な状態に移行
    return; // 業務処理は行わずに終了
  }

  // === ここから通常の業務処理 ===
  console.log('通常処理を開始します。');
  // ... 
}

/**
 * 指定した関数のトリガーを削除するヘルパー関数。トリガーの起動条件に関わらず全て削除する。
 * @param {string} functionName 削除したいトリガーが対象とする関数名
 */
function cleanUpTriggers(functionName) {
  // getProjectTriggers() メソッドは
  // 「実行者アカウントの設定しているトリガー」しか取得できないため
  // 他ユーザー(後任者など)のトリガーは安全
  const triggers = ScriptApp.getProjectTriggers();
  triggers.forEach(trigger => {
    // 自身の関数をハンドリングしているトリガーのみを対象にする
    if (trigger.getHandlerFunction() === functionName) {
      ScriptApp.deleteTrigger(trigger);
    }
  });
  console.log(`関数 ${functionName} に紐づくトリガーの削除が完了しました。`);
}

2. リセット実行用関数(運用交代時のスイッチ)

引き継ぎを行う際、後任者はrequestTriggerReset()関数を一度だけ実行します。これにより、次回起動時に前任者のトリガーが自動的に削除されます。

/**
 * トリガーのリセットフラグを立てる関数
 * メニューやボタンなどに登録しておくと便利
 */
function requestTriggerReset() {
  PropertiesService.getScriptProperties().setProperty('TRIGGER_RESET_FLAG', 'true');
  console.log('トリガーのリセットフラグを設定しました。次回実行時に古いトリガーは自動削除されます。');
}

/**
 * トリガーのリセットフラグを削除する関数
 */
function clearResetFlag() {
  PropertiesService.getScriptProperties().deleteProperty('TRIGGER_RESET_FLAG');
  console.log('リセットフラグを解除しました。新しい運用を開始できます。');
}

🚀 まとめ:コードに「思いやり」を実装しよう

GASでの開発は、動くものを作って終わりではありません。
**「そのツールが、自分がいなくなった後も健やかに動き続けられるか」**を考えることが、エンジニアとしての品質を高めます。

  • アカウントに依存しない管理の仕組みを作る
  • 「終わり方」をプログラムしておく

このひと手間を加えるだけで、あなたの作ったツールは「誰かの置き土産」ではなく、長く愛される「チームの資産」へと進化します。
ぜひ、次の開発からこのパターンを取り入れてみてください。

Discussion