基礎 / 繰り返し・関数・配列

第3回 繰り返し・関数・配列 × AIデバッグ

第2回では、1件のデータを 条件分岐(if文) で判定しました。しかし実務のリサーチでは、 何十・何百件もの市場セグメントを一気に処理したいはずです。今回は、複数データをまとめて扱う 配列、同じ処理を全件にかける 繰り返し(ループ)、処理に名前をつけて再利用する 関数 を習得します。さらに後半では、作ったプログラムを 「ボタンひとつで動く業務アプリ」に仕上げる 5つのGAS UIパターン ── 独自メニュー・図形ボタン・セル編集トリガー・サイドバー・ダイアログ ── を、簡単な例と実務例の両方で学びます。 わからない所は ChatGPTにデバッグ・改善を依頼しながら進めます。

🎯

到達目標

配列・二次元配列・forループ・配列メソッド・関数(引数と戻り値)を使い、リサーチ一覧表を一括でPPM判定するGASを書ける。さらに、独自メニュー/図形ボタン/onEditトリガー/サイドバー/ダイアログの5パターンを使い分けて、自分のGASを「現場で使える業務アプリ」に仕立てられる。詰まったらAIにデバッグを依頼して解決できる。

1第2回の振り返りと今回のテーマ

第2回では、変数・データ型・条件分岐(if / else if / else)を学び、 1件の市場データを読み取って PPMマトリクスの4分類(花形・問題児・金のなる木・負け犬) に 自動判定するプログラムを作りました。

前回までにできるようになったこと

1
値を読み書きする

getValue() で読み取り、判定して setValue() で書き戻せる。

2
条件で分岐する

比較演算子・論理演算子と if / else if / else で処理を枝分かれさせられる。

3
AIと協働する

判定表を言葉でAIに渡してコードを書かせ、テストケースで検証できる。

今回の課題 ── 「1件ずつ」では現場が回らない

第2回のプログラムは 1件のセグメントしか判定できませんでした。 しかし実際のリサーチレポートには、市場セグメントが何十行も並んだ一覧表があります。 これを1件ずつコピペして実行していては、自動化の意味がありません。

  • 50行ある市場一覧を、一気に全件PPM判定したい
  • 同じ判定ロジックを、何度も書かずに使い回したい
  • 成長率やシェアの数字を直したら、自動で判定し直してほしい
  • 毎回コードを開かず、ボタンやメニューから実行したい

これらを解決するのが、今回の3つの文法 ── 配列・繰り返し・関数 ── と、後半で扱う GASのUIパターンです。

💡

今回のゴールイメージ。シートに並んだ 10件の市場セグメントを、上部メニューの「▶ 全件をPPM判定する」をクリックするだけで一括判定し、色分けまで自動で完了。さらに数字を直すとその行だけ即座に再判定される——そんな「渡してそのまま使ってもらえる業務ツール」を、今日1日で組み上げます。

2配列 ── 複数の値をひとまとめにする

これまでの変数は「1つの箱に1つの値」でした。配列(Array)は、 「番号のついた仕切りがある、ひとつながりの箱」です。複数の値をまとめて1つの変数に入れられます。 [ ](角かっこ)で囲み、カンマで区切ります。

const segments = ["協働ロボット", "産業用センサー", "AI画像検査"]; "協働ロボット" "産業用センサー" "AI画像検査" [0] [1] [2]
図1:配列は「番号(インデックス)つきの箱」。番号は 0から始まる

番号は「1」ではなく「0」から始まります。最初の要素は segments[0]、2番目が segments[1]。 この「0スタート」は、初学者が最初につまずく代表的なポイントです。「1番目=[0]」と頭の中で1つずらすクセをつけましょう。

コード.gs ── ① 配列の作成と取り出し
function arrayDemo() {
  const segments = ["協働ロボット", "産業用センサー", "AI画像検査"];

  Logger.log(segments[0]);     // → 協働ロボット(1番目)
  Logger.log(segments[2]);     // → AI画像検査(3番目)
  Logger.log(segments.length); // → 3(要素の個数)
}
i

.length で「個数」がわかります。配列に何件入っているかは 配列.length で取得できます。 「全行を処理する」ループで、何回繰り返せばよいかを知るために、この .length が主役になります(第4章)。

3二次元配列とgetValues ── 表をまるごと読む

スプレッドシートは「行 × 列」の表です。この表をまるごと一気に読み取るのが getValues()(前回の getValue()s がついた複数形)です。 返ってくるのは 二次元配列 ── 「配列の中に、各行の配列が入っている」入れ子構造です。

スプレッドシート(A2:C4) 協働ロボット 14 12 産業センサー 6 35 AI画像検査 18 25 getValues() 二次元配列 data [ ["協働ロボット", 14, 12], ← data[0]   ["産業センサー", 6, 35], ← data[1]   ["AI画像検査", 18, 25] ] ← data[2] data[1][2] = 35(2行目・3列目のシェア)
図2:getValues() は表を二次元配列で返す。data[行][列] で1マスを取り出す(行も列も0スタート)
コード.gs ── ② 表をまるごと読み取る
function readTable() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();

  // A2:C4 の 3行 × 3列 を、二次元配列でまとめて読み取る
  const data = sheet.getRange("A2:C4").getValues();

  Logger.log(data[0][0]); // → 協働ロボット(1行目・1列目)
  Logger.log(data[1][2]); // → 35(2行目・3列目)
  Logger.log(data.length);  // → 3(行数)
}

