【GAS入門】突然のエラーも怖くない!プログラムを安定稼働させるエラーハンドリングの基本

基本解説

「Google Apps Script(GAS)を使って、日々の面倒な集計作業を自動化できた!」
そんな喜びも束の間、ある日突然プログラムがエラーで止まってしまい、原因もわからず困ってしまった…という経験はありませんか?

手作業より速くて正確なはずの自動化プログラムも、予期せぬエラーで停止してしまっては本末転倒です。

しかし、ご安心ください。プログラムの世界には、こうした不測の事態に備えるための「エラーハンドリング(エラー処理)」という重要な仕組みがあります。

この記事では、GASを使い始めたばかりの方や、これまでエラーメッセージを見て見ぬふりをしてきた方に向けて、エラーハンドリングの基本的な考え方と具体的な実装方法を、実用的なサンプルを交えながら解説します。

この記事を読み終える頃には、エラーを恐れるのではなく、エラーと上手く付き合い、より安定的で信頼性の高い自動化プログラムを作成できるようになっているはずです。

閲覧の前に

GASの基本的な使い方やスクリプトエディタの開き方については、別の記事で詳しく解説しています。もしGAS自体が初めてという方は、まずはこちらの記事からご覧いただくことをお勧めします。

なぜエラーハンドリングが必要なのか?

プログラムにおけるエラーは、いわば「想定外の出来事」です。例えば、

  • 数値を入力してほしいセルに、誤って文字列が入力されていた
  • 処理対象のファイル名が、いつの間にか変更されていた
  • 参照しようとしたデータが存在しなかった

など、プログラムそのものが正しくても、扱うデータや環境の変化によってエラーは発生します。

エラーハンドリングを実装していないと、プログラムはエラーが発生した時点で処理を完全に停止してしまいます。これでは、一部のデータに問題があっただけで、他の正常なデータまで処理できなくなってしまい、非常に効率が悪いです。

そこでエラーハンドリングの出番です。エラーハンドリングを正しく実装することで、

  • エラーが発生してもプログラムを止めずに、処理を最後まで続行させる
  • どこで・どのようなエラーが起きたのかを記録し、原因究明を容易にする
  • エラー発生時に、特定の代替処理を実行させる

といった対応が可能になり、プログラムの安定性と信頼性が格段に向上します。

GASエラーハンドリングの基本構文「try-catch」

まずは、エラーハンドリングの最も基本的な構文である「try-catch文」を覚えましょう。これは、エラーが「起きそうな処理」と「起きたときの処理」を分けて書くための仕組みです。

function sampleFunction() {
  try {
    // エラーが発生する可能性のある処理をここに書く
    Logger.log('処理を開始します');
    const sheet = null;
    const sheetName = sheet.getName(); // 存在しないもの(null)から名前を取得しようとしてエラー
    Logger.log('この行は実行されません');

  } catch (e) {
    // tryの中でエラーが発生した場合に、この中の処理が実行される
    Logger.log('エラーが発生しました!');
    Logger.log('エラー内容: ' + e.message); // e.message にはエラーメッセージが格納されている
  }
  
  Logger.log('処理を終了します');
}

上記のコードを実行すると、try { … } の中でエラーが発生した時点で、処理はすぐに catch (e) { … } の中にジャンプします。catchブロックの処理が終わると、その後の処理(Logger.log(‘処理を終了します’);)が実行されます。

tryブロック

エラーが発生するかもしれない、監視したい処理を囲む部分です。

catchブロック

tryブロック内でエラーが発生した場合にだけ実行される部分です。catch(e)の e は「エラーオブジェクト」と呼ばれる、エラーに関する情報がたくさん詰まった箱のようなものです。このeから e.message(エラーメッセージ)といった情報を取り出して、エラーの原因特定に役立てることができます。

補足:エラーの有無に関わらず必ず実行したい処理「finally」

try-catch文には、finallyというブロックを追加できます。ここに書いた処理は、tryブロックでエラーが起きても起きなくても、最後に必ず実行されます。

