MENU

動いたGASのコードを、後から直しやすい形に整える【CONFIG・関数分割・try-catch】

前の記事で、Gmailの添付ファイルをGoogleドライブに自動保存するスクリプトを動かしました。

Gmailの添付ファイルをGoogleドライブに自動保存する【GAS初心者向け・コピペOK】

あのコードは「まず動かすこと」だけを考えて書いたものです。実際に動いたので、そのまま自分の仕事で使うつもりで読み返しました。

そこで気づいたことがあります。「保存済み」というラベル名が、コードの中の2か所に書かれていました。検索条件の中と、ラベルを作る行と、両方です。ラベル名を変えたくなったとき、片方だけ直して動かなくなる形です。

そのコードを、次の3点で整えます。

  • 設定を先頭の1か所にまとめる
  • 処理を役割ごとに分ける
  • 1件の失敗で全体が止まらないようにする

動作は前と変わりません。同じことを、後から直しやすい形で書くという話です。

前の記事のコードが動いている前提で進めます。まだの方は、先にそちらを試してください。


目次

整えたあとのコード

先に全体を載せます。前のコードを全部消して、これに貼り替えてください。

/**
 * 設定はすべてここにまとめる
 */
const CONFIG = {
  FOLDER_ID: 'ここにGoogleドライブのフォルダIDを貼る',
  SEARCH_QUERY: 'subject:GASテスト用 has:attachment -label:保存済み',
  PROCESSED_LABEL: '保存済み',
};

/**
 * メイン処理。全体の流れはこの関数を読めば分かる。
 */
function saveGmailAttachments() {
  try {
    const threads = getTargetThreads_();
    const folder = DriveApp.getFolderById(CONFIG.FOLDER_ID);
    const label = getOrCreateLabel_(CONFIG.PROCESSED_LABEL);

    let savedCount = 0;

    for (const thread of threads) {
      savedCount += saveAttachmentsFromThread_(thread, folder);
      thread.addLabel(label);
    }

    Logger.log(`${threads.length}件のスレッドから${savedCount}件のファイルを保存しました`);
  } catch (error) {
    Logger.log('エラーが発生しました: ' + error.stack);
  }
}

/**
 * 検索条件に一致するスレッドを取得する。
 * @return {GmailThread[]}
 */
function getTargetThreads_() {
  return GmailApp.search(CONFIG.SEARCH_QUERY);
}

/**
 * 1スレッド分の添付ファイルを、フォルダへ保存する。
 * 1件の保存に失敗しても、残りの処理は止めない。
 * @param {GmailThread} thread 対象スレッド
 * @param {Folder} folder 保存先フォルダ
 * @return {number} 保存できたファイル数
 */
function saveAttachmentsFromThread_(thread, folder) {
  let count = 0;

  for (const message of thread.getMessages()) {
    for (const attachment of message.getAttachments()) {
      try {
        folder.createFile(attachment);
        count++;
      } catch (error) {
        Logger.log(`保存失敗: ${attachment.getName()} - ${error.message}`);
      }
    }
  }

  return count;
}

/**
 * 指定名のラベルを取得。なければ作成して返す。
 * @param {string} labelName ラベル名
 * @return {GmailLabel}
 */
function getOrCreateLabel_(labelName) {
  const label = GmailApp.getUserLabelByName(labelName);
  return label ? label : GmailApp.createLabel(labelName);
}

行数は増えました。前が34行、これで72行です。倍以上になっています。

短いほうがいいように見えますが、あとで手を入れるときは、こちらのほうが早く終わります。


設定を1か所にまとめる

いちばん先頭にある CONFIG がそれです。

const CONFIG = {
  FOLDER_ID: 'ここにGoogleドライブのフォルダIDを貼る',
  SEARCH_QUERY: 'subject:GASテスト用 has:attachment -label:保存済み',
  PROCESSED_LABEL: '保存済み',
};

前のコードでは、フォルダIDと検索条件が関数の中にあり、ラベル名は2か所に散らばっていました。冒頭に書いた、片方だけ直して動かなくなるパターンです。

設定を先頭にまとめておくと、直すときに見る場所が1つになります。コードの本体を読む必要がありません。