1マスずつ読むのは時代遅れ。50行を getValue() で1つずつ読むと50回シートにアクセスして遅くなりますが、getValues() なら1回で全部読めて高速です。「まとめて読んで、まとめて書く」がGASの鉄則。第8章のメインプログラムでこの威力を体感します。

4forループ ── 全行に同じ処理を繰り返す

配列の全要素に同じ処理をしたい——それが 繰り返し(ループ)です。 GASの基本は for 文。「カウンター変数 i を 0 から1つずつ増やしながら、配列の最後まで繰り返す」という形を丸ごと覚えましょう。

i = 0 から開始 i < data.length ? (まだ続ける?) true i 行目を処理 判定して書き込む i を +1 して戻る(i++) false ループ終了
図3:forループの流れ。「条件を満たす間、処理して i を1つ増やす」を繰り返す
コード.gs ── ③ for文の基本形
function forDemo() {
  const segments = ["協働ロボット", "産業用センサー", "AI画像検査"];

  // i を 0 から、segments の個数より小さい間、1つずつ増やす
  for (let i = 0; i < segments.length; i++) {
    Logger.log((i + 1) + "件目: " + segments[i]);
  }
  // → 1件目: 協働ロボット / 2件目: 産業用センサー / 3件目: AI画像検査
}

for文の「3つの部品」を分解する

部品書く場所意味
let i = 01つ目(初期化)カウンターを 0からスタート(最初の要素 [0] を指す)
i < data.length2つ目(続ける条件)この条件が true の間だけ繰り返す
i++3つ目(更新)1回終わるごとに i を1増やすi = i + 1 の略)

「<」と「<=」を間違えると1件はみ出します。要素が3つの配列の番号は [0][1][2] まで。続ける条件は i < 3未満)が正解です。うっかり i <= 3 と書くと、存在しない [3] を読もうとして undefined になります。この「1個ずれ」エラーは off-by-oneエラーと呼ばれ、第16章でも扱う頻出ミスです。

5配列メソッド ── forEach / map / filter

for 文は万能ですが、配列には「繰り返し専用の便利な道具(メソッド)」が用意されています。 カウンター i を自分で管理しなくてよいぶん、ミスが減り、読みやすくなります。 まずは代表的な3つを押さえましょう。

メソッドひとことで言うとリサーチでの使い道
forEach全要素に順番に処理する(結果は返さない)各行を判定してシートに書き込む
map全要素を別の値に変換して新しい配列を作る成長率の列だけ取り出す/判定ラベルの配列を作る
filter条件に合う要素だけ絞り込む「花形」の市場だけ抽出する
コード.gs ── ④ forEach / map / filter
function methodDemo() {
  const growths = [14, 6, 18, 3]; // 各市場の成長率

  // forEach:1つずつ順番に取り出して処理する
  growths.forEach(function(g) {
    Logger.log("成長率: " + g);
  });

  // map:各値を「成長市場かどうか」に変換した新しい配列を作る
  const labels = growths.map(function(g) {
    return g >= 10 ? "成長" : "成熟";
  });
  Logger.log(labels); // → [成長, 成熟, 成長, 成熟]

  // filter:成長率10以上だけを絞り込む
  const hot = growths.filter(function(g) {
    return g >= 10;
  });
  Logger.log(hot); // → [14, 18]
}
i

はじめは for 文だけでも十分です。「全行を順番に処理する」だけなら for でも forEach でも同じことができます。本研修のメインプログラムは、初学者にも追いやすいように 基本の forで組みます。map / filter は「こういう便利な道具もある」と頭の隅に置いておき、AIが生成したコードに出てきたら読めればOKです。? : は「三項演算子」で、if / else を1行で書く省略形です。

6関数 ── 処理に名前をつけて再利用する

第1回から function ○○() { } という形を書いてきました。これが 関数です。 関数とは 「ひとまとまりの処理に名前をつけて、何度でも呼び出せるようにしたもの」。 PPM判定のように繰り返し使うロジックは、関数にまとめておくと一気に使いやすくなります。

入力(引数) 成長率 14 / シェア 12 judgePpm() if / else で判定する処理 (中身は1回書けばOK) 出力(戻り値) "問題児"
図4:関数は「入力を渡すと、決まった処理をして、結果を返す箱」。中身(ロジック)は1回書けば何度でも使える
コード.gs ── ⑤ 同じロジックを関数にまとめる
// PPM判定の「処理」に judgePpm という名前をつける
function judgePpm(growth, share) {
  if (growth >= 10 && share >= 20) return "花形";
  if (growth >= 10 && share < 20) return "問題児";
  if (growth < 10 && share >= 20) return "金のなる木";
  return "負け犬";
}

// 何度でも、1行で呼び出せる
function useJudge() {
  Logger.log(judgePpm(14, 12)); // → 問題児
  Logger.log(judgePpm(18, 25)); // → 花形
  Logger.log(judgePpm(3,  8));  // → 負け犬
}

