テスト・デバッグ・デプロイ

第VI部:GASウェブアプリをテスト、デバッグし、実際に運用するための知識

20. エラー処理の基本

GASアプリケーションの信頼性を高めるため、適切なエラー処理は不可欠です。

一般的なエラーの種類

  • 構文エラー(SyntaxError):コードの書き方が間違っている
  • 実行時エラー(RuntimeError):実行中に発生するエラー
  • 論理エラー(LogicalError):コードは動くが結果が期待と異なる
エラー種類 原因例 対処法
構文エラー 括弧の閉じ忘れ、セミコロンの抜け エディタの警告を確認して修正
実行時エラー 存在しないシートの参照、データ型の不一致 try-catchブロックでエラーをキャッチ
論理エラー 計算ロジックの誤り、条件分岐のミス ログ出力で中間値を確認し、デバッグ

try-catchによるエラーハンドリング

try-catchブロックを使うと、エラーが発生してもアプリケーションがクラッシュせず、適切に対応できます。

基本的なtry-catch構文
try {
  // エラーが発生する可能性がある処理
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('存在しないかもしれないシート');
  var data = sheet.getDataRange().getValues();
  // データ処理...
} catch (e) {
  // エラー発生時の処理
  Logger.log('エラーが発生しました: ' + e.toString());
  return '申し訳ありません。データの取得中にエラーが発生しました。';
} finally {
  // エラーの有無にかかわらず実行される処理
  // リソースの解放などを行う
}

ユーザーフレンドリーなエラー表示

エラーが発生した場合でも、ユーザーに分かりやすく伝えることが重要です。

ユーザーフレンドリーなエラー表示の例
// サーバーサイドの処理
function getStudentData(studentId) {
  try {
    // スプレッドシートからデータ取得
    var ss = SpreadsheetApp.openById('スプレッドシートID');
    var sheet = ss.getSheetByName('生徒データ');
    
    // データが見つからない場合
    if (!sheet) {
      throw new Error('データシートが見つかりません');
    }
    
    var data = sheet.getDataRange().getValues();
    var studentData = null;
    
    // 生徒IDでデータを検索
    for (var i = 1; i < data.length; i++) {
      if (data[i][0] == studentId) {
        studentData = {
          id: data[i][0],
          name: data[i][1],
          grade: data[i][2],
          class: data[i][3]
        };
        break;
      }
    }
    
    // 生徒データが見つからない場合
    if (!studentData) {
      throw new Error('指定された生徒IDのデータが見つかりません');
    }
    
    return {
      success: true,
      data: studentData
    };
  
  } catch (e) {
    // エラー情報をログに記録
    Logger.log('エラー発生: ' + e.toString());
    
    // ユーザーフレンドリーなエラーメッセージを返す
    return {
      success: false,
      error: {
        code: 'DATA_NOT_FOUND',
        message: e.message || 'データの取得中にエラーが発生しました。しばらく経ってからもう一度お試しください。'
      }
    };
  }
} // クライアントサイドでのエラー表示
<script>
  function loadStudentData() {
    var studentId = document.getElementById('studentId').value;
    document.getElementById('loadingMessage').style.display = 'block';
    document.getElementById('errorMessage').style.display = 'none';
    document.getElementById('studentData').style.display = 'none';
    
    google.scripts.run
      .withSuccessHandler(function(result) {
        document.getElementById('loadingMessage').style.display = 'none';
        
        if (result.success) {
          // データ表示処理
          var data = result.data;
          document.getElementById('studentName').textContent = data.name;
          document.getElementById('studentGrade').textContent = data.grade;
          document.getElementById('studentClass').textContent = data.class;
          document.getElementById('studentData').style.display = 'block';
        } else {
          // エラーメッセージ表示
          document.getElementById('errorText').textContent = result.error.message;
          document.getElementById('errorMessage').style.display = 'block';
        }
      })
      .withFailureHandler(function(error) {
        document.getElementById('loadingMessage').style.display = 'none';
        document.getElementById('errorText').textContent =
          'システムエラーが発生しました。しばらく経ってからもう一度お試しください。';
        document.getElementById('errorMessage').style.display = 'block';
      })
      .getStudentData(studentId);
  }
</script> <!-- HTML部分 -->
<div id="loadingMessage" style="display: none;">
  データを読み込んでいます...
