Skip to content

よくあるアンチパターン

GASでよくあるアンチパターンと、実際に事故った構造を詳しく解説します。

A. 実行時間制限を無視した処理

Section titled “A. 実行時間制限を無視した処理”
// ❌ アンチパターン: 実行時間制限を考慮していない
function processLargeDataset() {
const data = fetchLargeDataset(); // 10,000件のデータ
data.forEach(item => {
processItem(item); // 各アイテムの処理に1秒かかる
// 合計: 10,000秒 = 約2.8時間 → タイムアウト
});
}
function fetchLargeDataset() {
const response = UrlFetchApp.fetch('https://api.example.com/data');
return JSON.parse(response.getContentText()); // 10,000件のデータ
}

なぜ事故るか:

  1. 実行時間制限: GASの実行時間制限(6分または30分)を超えると強制終了される
  2. 中途半端な状態: 処理が途中で終了し、データが中途半端な状態になる
  3. 再実行の困難: どこまで処理したか分からず、再実行が困難

実際の事故例:

10:00:00 - スクリプト開始(実行時間: 0秒)
10:00:01 - データ取得開始(10,000件のデータ)
10:05:30 - データ処理中(実行時間: 5分30秒、3,000件処理済み)
10:06:00 - 実行時間制限に達する(6分)
10:06:01 - スクリプトが強制終了
10:06:02 - エラー: "Maximum execution time exceeded"
10:06:03 - 3,000件だけ処理され、残り7,000件は未処理

設計レビューでの指摘文例:

【指摘】実行時間制限を考慮していません。
【問題】大量のデータを一度に処理すると、実行時間制限(6分)を超えて強制終了されます。
【影響】処理が中途半端な状態で終了、データの不整合、再実行の困難
【推奨】バッチ処理に分割し、実行時間をチェックして5分で停止する

B. クォータ制限を無視した処理

Section titled “B. クォータ制限を無視した処理”
// ❌ アンチパターン: クォータ制限を考慮していない
function sendBulkEmails(recipients) {
recipients.forEach(recipient => {
MailApp.sendEmail({
to: recipient.email,
subject: recipient.subject,
body: recipient.body,
});
// 問題: 100通を超えるとエラー
// 問題: エラーハンドリングがない
});
}
function fetchManyAPIs(urls) {
urls.forEach(url => {
UrlFetchApp.fetch(url); // 問題: 20,000回/日の制限を超える可能性
});
}

なぜ事故るか:

  1. 1日の実行時間制限: 6時間(無料プラン)を超えるとエラー
  2. 同時実行数制限: 30(無料プラン)を超えるとエラー
  3. URLフェッチ制限: 20,000回/日を超えるとエラー
  4. メール送信制限: 100通/日(無料プラン)を超えるとエラー

実際の事故例:

10:00:00 - メール送信開始(200通のメール)
10:00:01 - 1通目送信成功
10:00:02 - 2通目送信成功
...
10:00:50 - 100通目送信成功
10:00:51 - 101通目送信失敗: "Daily email limit exceeded"
10:00:52 - エラーが発生し、残り100通は送信されない

設計レビューでの指摘文例:

【指摘】クォータ制限を考慮していません。
【問題】1日の送信制限(100通)を超えるとエラーが発生します。
【影響】一部のメールが送信されない、エラー処理が不十分
【推奨】クォータをチェックし、制限を超える場合はキューに保存して次回実行する
// ❌ アンチパターン: エラーハンドリングがない
function updateSpreadsheet() {
const response = UrlFetchApp.fetch('https://api.example.com/data');
const data = JSON.parse(response.getContentText());
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.getRange('A1').setValue(data.value);
// 問題: エラーが発生すると、スクリプト全体が停止
// 問題: エラーの原因が分からない
}

なぜ事故るか:

  1. エラーの伝播: エラーが発生すると、スクリプト全体が停止する
  2. エラーの原因不明: エラーメッセージが不十分で、原因が分からない
  3. ロールバックの困難: エラー発生時、既に処理されたデータのロールバックが困難

実際の事故例:

10:00:00 - スクリプト開始
10:00:01 - データ取得開始
10:00:02 - API呼び出し失敗: "Connection timeout"
10:00:03 - エラーが発生し、スクリプトが停止
10:00:04 - エラーログが残らない
10:00:05 - 管理者がエラーに気づかない

設計レビューでの指摘文例:

【指摘】エラーハンドリングが不十分です。
【問題】エラーが発生すると、スクリプト全体が停止し、エラーの原因が分かりません。
【影響】処理の中断、エラーの原因不明、再実行の困難
【推奨】try-catchでエラーを捕捉し、ログに記録して通知を送信する