「1回書いて、何度も使う」が関数の価値です。もし判定ロジックを直したくなっても、judgePpm の中身を1か所直すだけで、呼び出している全部に反映されます。コピペした10か所を直して回る——という事故がなくなります。これが、大きなプログラムを正確に保つ最大のコツです。

7引数と戻り値 ── 関数に渡す・受け取る

前章の judgePpm(growth, share) には、2つの重要な仕組みが入っています。 引数(ひきすう)戻り値(もどりち)です。これが関数を「部品」として組み合わせる土台になります。

用語役割例(judgePpm
引数関数に渡す入力。かっこの中に書くgrowth(成長率)、share(シェア)
戻り値return返す結果。呼び出し元が受け取る"花形" などの判定ラベル

return を書き忘れると undefined が返ります。結果を呼び出し元に渡したいときは、必ず return で返します。また return が実行された瞬間に関数はそこで終了します(前章の judgePpm が、条件に当てはまったらすぐ return しているのはこのため)。

関数は組み合わせられます。判定ラベルを受け取り、推奨アクションを返す関数も用意しておきましょう。第8章のメインで、この2つを連携させます。

コード.gs ── ⑥ もう一つの部品(アクション関数)
// 判定ランクを受け取り、推奨アクションを返す関数
function recommendAction(rank) {
  if (rank === "花形")       return "重点投資 ── シェアを保ち成長に乗る";
  if (rank === "問題児")     return "育成 ── シェア拡大を狙うか見極める";
  if (rank === "金のなる木") return "維持・収穫 ── 利益を他事業の原資に";
  return "撤退検討 ── 資源の再配分を検討";
}
i

=== は前回の == の「より厳密な版」です。値だけでなくデータ型まで一致するかを見る、より安全な比較で、実務では === が推奨されます。"花形" === "花形" は true。意味は「等しいか」で同じなので、これまで通り「イコール3つ=厳密に等しい」と覚えれば大丈夫です。

8統合実践:リサーチ一覧を一括PPM判定

ここまでの 二次元配列・getValues・forループ・関数 を全部つなげて、 市場セグメント一覧をまるごと一括判定するメインプログラムを組み上げます。 第2回は1件だけでしたが、今回は何十行でも一発です。

入力シートのレイアウト(リサーチ一覧)

シート名を 「リサーチ一覧」 にし、1行目を見出し、2行目以降にデータを並べます。 E・F列(判定・アクション)は空のままでOK。プログラムが自動で埋めます。

A B C D E F 1 セグメント 規模 成長率 シェア PPM判定 アクション 2 協働ロボット 850 14 12 自動 自動 3 産業センサー 1200 6 35 自動 自動 4 AI画像検査 500 18 25 自動 自動 ⋮ 必要なだけ行を増やせます(10行でも100行でも一括判定)
図5:リサーチ一覧シート。A〜D列に入力、E・F列はプログラムが自動で埋める

メインプログラム(一括判定)

流れは ① 表を二次元配列で読む → ② forループで全行を処理 → ③ 各行で judgePpmrecommendAction を呼ぶ → ④ 結果をまとめて一括書き込み。 第6・7章で作った2つの関数を、ここで部品として使います。

コード.gs ── ⑦【メイン】リサーチ一覧を一括判定
function evaluateAll() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");

  // ===== ① データ範囲を二次元配列で読み取る(2行目〜最終行のA〜D列) =====
  const lastRow = sheet.getLastRow();        // 入力されている最終行
  const numRows = lastRow - 1;             // 見出し行を除いたデータ行数
  const data = sheet.getRange(2, 1, numRows, 4).getValues();

  // ===== ② 書き込む結果をためる空の配列を用意 =====
  const output = [];

  // ===== ③ 全行をループして判定する =====
  for (let i = 0; i < data.length; i++) {
    const growth = Number(data[i][2]); // C列:成長率(確実に数値化)
    const share  = Number(data[i][3]); // D列:シェア

    const rank   = judgePpm(growth, share);  // 第6章の関数を呼ぶ
    const action = recommendAction(rank);  // 第7章の関数を呼ぶ

    output.push([rank, action]); // [判定, アクション] を結果配列に追加
  }

  // ===== ④ E2から下へ、結果をまとめて一括書き込み =====
  sheet.getRange(2, 5, output.length, 2).setValues(output);

  Logger.log(output.length + "件の判定が完了しました");
}

このメインに、今日の文法が全部入っています。二次元配列(getValues)、forループ、.length、配列への追加(push)、自作関数の呼び出し(judgePpm / recommendAction)、一括書き込み(setValues)。 getRange(2, 5, output.length, 2) は「2行目・5列目(E2)から、output.length 行 × 2列」という意味です。行も列も1から数える点が、配列の番号(0スタート)と違うので注意しましょう。

色分けも関数で(任意)

判定に応じてE列を色分けする関数も用意しておくと、見やすくなります。後半のUIパターンでも使います。