</div>
<div id="errorMessage" class="error-container" style="display: none;">
  <p class="error-icon">⚠️</p>
  <p id="errorText"></p>
  <button onclick="loadStudentData()">再試行</button>
</div>
<div id="studentData" style="display: none;">
  <h3>生徒情報</h3>
  <p>氏名: <span id="studentName"></span></p>
  <p>学年: <span id="studentGrade"></span></p>
  <p>クラス: <span id="studentClass"></span></p>
</div>
エラー表示のベストプラクティス
  • 技術的な詳細よりもユーザーが理解できる言葉で説明する
  • 何が起きたかだけでなく、ユーザーが次に何をすべきかを示す
  • 視覚的に目立つがユーザーを脅かさないデザインを使用する
  • 可能であれば再試行やフォールバックオプションを提供する

21. よくあるエラーとその解決法

GAS開発でよく遭遇するエラーとその解決方法を学びましょう。

初心者がつまずきやすいエラー集

エラーメッセージ 考えられる原因 解決策
TypeError: Cannot read property 'getRange' of null 存在しないスプレッドシートやシートを参照している シート名を確認し、正確に指定する
ReferenceError: ... is not defined 定義されていない変数や関数を使用している 変数名や関数名のスペルミスを確認する
SyntaxError: Unexpected token 構文エラー(括弧の閉じ忘れなど) コードの構文を確認し、括弧やセミコロンを修正
Exception: Service invoked too many times in a short time 短時間に多くのAPIリクエストを実行した 処理を分散させる、キャッシュを活用する
Script has attempted to exceed maximum execution time スクリプトの実行時間が長すぎる 処理を複数のバッチに分ける、トリガーを活用する
よくあるミス:スプレッドシートの権限不足、誤ったURLの指定、大文字小文字の区別の誤りなどが原因になることが多いです。

AIを活用したエラー解決法

エラーが発生した場合、生成AIを活用することで効率的に解決できます。

AIへの効果的な質問方法
  1. エラーメッセージ全文をコピーする
  2. エラーが発生した状況や背景を簡潔に説明する
  3. 関連するコードの該当部分を提示する
  4. 「このエラーの原因と解決方法を教えてください」と質問する
エラーメッセージをそのままAIに質問すると、具体的で的確な回答が得られやすくなります!

トラブルシューティングのフロー

エラーが発生した際の体系的な解決手順を学びましょう。

トラブルシューティングの手順
  1. エラーメッセージの確認:具体的なエラー内容と発生箇所を特定
  2. エラーの切り分け:問題の箇所を特定するために小さな単位でテスト
  3. ログの活用:Logger.logを使って中間値や処理の流れを確認
  4. AIに質問:エラーメッセージとコードを提示して解決策を質問
  5. 修正と再テスト:提案された解決策を実装し、再度テスト
  6. エラー対策の追加:同様のエラーを防ぐためのコード改善
エラー解決の実例
// エラー:TypeError: Cannot read property 'getSheetByName' of null

// 問題のあるコード
function getGrade(name) {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Grades');
  var data = sheet.getDataRange().getValues();
  // ...
}

// 修正後のコード
function getGrade(name) {
  try {
    // URLを指定してスプレッドシートを開く
    var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/...');
    var sheet = ss.getSheetByName('Grades');
    
    // シートが存在するか確認
    if (!sheet) {
      Logger.log('シートが見つかりません: Grades');
      return '成績データが見つかりません';
    }
    
    var data = sheet.getDataRange().getValues();
    // ...
  } catch (e) {
    Logger.log('エラーが発生しました: ' + e.toString());
    return 'データ取得中にエラーが発生しました';
  }
}

22. テストとデバッグの基本

コードを書いたら、正しく動作するかテストし、問題があれば修正する必要があります。特にプログラミング初心者にとって、この過程は重要です。

テスト実行の方法

  1. 小さな機能ごとにテスト実行する
  2. 「実行」ボタンで選択した関数を直接実行できる
  3. テスト用の関数を作成して実行する
テスト用関数の例
function testGetGrade() {
  // テスト用データ
  var testNames = ['山田太郎', '佐藤花子', '鈴木一郎', '存在しない名前'];
  
  // 各データでテスト実行
  for (var i = 0; i < testNames.length; i++) {
    var name = testNames[i];
    var result = getGrade(name);
    Logger.log('名前: ' + name + ', 結果: ' + result);
  }
}