D. トランザクション境界の不備

Section titled “D. トランザクション境界の不備”
// ❌ アンチパターン: トランザクション境界がない
function updateSpreadsheet() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// 問題: 各更新が個別のトランザクション
sheet.getRange('A1').setValue('Value 1');
sheet.getRange('A2').setValue('Value 2');
sheet.getRange('A3').setValue('Value 3'); // ここでエラー
// A1とA2だけ更新され、A3は更新されない(データ不整合)
}

なぜ事故るか:

  1. 部分的な更新: エラーが発生すると、一部だけ更新される
  2. データ不整合: データの整合性が保たれない
  3. ロールバックの困難: 既に更新されたデータのロールバックが困難

実際の事故例:

10:00:00 - スクリプト開始
10:00:01 - A1を更新: 成功
10:00:02 - A2を更新: 成功
10:00:03 - A3を更新: エラー(権限エラー)
10:00:04 - スクリプトが停止
10:00:05 - A1とA2は更新されているが、A3は更新されていない(データ不整合)

設計レビューでの指摘文例:

【指摘】トランザクション境界が不適切です。
【問題】各更新が個別のトランザクションのため、エラー時に部分的な更新が発生します。
【影響】データの不整合、ロールバックの困難
【推奨】範囲単位で一度に更新し、アトミックに処理する

E. 外部API呼び出しのタイムアウト設定なし

Section titled “E. 外部API呼び出しのタイムアウト設定なし”
// ❌ アンチパターン: タイムアウト設定がない
function fetchData() {
const response = UrlFetchApp.fetch('https://api.example.com/data');
// 問題: タイムアウト設定がない(デフォルト60秒)
// 問題: エラーハンドリングがない
return JSON.parse(response.getContentText());
}

なぜ事故るか:

  1. 長時間の待機: 外部APIの応答が遅い場合、60秒まで待機する
  2. 実行時間の浪費: 実行時間制限(6分)のうち、60秒を浪費する
  3. エラーの伝播: エラーが発生すると、スクリプト全体が停止する

実際の事故例:

10:00:00 - スクリプト開始(実行時間: 0秒)
10:00:01 - 外部API呼び出し開始
10:00:30 - 外部APIが応答しない(30秒経過)
10:01:00 - タイムアウト(60秒経過)
10:01:01 - エラーが発生し、スクリプトが停止
10:01:02 - 実行時間の10%を浪費

設計レビューでの指摘文例:

【指摘】外部API呼び出しにタイムアウト設定がありません。
【問題】外部APIの応答が遅い場合、60秒まで待機し、実行時間を浪費します。
【影響】実行時間の浪費、エラーの伝播、パフォーマンスの低下
【推奨】タイムアウトを30秒に設定し、エラーハンドリングとフォールバックを実装する
// ❌ アンチパターン: 状態を保持しない
function processLargeDataset() {
const data = fetchLargeDataset(); // 10,000件のデータ
let processed = 0;
data.forEach(item => {
processItem(item);
processed++;
// 問題: 実行時間制限で停止した場合、どこまで処理したか分からない
});
}

なぜ事故るか:

  1. 再実行の困難: 実行時間制限で停止した場合、どこまで処理したか分からない
  2. 重複処理: 再実行時に、既に処理したデータを再度処理する可能性がある
  3. データ不整合: 重複処理により、データが不整合になる

実際の事故例:

10:00:00 - スクリプト開始
10:00:01 - データ処理開始(10,000件)
10:05:30 - 3,000件処理済み(実行時間: 5分30秒)
10:06:00 - 実行時間制限で停止
10:06:01 - 次回実行時に、最初から処理を開始
10:06:02 - 既に処理した3,000件を再度処理(重複処理)

設計レビューでの指摘文例:

【指摘】状態管理が不適切です。
【問題】実行時間制限で停止した場合、どこまで処理したか分からず、再実行が困難です。
【影響】重複処理、データ不整合、再実行の困難
【推奨】PropertiesServiceに処理位置を保存し、次回実行時に続きから処理する

GASでよくあるアンチパターンは、実行時間制限、クォータ制限、エラーハンドリング、トランザクション境界、状態管理の不備が主な原因です。

重要なポイント:

  • 実行時間制限: 6分または30分の制限を考慮する
  • クォータ制限: 1日の制限をチェックする
  • エラーハンドリング: try-catchでエラーを捕捉し、ログに記録する
  • トランザクション境界: 範囲単位でアトミックに処理する
  • 状態管理: PropertiesServiceに状態を保存する

これらのアンチパターンを避けることで、堅牢で効率的なGASアプリケーションを構築できます。