コード.gs ── ⑧ 判定結果を色分けする
function colorByRank() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
  const numRows = sheet.getLastRow() - 1;
  const ranks = sheet.getRange(2, 5, numRows, 1).getValues(); // E列

  for (let i = 0; i < ranks.length; i++) {
    const rank = ranks[i][0];
    let color = "#ffffff";
    if (rank === "花形")       color = "#fde68a";
    else if (rank === "問題児")     color = "#bfdbfe";
    else if (rank === "金のなる木") color = "#bbf7d0";
    else                          color = "#fecaca";
    // 該当行(i+2行目)のE列に色をつける
    sheet.getRange(i + 2, 5).setBackground(color);
  }
}

9AIにデバッグ・改善を依頼する

ループや関数が絡むと、コードは一気に「動かない」「結果が変」になりがちです。今回のAI活用の主役は デバッグ(不具合の原因究明と修正)。AIに丸投げするのではなく、 「症状」を具体的に伝えて、原因と直し方を引き出すのがコツです。

デバッグ依頼の「型」── 3点セットで伝える

伝える3点具体例
① コード全文実行しているGASをそのまま貼る
② エラーメッセージ/症状TypeError: Cannot read ...」や「最後の1件だけ判定されない」
③ 期待する結果「10件すべてに判定が入ってほしい」
🔧 ChatGPT へのプロンプト ── デバッグ依頼
あなたはGoogle Apps Script(GAS)の専門家です。 初学者が書いた以下のコードがうまく動きません。原因を特定し、修正後のコード全文と、 「なぜ間違っていたか」を初学者にもわかる言葉で説明してください。 【やりたいこと】 「リサーチ一覧」シートの2行目以降について、C列(成長率)とD列(シェア)から PPM判定をして、E列に書き込みたい。 【症状】 一番下の1件だけ判定が入らない。また、たまに「undefined」と表示される。 【いま動かしているコード】 function evaluateAll() { const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧"); const data = sheet.getRange(2, 1, sheet.getLastRow(), 4).getValues(); for (let i = 0; i <= data.length; i++) { const rank = judgePpm(data[i][2], data[i][3]); sheet.getRange(i + 2, 5).setValue(rank); } }

上のコードには、わざと2つのバグを仕込んでいます。getRange(2, 1, sheet.getLastRow(), 4) は行数を取りすぎ(見出し分 -1 が必要)、② i <= data.length は1回多く回る(正しくは <)。これが「最後の1件がおかしい」「undefined が出る」の正体です。AIはこうしたoff-by-oneエラーの指摘が得意なので、デバッグ練習の格好の相棒になります。

「改善」も頼める ── リファクタリング依頼

動いてはいるが読みにくい・遅いコードは、AIに改善(リファクタリング)を頼めます。ただし動作が変わっていないかは必ず自分のテストで確認しましょう。

✨ ChatGPT へのプロンプト ── 改善依頼
次のGASは動いていますが、改善したいです。以下の観点でリファクタリングし、 変更点を箇条書きで説明してください。動作(出力結果)は変えないでください。 - 1マスずつ setValue している部分を、setValues での一括書き込みに変える(高速化) - 判定ロジックは judgePpm という関数に切り出す(再利用しやすく) - 初学者が読めるよう、日本語コメントを各処理につける (ここに自分のコードを貼り付ける)

AIデバッグの黄金ルール:① 症状を具体的に(「動かない」だけはNG、どう動かないかを書く)/② 直してもらったら必ず自分のテストデータで全件検証/③ 「なぜ間違っていたか」を説明させて自分の学びにする。AIは間違えることもあるので、「正解を自分で確認できる状態」を保つのが品質の鍵です。

10GASを「業務アプリ」にする5つのUIパターン

ここからが今回の後半の主役です。これまでのコードは「スクリプトエディタを開いて ▷ 実行」していました。 しかし、同僚に渡して使ってもらうには、それでは不便です。GASには、スプレッドシートを 「ボタンやメニューで動く業務アプリ」に変える仕組みが用意されています。代表的な5つを、ひとつずつ 「簡単な例」と「実務で使う複雑な例」の両方で学びます。

ファイル 編集 表示 挿入 … リサーチツール ▾ ① 独自メニュー ▶ 判定実行 ② 図形ボタン 18 ③ セル編集で自動実行 セグメント追加 追加して判定 ④ サイドバー 判定サマリー ⭐ 花形:3件 ❓ 問題児:2件 💰 金のなる木:4件 🐕 負け犬:1件 OK ⑤ ダイアログ
図6:5つのUIパターンが画面のどこに現れるか。役割を理解して使い分けるのがポイント
パターンどんな時に使うキーになる関数
① 独自メニュー機能を整理して「いつでも呼べる場所」に置きたいonOpen() / createMenu
② 図形ボタンシート上に「押せばすぐ動く」大きなボタンが欲しい図形にスクリプトを割り当て
③ セル編集トリガー値を直したら自動で再計算・更新したいonEdit(e)
④ サイドバー入力フォームや操作パネルを画面横に常駐させたいshowSidebar / HtmlService
⑤ ダイアログ確認・入力・結果サマリーをポップアップで見せたいui.alert / showModalDialog
i

共通の入口は SpreadsheetApp.getUi()メニュー・アラート・サイドバー・ダイアログはすべて、この getUi()(UI=ユーザーインターフェース)から作ります。「画面に何かを出す系はまず getUi()」と覚えておきましょう。以降、第8章で作った evaluateAll / colorByRank / judgePpm / recommendAction を部品として再利用します。

11パターン①:上部に独自メニューを作る

スプレッドシートの上部メニュー(ファイル・編集…の並び)に、自分専用のメニューを追加できます。 鍵は onOpen() という特別な名前の関数。ファイルを開いた瞬間に自動で実行されるため、 ここでメニューを組み立てます。

🟢 簡単な例 ── メニューから挨拶を出すだけ

コード.gs ── ⑨ いちばん簡単な独自メニュー
// ファイルを開くと自動で実行される特別な関数
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu("リサーチツール")        // メニュー名
    .addItem("ごあいさつ", "sayHello")  // 表示名, 実行する関数名
    .addToUi();                          // 画面に追加
}