function sampleFunctionWithFinally() {
  try {
    Logger.log('tryブロック:処理を開始します');
    return; // 関数はここで終了
    
  } catch (e) {
    Logger.log('catchブロック:エラーが発生しました');

  } finally {
    // このブロックは、tryの途中でreturnされても必ず実行される
    Logger.log('finallyブロック:この処理は必ず実行されます');
  }
  
  // tryブロックでreturnされると、この行は実行されない
  Logger.log('この行は実行されません'); 
}

tryブロックの中にreturn(関数を終了する命令)があっても、finallyブロック内の処理は、関数が終了する直前に実行されます。このように、処理の成否に関わらず、絶対に実行したい「後始末」のような処理(例:処理完了の通知、作成した一時ファイルの削除など)を書くのに適しています。

finallyブロックは絶対ではない?

finallyは非常に強力ですが、万能ではありません。GASの実行時間上限(6分)によるタイムアウトや、Googleサーバー側での予期せぬ問題など、スクリプトのプロセス自体が強制的に中断された場合には、finallyブロックが実行されない可能性もあります。

【実践】契約データの集計でエラーハンドリングを使ってみよう

それでは、より実践的な例として、「契約データの集計作業」を題材にエラーハンドリングを実装してみましょう。

Step0: 事前準備

毎月、従業員の契約日リストから契約した年だけを抜き出して、別シートに記録するという自動化スクリプトを考えます。

【準備するスプレッドシート】

  1. 契約データシート: B列の契約日が手入力のため、日付以外(例:「未定」)が入力される可能性がある。
  2. 契約年ログシート: スクリプトが契約年を書き出すシート。
  3. エラーログシート: エラーが発生した際に、その内容を記録するためのシート。

Step1: エラーハンドリング「なし」の場合 (エラーで処理が停止)

まず、エラーハンドリングを実装しない場合のコードを見てみましょう。

function extractContractYear_v1() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dataSheet = ss.getSheetByName('契約データ');
  const logSheet = ss.getSheetByName('契約年ログ');

  // データを取得 (ヘッダーを除く)
  const data = dataSheet.getRange(2, 1, dataSheet.getLastRow() - 1, 2).getValues();

  for (let i = 0; i < data.length; i++) {
    const employeeId = data[i][0];
    const contractDate = data[i][1];

    // 年を取得
    const year = contractDate.getFullYear(); // エラー発生箇所

    logSheet.appendRow([new Date(), employeeId, year]);
  }
}

このコードを実行すると、3行目(従業員ID: 1003)のデータで処理が止まってしまいます。なぜなら、セルの値「未定」は文字列であり、日付オブジェクトが持つ.getFullYear()という命令(メソッド)を持っていないためです。

結果として、「TypeError: contractDate.getFullYear is not a function」というエラーメッセージが表示され、ID:1004以降の正しいデータも処理されずに終わってしまいます。

Step2: try-catchでエラーを回避する

この問題を解決するために、ループ処理の中の、エラーが発生しうる部分をtry-catchで囲みます。

function extractContractYear_v2() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dataSheet = ss.getSheetByName('契約データ');
  const logSheet = ss.getSheetByName('契約年ログ');

  const data = dataSheet.getRange(2, 1, dataSheet.getLastRow() - 1, 2).getValues();

  for (let i = 0; i < data.length; i++) {
    try {
      const employeeId = data[i][0];
      const contractDateValue = data[i][1];

      // スプレッドシートからの値でDateオブジェクトを生成してみる
      const contractDate = new Date(contractDateValue);

      // 生成したDateオブジェクトが有効かチェック
      if (isNaN(contractDate.getTime())) {
        // 無効な日付(Invalid Date)の場合は、意図的にエラーを発生させる
        throw new Error('契約日が日付形式ではありません。');
      }

      const year = contractDate.getFullYear();
      logSheet.appendRow([new Date(), employeeId, year]);
    
    } catch (e) {
      // エラーが発生したら、その行の処理はスキップして次の行へ進む
      // ログにエラー内容を出力しておく
      Logger.log((i + 2) + '行目でエラーが発生しました: ' + e.message);
    }
  }
}