ログ出力によるデバッグ

コードの実行過程や変数の値を確認するために、ログ出力を活用しましょう。

ログ出力の基本
function processData() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('データ');
  var data = sheet.getDataRange().getValues();
  
  Logger.log('データ行数: ' + data.length);
  Logger.log('先頭行: ' + JSON.stringify(data[0]));
  
  var totalScore = 0;
  var count = 0;
  
  for (var i = 1; i < data.length; i++) { // ヘッダー行をスキップ
    var score = data[i][2]; // 3列目が点数
    
    // デバッグ出力
    Logger.log('処理中 - 行: ' + i + ', 名前: ' + data[i][1] + ', 点数: ' + score);
    
    if (typeof score === 'number' && !isNaN(score)) {
      totalScore += score;
      count++;
    } else {
      Logger.log('警告: 無効な点数データ - 行: ' + i + ', 値: ' + score);
    }
  }
  
  var average = count > 0 ? totalScore / count : 0;
  Logger.log('集計結果 - 合計点: ' + totalScore + ', 件数: ' + count + ', 平均: ' + average);
  
  return average;
}
ログの確認方法
  • GASエディタで「表示」>「ログ」を選択
  • ショートカットキー:Ctrl+Enter (Windows) / Cmd+Enter (Mac)
  • 実行完了後、自動的にログビューが表示される設定も可能

段階的なテスト手法

複雑なアプリケーションをテストする場合は、段階的なアプローチが効果的です。

段階的テストの手順
  1. 単体テスト:個々の関数や機能を個別にテスト
  2. 結合テスト:複数の関数が連携する部分をテスト
  3. システムテスト:アプリケーション全体の動作をテスト
  4. ユーザー受け入れテスト:実際のユーザーによる検証
効率的なテストのコツ:テストケースをあらかじめ文書化し、想定される入力と期待される出力を明確にしておくと、テストがスムーズに進みます。

23. デプロイと共有

開発したGASアプリケーションを実際に使えるようにデプロイし、ユーザーと共有する方法を学びましょう。

ウェブアプリとしてのデプロイ方法

  1. GASエディタで「デプロイ」>「新しいデプロイ」をクリック
  2. 歯車アイコンをクリックし、「ウェブアプリ」を選択
  3. 次のパラメータを設定:
    • 説明:デプロイの目的を簡潔に記述
    • 実行するユーザー:「自分(あなたのメール)」または「アプリにアクセスするユーザー」
    • アクセスできるユーザー:「全員」「組織内の全員」「指定したユーザーのみ」から選択
  4. 「デプロイ」ボタンをクリック
  5. アクセス許可を求められた場合は「許可」を選択
  6. 生成されたウェブアプリURLをコピー
重要:コードを変更した場合は、新しいバージョンとして再デプロイする必要があります。

アクセス権限の設定

GASアプリケーションのアクセス権限には複数のオプションがあります。

アクセス設定 説明 適用例
全員(匿名を含む) インターネット上の誰でもアクセス可能 公開情報を提供するアプリ
組織内の全員 同じGoogle Workspaceドメインのユーザーのみ 学校全体で利用するアプリ
指定したユーザーのみ 特定のユーザーやグループのみ 特定の教員やクラスのみが使用するアプリ
実行ユーザーの設定による違い
  • 自分(開発者)として実行:アプリは開発者のアクセス権限でスプレッドシートなどにアクセス。ユーザー自身の権限は不要
  • アプリにアクセスするユーザーとして実行:各ユーザー自身の権限でスプレッドシートなどにアクセス。ユーザーごとに適切な権限が必要

同僚への共有テクニック

同僚と効果的にアプリを共有するためのテクニックを紹介します。

共有の方法
  • URL共有:デプロイしたウェブアプリのURLをメールやチャットで共有
  • QRコード:URLをQRコードに変換して印刷物やスライドで共有
  • 学校サイトへの埋め込み:iframeを使ってGoogleサイトなどに埋め込み
  • ショートカットの作成:Chromeのアプリショートカットとしてデスクトップやタスクバーに追加