function sayHello() {
  SpreadsheetApp.getUi().alert("リサーチツールへようこそ!");
}

コードを保存したら、いったんスプレッドシートを再読み込み(ブラウザの更新)してください。 上部に「リサーチツール」メニューが現れ、「ごあいさつ」を選ぶとポップアップが出ます。

関数名は文字列で渡します。addItem("ごあいさつ", "sayHello") の2つ目は、かっこなしの文字列で関数名を書きます。sayHello() のようにかっこをつけるとその場で実行されてしまい、正しく登録できません。また、メニューはファイルを開いた時だけ作られるので、追加直後は再読み込みが必要です。

🔵 実務的な例 ── ツール一式をメニューに集約する

第8章で作った機能を、メニューから呼べるように整理します。addSeparator(区切り線)や addSubMenu(サブメニュー)を使うと、本格的なツールバーになります。

コード.gs ── ⑩ 機能を集約した実務メニュー
function onOpen() {
  const ui = SpreadsheetApp.getUi();
  ui.createMenu("📊 リサーチ自動評価")
    .addItem("▶ 全件をPPM判定する", "runEvaluate")
    .addItem("📈 判定サマリーを表示", "showSummaryDialog")
    .addSeparator()                       // 区切り線
    .addItem("➕ セグメント追加フォーム", "showSidebar")
    .addSubMenu(                            // 入れ子のサブメニュー
      ui.createMenu("⚙ 設定")
        .addItem("判定結果をクリア", "clearResults")
        .addItem("使い方を見る", "showHelp")
    )
    .addToUi();
}

// メニューから呼ぶ:判定して色をつけ、完了通知まで出す
function runEvaluate() {
  evaluateAll();   // 第8章のメイン
  colorByRank();  // 第8章の色分け
  SpreadsheetApp.getActiveSpreadsheet()
    .toast("全セグメントの判定が完了しました", "✅ 完了", 3);
}

// 判定結果(E:F列)を消す
function clearResults() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
  const numRows = sheet.getLastRow() - 1;
  sheet.getRange(2, 5, numRows, 2).clearContent().setBackground(null);
}

function showHelp() {
  SpreadsheetApp.getUi().alert(
    "使い方\n\n1) A〜D列に市場データを入力\n2)「▶ 全件をPPM判定する」を実行\n3) E・F列に判定とアクションが入ります"
  );
}

toast() は控えめな完了通知。画面右下にスッと出て自動で消える通知です(toast(本文, タイトル, 表示秒数))。アラート(OKを押すまで消えない)より邪魔にならないので、「処理が終わった」合図に最適です。これでメニュー1つから、判定→色分け→通知まで一気通貫の業務ツールになりました。

12パターン②:図形を実行ボタンにする

メニューよりもっと直感的なのが、シート上に置いた図形(ボタン)です。 「ここを押せば動く」と一目でわかるので、ITが苦手な同僚にも渡しやすくなります。 ポイントは、図形に「スクリプトを割り当てる」という操作です。

図形にスクリプトを割り当てる手順

1
図形を挿入

「挿入」→「図形描画」で角丸四角などを描き、「保存して終了」でシートに配置。

2
メニューを開く

配置した図形をクリックで選択し、右上に出る「︙」(3点)をクリック。

3
関数名を割り当て

「スクリプトを割り当て」を選び、実行したい関数名だけ(例 runEvaluate)を入力。

割り当てるのは「関数名だけ」。かっこ () は付けません(runEvaluate ○ / runEvaluate() ✗)。また、図形に割り当てる関数は引数を取れません。「ボタン用の入口関数」を1つ用意し、その中から本体を呼ぶ形にします。初回クリック時は承認(権限の許可)を求められるので、許可してください。

🟢 簡単な例 ── 押したらメッセージが出るボタン

コード.gs ── ⑪ ボタンに割り当てる関数(簡単)
// 図形に「buttonHello」を割り当てる
function buttonHello() {
  SpreadsheetApp.getActiveSpreadsheet()
    .toast("ボタンが押されました!", "👆 クリック検知", 3);
}

🔵 実務的な例 ── 「判定実行」「クリア」の2ボタン運用

シートに 「▶ 判定実行」「🧹 結果クリア」の2つの図形を置き、それぞれに関数を割り当てます。 ユーザーはコードを一切触らず、ボタンだけで操作できます。