このコードを実行すると、ID:1003の行でエラーが発生しますが、catchブロックの処理が実行された後、ループは止まらずに次のID:1004の処理へと進みます。結果として、処理できるデータはすべて「契約年ログ」シートに記録され、プログラムは最後まで完走します。

Step3: エラー内容をスプレッドシートに記録する

実行ログを確認すればエラーが起きたことは分かりますが、これではスクリプトの実行のたびに毎回ログを確認しなくてはなりません。

そこで、catchブロックの処理を改良して、エラー内容を「エラーログ」シートに自動で書き出すようにしましょう。

function extractContractYear_final() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const dataSheet = ss.getSheetByName('契約データ');
  const logSheet = ss.getSheetByName('契約年ログ');
  const errorSheet = ss.getSheetByName('エラーログ'); // エラーログシートを取得

  const data = dataSheet.getRange(2, 1, dataSheet.getLastRow() - 1, 2).getValues();

  for (let i = 0; i < data.length; i++) {
    // データのある行番号を取得(ヘッダー分+1と配列のインデックス+1)
    const rowNum = i + 2; 

    try {
      const employeeId = data[i][0];
      const contractDateValue = data[i][1];
      
      const contractDate = new Date(contractDateValue);

      if (isNaN(contractDate.getTime())) {
        throw new Error('契約日が日付形式ではありません。');
      }

      const year = contractDate.getFullYear();
      logSheet.appendRow([new Date(), employeeId, year]);

    } catch (e) {
      // エラー内容をエラーログシートに記録
      const errorData = data[i]; // エラーが発生した行のデータ
      errorSheet.appendRow([new Date(), '契約データ', rowNum, e.name, e.message, JSON.stringify(errorData), e.stack]);
    }
  }
}

このコードでは、catch (e) ブロックの処理をLogger.logからerrorSheet.appendRow(…)に変更し、エラーの詳細をスプレッドシートに記録するようにしています。

記録している各項目はエラーの原因究明に非常に役立ちます。

  • e.name: エラーの種類(例: ‘Error’, ‘TypeError’)。どんな種類の間違いが起きたかがわかります。
  • e.message: 具体的なエラー内容(例: ‘契約日が日付形式ではありません。’)。エラーの直接的な原因を示します。
  • e.stack: エラー発生箇所までの関数の呼び出し履歴。コードのどこで問題が起きたかを正確に特定するための最も重要な情報です。
  • JSON.stringify(errorData): 配列形式の元データを文字列に変換して記録しています。これにより、どんなデータが原因でエラーになったのかを一目で確認できます。

ここまで実装できれば、エラーをただ無視するのではなく、後から原因を分析して元データを修正する、といった改善アクションに繋げることができます。

まとめ

今回は、GASにおけるエラーハンドリングの基本と、実用的な実装方法について解説しました。

  • エラーはつきもの。怖がらずに備えることが大切。
  • try-catch文がエラーハンドリングの基本。
  • tryブロックにエラーが起きそうな処理を書く。
  • catchブロックにエラーが起きたときの処理(ログ記録など)を書く。
  • finallyブロックに、処理の成否に関わらず必ず実行したい後始末処理を書く。
  • エラーログをスプレッドシートに残せば、原因究明が格段に楽になる。

エラーハンドリングを身につけることは、単にプログラムを止めないためだけではありません。将来の自分や、プログラムを引き継ぐ他の人のために、何が起きているのかを分かりやすく示す「思いやり」の実装でもあります。

ぜひ、ご自身が作成したGASにもエラーハンドリングを取り入れて、より安心して使える、ワンランク上の自動化ツールを目指してみてください。

※Googleサービスは、Google LLC の商標であり、この記事はGoogleによって承認されたり、Google と提携したりするものではありません。