0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

0
Posted at

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

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

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

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?