自分以外の誰かにこのスクリプトを渡すときも、「先頭の3行だけ書き換えてください」と言えば済みます。私は職場で使うものをこの形にしています。渡した相手に本体を触らせないで済むのが大きいです。


処理を役割ごとに分ける

関数が4つに分かれました。それぞれの役割はこうです。

  • saveGmailAttachments … 全体の流れを組み立てる
  • getTargetThreads_ … 対象のメールを探す
  • saveAttachmentsFromThread_ … 1通分の添付を保存する
  • getOrCreateLabel_ … ラベルを用意する

メインの関数を見てください。中身はほとんど、他の関数を呼んでいるだけです。

const threads = getTargetThreads_();
const folder = DriveApp.getFolderById(CONFIG.FOLDER_ID);
const label = getOrCreateLabel_(CONFIG.PROCESSED_LABEL);

「対象を探して、フォルダとラベルを用意して、順番に処理する」という流れが、そのまま読めます。細かい手順を追わなくても、何をしているか分かる状態です。

半年後に自分が見返したとき、これが効きます。前のコードは for が3重に入れ子になっていて、どこが何をしているのか、追いかけないと分かりませんでした。

名前の末尾についている _ の意味

getTargetThreads_ のように、末尾にアンダースコアが付いた関数があります。

これはGASの決まりで、末尾に _ を付けた関数は、エディタの実行ボタンから選べなくなります

前のコードでは、実行する関数を選ぶ欄に getOrCreateLabel も並んでいました。人が手で実行するものではないのに、選択肢に出てくるわけです。うっかり選んで実行しても意味がありません。

_ を付けておけば、実行できるのは saveGmailAttachments だけになります。「これが入口です」と示す目印として使えます。


1件の失敗で全体が止まらないようにする

trycatch が2か所に入りました。

メインの関数を囲んでいるほうは、全体の保険です。何か想定外のことが起きたときに、エラーの内容をログに残します。

} catch (error) {
  Logger.log('エラーが発生しました: ' + error.stack);
}

もう1か所は、ファイル1件ごとの保存を囲んでいます。

try {
  folder.createFile(attachment);
  count++;
} catch (error) {
  Logger.log(`保存失敗: ${attachment.getName()} - ${error.message}`);
}

こちらのほうが実用的です。

添付ファイルが壊れていたり、ドライブの容量が足りなかったりすると、保存に失敗することがあります。前のコードだと、そこでスクリプト全体が止まります。10通のうち3通目で失敗すれば、残りの7通は手つかずです。

囲んでおけば、失敗した1件だけをログに記録して、残りの処理を続けます。毎日自動で動かすものは、途中で止まらないことが大事です。

失敗したファイルの名前がログに残るので、後から手で拾えます。


動作確認のしかた

コードを入れ替えたら、必ず1回実行して確かめてください。整えたつもりで壊していることがあります。

ここで先に知っておいてほしいことがあります。前のコードで一度動かしていると、テストメールに「保存済み」ラベルが付いているので、検索に引っかかりません。 そのまま実行すると0件で終わって、うまくいったのかどうか分からなくなります。

私は新しくテストメールを送り直しました。あわせて、次の2つもやっています。

  • テストフォルダを空にする … 実行後にファイルが1つだけ増える状態にしておくと、成功したかどうかを目で確認しやすくなります
  • 添付ファイルを2つ付ける … スレッド数とファイル数が別々に出るので、ログの意味が分かりやすくなります

送ったのは、件名 GASテスト用(半角)、添付は中身のないダミーを2つです。実行するとこうなりました。

11:53:26  実行開始
11:53:29  1件のスレッドから2件のファイルを保存しました
11:53:30  実行完了
スレッド数とファイル数が別々に出るようになりました

前は「1件のスレッドを処理しました」でした。メール1通に添付が何個入っていても、前のログでは「1件」としか出ません。何件保存されたかが分からない形です。

自動で回すものは、後からログを見て確認することになります。数字が出ているかどうかで、確認にかかる時間が変わります。


わざとエラーを起こして確かめる

trycatch が効いているかどうかは、正常に動いた画面を見ても分かりません。エラーを起こしてみないと確認できないところです。

CONFIG.FOLDER_ID の文字列を1文字だけ消して、保存してから実行します。存在しないフォルダIDになるので、確実に失敗します。