コード.gs ── ⑫ 2つのボタン用入口関数
// 「▶ 判定実行」ボタンに割り当てる
function onClickEvaluate() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  ss.toast("判定中…", "⏳", 2);
  evaluateAll();
  colorByRank();
  ss.toast("判定が完了しました", "✅ 完了", 3);
}

// 「🧹 結果クリア」ボタンに割り当てる(確認してから消す)
function onClickClear() {
  const ui = SpreadsheetApp.getUi();
  const res = ui.alert("確認", "判定結果(E・F列)を消しますか?", ui.ButtonSet.YES_NO);
  if (res === ui.Button.YES) {
    clearResults();  // 第11章で作った関数を再利用
    SpreadsheetApp.getActiveSpreadsheet().toast("クリアしました");
  }
}

「入口関数」という考え方。ボタンには onClickEvaluate のような専用の入口関数を割り当て、その中で evaluateAll など本体を呼びます。こうすると、トースト通知や確認ダイアログといった「ボタンならではの気配り」を、本体ロジックを汚さずに足せます。メニュー・ボタンの両方から同じ本体を呼べるのも、関数化(第6章)のおかげです。

13パターン③:セル編集トリガー(onEdit)

ボタンすら押さず、「値を直したら、その場で自動実行」を実現するのが onEdit(e) です。これも onOpen と同じ特別な名前の関数で、 誰かがセルを編集するたびに自動で呼ばれます。引数 e には「どこを、何に編集したか」の情報が入っています。

e の中身意味
e.range編集されたセル範囲.getRow() / .getColumn() で行・列番号)
e.value編集後の新しい値
e.oldValue編集前の古い値
e.source対象のスプレッドシート本体

🟢 簡単な例 ── 編集したセルに色をつける

コード.gs ── ⑬ 編集を検知して光らせる
// セルを編集するたびに自動実行される
function onEdit(e) {
  e.range.setBackground("#fff3cd"); // 編集されたセルを黄色に
}

どこかのセルに何か入力してみてください。そのセルが自動で黄色くなれば成功です。

🔵 実務的な例 ── 数字を直すと、その行だけ即・再判定

いよいよ実務レベル。「リサーチ一覧」シートで、成長率(C列)かシェア(D列)を編集したら、その行だけPPM判定をやり直す仕組みです。 全件再計算せず、触った1行だけを更新するのがポイント。「関係ない編集は無視する」ガード(早期 return)が肝になります。

コード.gs ── ⑭ 編集行だけ自動で再判定
function onEdit(e) {
  const sheet = e.range.getSheet();
  const row = e.range.getRow();
  const col = e.range.getColumn();

  // --- 関係ない編集は無視する(ガード) ---
  if (sheet.getName() !== "リサーチ一覧") return; // 別シートなら何もしない
  if (row < 2) return;                       // 見出し行なら無視
  if (col !== 3 && col !== 4) return;        // C・D列以外は無視

  // --- その行の成長率・シェアを読んで再判定 ---
  const growth = Number(sheet.getRange(row, 3).getValue());
  const share  = Number(sheet.getRange(row, 4).getValue());
  const rank   = judgePpm(growth, share);
  const action = recommendAction(rank);

  // --- E・F列に書き込み、色もつける ---
  sheet.getRange(row, 5).setValue(rank);
  sheet.getRange(row, 6).setValue(action);

  let color = "#fecaca";
  if (rank === "花形")       color = "#fde68a";
  else if (rank === "問題児")     color = "#bfdbfe";
  else if (rank === "金のなる木") color = "#bbf7d0";
  sheet.getRange(row, 5).setBackground(color);
}

シンプルな onEdit では「画面に出す系」が使えません。自動で動く onEdit(シンプルトリガー)は、権限の要る操作 ── ui.alert() やメール送信など ── を呼べません。セルの読み書き・色付けはOKです。アラートを出したい等の高度なことをしたい場合は、「インストール型トリガー」という別の仕組みが必要になります(次回以降のトリガー回で扱います)。

i

ガード(早期 return)が読みやすさのコツ。「関係ない編集なら、すぐ return で抜ける」を先頭に並べると、本処理が深いインデントに埋もれず、意図が明確になります。onEditあらゆる編集で毎回動くので、「自分が反応すべき編集か?」を最初に判定するのが定石です。

14パターン④:サイドバーを作る

サイドバーは、画面の右側に常駐する操作パネルです。入力フォームやダッシュボードを置くのに向いています。 中身は HTML(Webページの書き方)で作り、HtmlService 経由で表示します。 GAS(サーバー側)とHTML(画面側)が、google.script.run でやり取りするのがポイントです。

🟢 簡単な例 ── テキストを表示するだけ

コード.gs ── ⑮ いちばん簡単なサイドバー
function showSimpleSidebar() {
  const html = HtmlService.createHtmlOutput(
    "<p style='font-family:sans-serif;padding:12px;'>これはサイドバーです 👋</p>"
  ).setTitle("はじめてのサイドバー");

  SpreadsheetApp.getUi().showSidebar(html);
}

🔵 実務的な例 ── 新セグメントを追加して即判定するフォーム