GoogleサイトでのiFrame埋め込み例
<!-- 以下のコードをGoogleサイトのHTMLブロックに追加 -->
<iframe
  src="https://script.google.com/macros/s/.../exec"
  width="100%"
  height="500px"
  frameborder="0"
  scrolling="yes">
</iframe>
効果的な共有のためのアドバイス
  • 簡単な使い方ガイドを作成して一緒に共有する
  • 初回使用時のデモや操作説明の時間を設ける
  • フィードバックを収集する仕組みを用意する
  • アップデート情報を定期的に共有する

24. セキュリティと個人情報保護

教育データを扱う際は、セキュリティと個人情報保護に特に注意が必要です。

教育データの安全な管理方法

  • 最小権限の原則:必要最小限のデータにのみアクセスを許可
  • データの匿名化:可能な限り個人を特定できる情報を削除または置換
  • セキュアな保存場所:組織のGoogle Driveなど管理されたストレージを使用
  • 定期的なバックアップ:重要データは定期的にバックアップを作成
セキュリティのベストプラクティス
  • 機密データは専用のスプレッドシートに分けて保存
  • 個人を特定できる情報と成績などのデータを別々に管理
  • アプリケーションのアクセスログを取得・保存
  • 不要になったデータは適切に削除または匿名化

アクセス制限の実装

アプリケーション内でユーザーのアクセス権限を制御する方法を紹介します。

ユーザー認証と権限管理の例
function doGet(e) {
  // 現在のユーザーのメールアドレスを取得
  var userEmail = Session.getActiveUser().getEmail();
  
  // 管理者リストを取得
  var adminEmails = getAdminEmails();
  
  // 権限に基づいてテンプレートを選択
  var template;
  if (adminEmails.indexOf(userEmail) !== -1) {
    // 管理者向けページ
    template = HtmlService.createTemplateFromFile('admin');
  } else if (userEmail.endsWith('@school.edu')) {
    // 教員向けページ
    template = HtmlService.createTemplateFromFile('teacher');
  } else {
    // 一般ユーザー向けページ
    template = HtmlService.createTemplateFromFile('index');
  }
  
  // ユーザー情報をテンプレートに渡す
  template.userEmail = userEmail;
  template.isAdmin = (adminEmails.indexOf(userEmail) !== -1);
  
  return template.evaluate()
    .setTitle('教育アプリ')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1');
} // 管理者メールアドレスのリストを取得
function getAdminEmails() {
  var ss = SpreadsheetApp.openById('スプレッドシートID');
  var sheet = ss.getSheetByName('権限設定');
  var data = sheet.getDataRange().getValues();
  
  var adminEmails = [];
  for (var i = 1; i < data.length; i++) { // ヘッダー行をスキップ
    if (data[i][1] === 'admin') { // 2列目が権限レベル
      adminEmails.push(data[i][0]); // 1列目がメールアドレス
    }
  }
  
  return adminEmails;
}
注意点:Session.getActiveUserを使用するには、「アプリにアクセスするユーザー」としてスクリプトを実行するように設定する必要があります。

情報漏洩防止の対策

  • 入力データの検証:ユーザー入力は適切にバリデーション
  • エラーメッセージの制御:詳細なエラー情報を外部に公開しない
  • ログ管理:重要な操作のログを記録
  • 定期的な監査:アクセス権限とデータ使用状況を定期的に確認
操作ログの記録例
function logUserAction(action, details) {
  var user = Session.getActiveUser().getEmail();
  var timestamp = new Date();
  
  var ss = SpreadsheetApp.openById('ログスプレッドシートID');
  var sheet = ss.getSheetByName('アクセスログ');
  
  // ログを追加
  sheet.appendRow([
    timestamp,
    user,
    action,
    JSON.stringify(details),
    Session.getActiveUserLocale(),
    Session.getScriptTimeZone()
  ]);
} // 使用例
function viewStudentData(studentId) {
  // ログを記録
  logUserAction('view_student_data', { studentId: studentId });
  
  // データ取得処理...
}
情報保護のポイント
  • 必要のない個人情報は収集しない
  • 使用目的を明確にし、それ以外の目的には使用しない
  • データの保持期間を設定し、期間経過後は適切に削除
  • 学校や自治体のデータ保護ポリシーに準拠する