11:56:57  実行開始
11:56:56  エラーが発生しました: Exception: Unexpected error while getting
          the method or property getFolderById on object DriveApp.
              at saveGmailAttachments (コード:16:29)
              at __GS_INTERNAL_top_function_call__.gs:1:8
11:56:58  実行完了
エラーが出ているのに、最後は「実行完了」で終わっています

見てほしいのは最後の行です。「実行完了」で終わっています。 前のコードで同じことをすると、赤い文字で止まります。

自動で回しているスクリプトの場合、この違いがそのまま「気づけるかどうか」につながります。止まった場合は実行履歴に失敗として残るだけですが、catch で受けておけば、何が起きたのかがログに文章で残ります。

予想と違ったこと

エラーの文言は Unexpected error while getting the method or property getFolderById on object DriveApp. でした。

「フォルダが見つかりません」とは書かれていません。私は最初、コードの書き方を間違えたのかと思って、getFolderById の綴りを何度も見直しました。

GASのエラーは、原因をそのまま日本語で教えてくれるとは限りません。心当たりのある変更を先に疑うほうが早いです。この場合は、直前にフォルダIDを触っています。

もうひとつ手がかりがあります。2行目の コード:16:29 の部分です。これは「コード.gsの16行目、29文字目」という意味で、エラーが起きた場所を指しています。16行目を見に行けば DriveApp.getFolderById(CONFIG.FOLDER_ID) の行なので、フォルダIDが怪しいと分かります。

確認が終わったら、フォルダIDを元に戻すのを忘れないでください。 私は1文字消しただけだったので、戻すのも1文字です。戻したら、もう一度実行して正常に動くところまで見ておくと安心です。


つまずいたこと:保存しないと反映されない

これは前の記事にも書いたのですが、また同じところで引っかかりました。

フォルダIDを1文字消して、そのまま「実行」を押したつもりでいました。ところがログは前回の成功したままで、時刻も変わりません。何度見ても 1件のスレッドから2件のファイルを保存しました と出ています。

原因は保存し忘れでした。画面の上のほうに「変更が保存されていません」と出ていたのを見落としていたんです。

GASのエディタは、コードを書き換えても保存するまで実行内容に反映されません。ログの時刻が変わっていなければ、実行そのものが行われていないと考えてください。Ctrl + S(Macは Command + S)で保存してから、もう一度実行します。

前回もこれで止まったので、2回目です。手が覚えるまでは、何か直したら保存、を先にやるのがよさそうです。


トリガーは設定し直さなくていい

前の記事で1時間おきのトリガーを設定した方は、そのまま動きます。

トリガーは関数名で登録されているので、saveGmailAttachments という名前が変わっていなければ、設定はそのまま有効です。

逆に言うと、メインの関数名を変えるときはトリガーを作り直す必要があります。名前を変えただけで動かなくなり、しかもエラーも出ないので、気づくのが遅れます。名前は変えないほうが無難です。


まとめ

  • 設定を先頭の CONFIG にまとめると、直す場所が1か所になる
  • ファイル1件ごとに try で囲むと、1件の失敗で全体が止まらない
  • 整えたあとは必ず1回実行する。前のテストメールにはラベルが付いているので、送り直すか、ラベルを外す

このコードにも、まだ弱いところがあります。

ひとつは、保存に失敗してもラベルが付くことです。ファイル1件ごとの try は、失敗をログに残して処理を続けます。ただ、そのあとのラベル付けは、失敗があってもそのまま実行されます。そのメールは次回の検索から外れるので、失敗した添付は放っておくと拾い直されません。ログに 保存失敗: の行が出ていないか、ときどき見てください。

もうひとつは、実行時間です。GASには1回の実行が6分までという上限があり、添付ファイルが大量にたまっていると、途中で打ち切られます。1時間おきに動かしていれば、たまる前に処理されるので普段は問題になりません。ただ、しばらく止めていたあとにまとめて動かすときは、この上限に当たることがあります。

まず動かして、続けて使うと決めてから整える。この順番でいいと思っています。

※この記事の内容は執筆時点のものです。Googleの画面表示は変わることがあります。

最後まで読んでいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

CAPTCHA


目次