実務サイドバーの定番は「入力フォーム」です。サイドバーに市場データを入力して「追加」を押すと、 GAS側が一覧表に1行追加し、PPM判定まで済ませて結果を返す——という流れを作ります。 HTMLは 別ファイルとして用意し、createHtmlOutputFromFile で読み込みます。

i

HTMLファイルの作り方:スクリプトエディタ左の「+」→「HTML」を選び、ファイル名を Sidebar にして、下の内容を貼り付けて保存します(拡張子 .html は自動)。Code.gsSidebar.html2ファイル構成になります。

コード.gs ── ⑯ フォーム付きサイドバーを開く+受け取る
// サイドバー(Sidebar.html)を開く
function showSidebar() {
  const html = HtmlService.createHtmlOutputFromFile("Sidebar")
    .setTitle("➕ セグメント追加");
  SpreadsheetApp.getUi().showSidebar(html);
}

// 画面(HTML)から呼ばれる:1行追加してPPM判定し、結果を返す
function addSegment(form) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
  const growth = Number(form.growth);
  const share  = Number(form.share);
  const rank   = judgePpm(growth, share);
  const action = recommendAction(rank);

  // 末尾に1行追加:A〜F列
  sheet.appendRow([form.name, Number(form.size), growth, share, rank, action]);

  return rank; // 画面側に判定結果を返す
}
Sidebar.html ── 入力フォームの画面
<!DOCTYPE html>
<html>
  <head><base target="_top"></head>
  <body style="font-family:sans-serif; padding:12px;">
    <h3>新しいセグメントを追加</h3>

    <label>セグメント名</label>
    <input id="name" type="text" style="width:100%">
    <label>市場規模(億円)</label>
    <input id="size" type="number" style="width:100%">
    <label>成長率(%)</label>
    <input id="growth" type="number" style="width:100%">
    <label>自社シェア(%)</label>
    <input id="share" type="number" style="width:100%">

    <button onclick="submitForm()" style="margin-top:10px;">追加して判定</button>
    <p id="result" style="font-weight:bold;"></p>

    <script>
      function submitForm() {
        const form = {
          name:   document.getElementById("name").value,
          size:   document.getElementById("size").value,
          growth: document.getElementById("growth").value,
          share:  document.getElementById("share").value
        };
        // GAS側の addSegment を呼び、戻り値を画面に表示
        google.script.run
          .withSuccessHandler(function(rank) {
            document.getElementById("result").textContent = "判定: " + rank;
          })
          .addSegment(form);
      }
    </script>
  </body>
</html>

google.script.run が画面とサーバーの「橋」です。HTML側の google.script.run.addSegment(form) が、GAS側の addSegment 関数を呼び出します。結果は withSuccessHandler に渡した関数で受け取ります(処理は非同期なので、戻り値を直接代入できない点に注意)。これで、コードを知らない人でもフォーム入力だけでデータ追加できる本格ツールの完成です。

15パターン⑤:ダイアログを表示する

最後はポップアップ=ダイアログ。「確認したい」「ちょっと入力させたい」「結果サマリーを見せたい」場面で使います。 手軽な順に alert(お知らせ)→ ② prompt(入力)→ ③ showModalDialog(HTMLで自由表示) の3段階を押さえましょう。

🟢 簡単な例 ── alert と prompt

コード.gs ── ⑰ お知らせと入力
// ① alert:メッセージを見せてOKを待つ
function showAlert() {
  SpreadsheetApp.getUi().alert("判定が完了しました!");
}

// ② prompt:ユーザーに文字を入力してもらう
function askThreshold() {
  const ui = SpreadsheetApp.getUi();
  const res = ui.prompt("成長率のしきい値は?(例: 10)", ui.ButtonSet.OK_CANCEL);
  if (res.getSelectedButton() === ui.Button.OK) {
    ui.alert("入力された値: " + res.getResponseText());
  }
}
i

prompt は「どのボタンが押されたか」と「入力文字」の2つを返します。getSelectedButton() でOK/キャンセルを判定し、getResponseText() で入力文字を取り出します。ButtonSet には OK / OK_CANCEL / YES_NO などがあります。

🔵 実務的な例 ── 判定サマリーをHTMLダイアログで見せる

一覧の判定結果を集計し、「花形◯件/問題児◯件…」のサマリーをきれいなポップアップで表示します。 集計には第5章の forEach を使い、表示は showModalDialog(HTMLを自由にレイアウトできるダイアログ)で行います。

コード.gs ── ⑱ 判定サマリーのダイアログ
function showSummaryDialog() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("リサーチ一覧");
  const numRows = sheet.getLastRow() - 1;
  const ranks = sheet.getRange(2, 5, numRows, 1).getValues(); // E列

  // --- forEach で件数を集計する ---
  const count = { "花形": 0, "問題児": 0, "金のなる木": 0, "負け犬": 0 };
  ranks.forEach(function(r) {
    const k = r[0];
    if (count[k] !== undefined) count[k]++;
  });

  // --- 集計結果をHTMLに組み立てる ---
  const html =
    "<div style='font-family:sans-serif; padding:8px 14px;'>" +
    "<h3 style='margin-top:0;'>PPM判定サマリー</h3><ul style='line-height:2;'>" +
    "<li>⭐ 花形:"       + count["花形"]       + " 件</li>" +
    "<li>❓ 問題児:"     + count["問題児"]     + " 件</li>" +
    "<li>💰 金のなる木:" + count["金のなる木"] + " 件</li>" +
    "<li>🐕 負け犬:"     + count["負け犬"]     + " 件</li>" +
    "</ul></div>";

  const dialog = HtmlService.createHtmlOutput(html)
    .setWidth(320).setHeight(220);
  SpreadsheetApp.getUi().showModalDialog(dialog, "📈 判定サマリー");
}

