「別の市でも同じシステムを使いたい」
そう連絡が来たとき、最初にやったのはコードのコピーだった。AiReview.gsを丸ごとコピーして、設定値3か所を書き換えた。AiReview_B.gsの完成だ。
これで動く。しかし「これでいいのか」という違和感が残った。3つ目の講座が来たらどうするか。4つ目が来たら?
変わるのは設定値だけで、ロジックは完全に同じだ。同じものが3つ、4つと増えていく設計は間違っている。
設定値の差分だけで作った2ファイル
AiReview.gs(A版)とAiReview_B.gs(B版)の違いは3行だけだ。
// A版
const AI_ROOT_FOLDER_ID = '0ANmtZhANtL7gUk9PVA';
const COURSE_NAME = 'ある創業カレッジ2026';
const REGION_EXAMPLE = 'その市';
// B版
const AI_ROOT_FOLDER_ID = '0AKcC-e5F91-8Uk9PVA';
const COURSE_NAME = '別の市の創業塾2026';
const REGION_EXAMPLE = '別の市市';
ロジックは全く同じだ。設定値の変更やバグ修正のたびに2か所を直す必要がある。バグを直したとき「こっちは直したけどあっちは?」という確認が必要になる。
設計を変えることにした。
Flex版の設計方針
AiReview_Flex.gsは「どの講座でも使える」ことを前提に設計した。
TypeFormのフィールドから講座名・地域・受講者情報を動的に取得する。固定値をコードに埋め込まない。
ファイル保存先はGoogle Driveの1つのルートフォルダの中にサブフォルダを作って管理する。
FLEX_ROOT_FOLDER_ID/
├── ある創業カレッジ2026/
│ ├── 田中太郎_2024-05-01/
│ └── 山田花子_2024-05-02/
└── 別の市の創業塾2026/
└── 佐藤一郎_2024-06-01/
新しい講座を追加するときはTypeFormを設定するだけでよく、GASのコードを変更しない。
2フォーム構成を選んだ理由
AiReview.gsは1つのTypeFormで完結していた。受講者はフォームを開いて回答して送信するだけだ。
Flex版では2フォーム構成を採用した。
フォーム1(登録フォーム):メールアドレスと名前を入力して送信する。GASが即座に「添削フォームのURL」を記載した招待メールを送る。
フォーム2(添削フォーム):招待メールのURLを開いて事業計画書の回答を送信する。GASがGeminiで処理して添削レポートを保存する。
2フォームにした理由は「添削フォームのURLを制限したかった」からだ。フォーム1で登録した人だけが添削フォームのURLを手にできる。誰でも使えるようにすると、Gemini APIの使用量が無制限になってしまう。
form_idによる処理の分岐
TypeFormのWebhookリクエストにはどのフォームから来たかを示すform_response.form_idが含まれる。
function doPost(e) {
const payload = JSON.parse(e.postData.contents);
const formId = payload.form_response.form_id;
if (formId === REGISTRATION_FORM_ID) {
handleRegistration(payload);
} else if (formId === REVIEW_FORM_ID) {
handleReviewRequest(payload);
}
return ContentService.createTextOutput(
JSON.stringify({ status: 'ok' })
).setMimeType(ContentService.MimeType.JSON);
}
1つのGASエンドポイントで2つのフォームを受け取る。form_idはjwLurS10という形式だ。TypeFormのフォームURLから取得して定数として定義する。
フィールドタイトルのキーワードマッチ
受講者情報(名前・メールアドレス)はTypeFormのフィールドから取得する。しかしフィールドのIDはフォームごとに異なる。
Flex版ではフィールドのタイトルにキーワードが含まれるかどうかで値を取得する。
function getVal(fields, keyword) {
const field = fields.find(f =>
f.field && f.field.title && f.field.title.includes(keyword)
);
if (!field) return '';
return field.text || field.email || field.url || '';
}
const name = getVal(fields, '氏名') || getVal(fields, '名前') || getVal(fields, 'お名前');
const email = getVal(fields, 'メール') || getVal(fields, 'email');
「氏名」「名前」「お名前」のどのラベルを使っても、同じコードで取得できる。TypeFormのフォームを自由に設計しても、GASのコードを変更する必要がない。
コードをコピーして設定値を変えた瞬間から、「ここは変えてあそこは変えていない」という管理コストが積み上がりはじめる。変わるものと変わらないものを分けることが、設計の仕事だ。
*次回は「スプレッドシートをキューとして使い、GASの30秒制限を回避する」*
*シンプルシステム株式会社 代表 伊藤勝彦*