3つのダイアログの使い分け:alert は「見せて終わり」、prompt は「短い入力」、showModalDialog は「自由なレイアウトで見せる/複雑な操作」。サマリーやグラフ、複数項目の入力など凝った表示は showModalDialog(HTML)が担当します。第11章のメニュー「📈 判定サマリーを表示」から、この関数を呼べるようにつないであります。

16つまずきポイントと対処

繰り返し・関数・UIが絡む今回は、つまずきも増えます。代表的な5つを先回りで押さえましょう。

① ループの「1個ずれ」(off-by-one)

続ける条件を <= にすると1回多く回り、存在しない要素を読んで undefined が出ます。配列ループは <(未満)が基本です。

よくある間違い → 正しい書き方
// ✗ 間違い:i が data.length まで回り、1件はみ出す
for (let i = 0; i <= data.length; i++) { ... }

// ✓ 正しい:未満(<)で止める
for (let i = 0; i < data.length; i++) { ... }

② getValues の行数を取りすぎる

getLastRow() は「データの最終行番号」。見出し行を除いたデータ行数は getLastRow() - 1 です。 getRange(2, 1, getLastRow(), 4) のように -1 を忘れると、空行まで読んで判定が崩れます。

③ 配列の番号は0から、シートの行・列は1から

対象数え始め
配列のインデックス0 から1件目 = data[0]
getRange の行・列1 から1行1列目(A1) = getRange(1, 1)

だから「配列の i 番目」と「シートの行」をつなぐとき、getRange(i + 2, ...) のように「+2」(0始まり+見出し1行)の調整が要ります。

④ メニュー・トリガーが反応しない

onOpenonEdit名前が決まった特別な関数です。綴りを間違える(onedit 等)と動きません。 また独自メニューはファイルを開いた時だけ作られるので、追加直後は再読み込みが必要。 シンプル onEditui.alert 等の権限が要る操作は使えない点も再確認しましょう(第13章)。

⑤ 図形ボタンに割り当て忘れ/引数つき関数を割り当てる

図形を置いただけでは動きません。「︙」→「スクリプトを割り当て」で関数名(かっこなし)を登録します。 割り当てる関数は引数なしであること。引数が必要な処理は、引数なしの「入口関数」から呼び出してください(第12章)。

i

困ったら、まず Logger.log と「実行ログ」。onEdit がそもそも呼ばれているか?」を確かめたいときは、関数の先頭に Logger.log(e.range.getA1Notation()) を入れて、編集後に「実行数」画面でログを見ます。どこまで動いているかを見える化するのが、デバッグの第一歩です(第9章のAIデバッグにも、このログを添えると精度が上がります)。

HW

第3回の宿題

次回までに、以下の4つに取り組んでください。所要時間:60〜90分程度
1

リサーチ一覧の一括判定プログラムを完成・実行する

第8章のレイアウトで「リサーチ一覧」シートを作り(データは5件以上)、judgePpm / recommendAction / evaluateAll / colorByRank を貼り付けて実行。全行のE・F列が自動で埋まり、色分けされることを確認してください。

2

独自メニュー+図形ボタンの「両方」から動かす

第11章の実務メニュー(📊 リサーチ自動評価)を作り、再読み込みしてメニューから判定を実行。さらに第12章の手順で図形ボタン「▶ 判定実行」を置き、onClickEvaluate を割り当てて、ボタンからも同じ処理が動くことを確認しましょう。

3

onEditで「自動再判定」を体験する

第13章の onEdit(実務版)を追加し、成長率(C列)やシェア(D列)の数字を書き換えると、その行だけ判定と色が自動更新されることを確認してください。「見出し行を編集しても何も起きない」「別シートでは反応しない」というガードの動きも試しましょう。

4

AIデバッグ&自分の業務ツール化に挑戦

まず第9章のバグ入りコードをデバッグ依頼プロンプトでAIに直してもらい、「なぜ間違いか」を1〜2行でメモ。次に、サイドバー(第14章)かダイアログ(第15章)のどちらか一方を自分のツールに足してみてください。余力があれば、自分の業務データ(問い合わせ一覧、在庫リスト等)でも一括判定ツールを作ってみましょう。次回冒頭で数名に共有いただきます。

📩

提出方法:作成したスプレッドシートのURL、最終的に動いたGASコード(Code.gs、サイドバーを作った方は Sidebar.html も)、宿題④でAIに直してもらった「バグの原因メモ」の3点を、研修事務局へメールでご提出ください。提出期限:次回研修の前日17:00まで。