スパゲッティコードからの脱却:なぜ整理整頓が必要なのか
これまでの章では、Google Apps Script(GAS)を用いてスプレッドシートの操作や外部連携、動的なデータ処理など、様々な自動化を実現してきました。開発者の皆さんは、AIの力を借りながら、あるいは自らの手でコードを記述し、意図した通りにプログラムが動く喜びを感じていることでしょう。しかし、機能を追加するたびにコードの行数が増え、一つの関数(特にmyFunctionなどのメイン関数)が数百行にも及ぶ巨大なブロックになってはいないでしょうか。処理の流れが複雑に絡み合い、どこで何をしているのかが一目で判別できない状態、いわゆる「スパゲッティコード」は、開発者にとって最大の敵です。
スパゲッティコードは、バグの温床となるだけでなく、修正や機能追加を極めて困難にします。「ちょっとした修正のつもりが、全く関係ない別の場所に影響を与えてエラーを引き起こした」という経験は、多くのプログラマが通る道です。また、数ヶ月後に自分のコードを見返した際、何を書いたのか全く理解できないという事態も招きます。これは、コードが「動けばいい」という段階から、「保守・運用できる資産」へと昇華できていない証拠です。
本格的な開発者としてステップアップするためには、書き散らかしたコードを整理整頓し、論理的な構造を持たせるスキルが不可欠です。この章では、長くなったコードを意味のある単位(関数)に分割し、可読性と再利用性を高める「リファクタリング」の手法を学びます。部屋の片付けと同じように、コードも定期的に整理することで、開発効率は劇的に向上します。AIにコード生成を依頼する際も、整理された構造を意識した指示を出すことで、より高品質な回答を引き出すことが可能になります。ここから、単なるスクリプターから、アーキテクチャを設計できるエンジニアへの変革を始めましょう。
リファクタリングの本質:振る舞いを変えずに構造を改善する
リファクタリング(Refactoring)とは、ソフトウェア工学における重要な用語で、「外部から見たプログラムの挙動を変えることなく、内部の構造を整理・改善すること」を指します。つまり、プログラムの入力と出力の結果は変えずに、コードの読みやすさや保守性を向上させる作業のことです。初心者はしばしば、機能追加とリファクタリングを同時に行おうとしますが、これは推奨されません。リファクタリングを行う際は、純粋に「コードの掃除」に集中し、機能の変更は行わないことが鉄則です。
なぜ、わざわざ動いているコードを触る必要があるのでしょうか。それは「技術的負債」を返済するためです。急いで書いたコードや、コピペで増殖した似たような処理は、将来的な変更に対する「負債」となります。この負債を放置すると、利子のようにメンテナンスコストが膨らんでいきます。リファクタリングは、この負債を解消し、将来の開発をスムーズにするための投資です。
GAS開発におけるリファクタリングの具体的なアプローチとしては、主に「重複コードの排除」「長い関数の分割」「変数の適切な命名」「マジックナンバー(意味不明な数値)の定数化」などが挙げられます。特に重要なのが「関数の抽出」です。例えば、請求書作成スクリプトの中で「消費税計算」のロジックが複数の場所に散らばっているなら、それを「calculateTax」という一つの関数にまとめます。これにより、もし消費税率が変わったとしても、その一つの関数を修正するだけで全ての計算箇所に反映されるようになります。
また、リファクタリングは一度やって終わりではありません。コードを書くたびに、少しずつ整理を行う「ボーイスカウト・ルール(来た時よりも美しくして立ち去る)」を意識することが、健全なコードベースを維持する秘訣です。本章では、AIを活用しながら、安全かつ効果的にリファクタリングを行う手順を詳述します。AIはコードの構造解析を得意としているため、リファクタリングの強力なパートナーとなります。
関数の解剖学:入力(引数)と出力(戻り値)の関係性
リファクタリングの中心となるのは、独自の「関数」を作成することです。これまでは function myFunction() { ... } のように、引数も戻り値もない関数を主に扱ってきましたが、プログラミングにおける関数の真価は、データを受け取り、加工して返す「変換装置」としての役割にあります。数学の関数 y = f(x) を思い出してください。x を入れたら y が出てくる、このシンプルな構造こそが関数の本質です。
GAS(JavaScript)における関数は、以下の要素で構成されます。
1. 関数名:処理の内容を表す動詞(例:calculateTotal)
2. 引数(Parameters):関数が受け取る入力データ
3. 処理ブロック:波括弧 {} 内のロジック
4. 戻り値(Return Value):処理結果として呼び出し元に返すデータ
この構造を理解することは、システムを「部品(コンポーネント)」の集合体として捉える第一歩です。巨大なメイン関数の中にすべての処理を書くのではなく、getData()、processData()、outputData() のように役割ごとに関数を分け、メイン関数はそれらを順番に呼び出す「司令塔」の役割に徹する。これが、読みやすくメンテナンスしやすいコードの理想形です。
関数化する際のポイントは、「単一責任の原則(Single Responsibility Principle)」を意識することです。一つの関数は、一つのことだけを行うべきです。「データを取得して、計算して、メールを送る」という関数は作りすぎです。これらは3つの関数に分割されるべきです。AIにコード生成を依頼する際も、「この処理を関数化して」と指示するだけでなく、「単一責任の原則に従って関数を分割してください」と付け加えることで、より洗練されたコード構造が提案されます。
汎用性を高める引数の設計とスコープの理解
関数を柔軟な「部品」にするために欠かせないのが「引数(ひきすう)」です。引数とは、関数を呼び出す際に外部から渡すデータのことで、関数の定義では function sendEmail(recipient, subject, body) のように括弧内に変数名として記述します。関数内部では、これらの変数を通常の変数と同じように扱うことができますが、その値は実行時に決定されます。
引数を活用することで、関数の汎用性は飛躍的に向上します。例えば、特定の宛先にメールを送る処理をハードコーディング(コード内に直接記述)してしまうと、宛先が変わるたびにコードを修正しなければなりません。しかし、宛先を引数として受け取るように設計すれば、同じ関数を使ってAさんにもBさんにもメールを送ることができます。つまり、処理の「ロジック」と「データ」を分離することができるのです。
また、引数を理解する上で重要なのが「スコープ(有効範囲)」の概念です。関数内で宣言された変数や引数は、基本的にその関数の中だけで有効な「ローカル変数」となります。関数Aの変数 x と、関数Bの変数 x は、名前が同じでも全くの別物として扱われます。これにより、変数の名前衝突を気にすることなく開発を進めることができます。
一方で、関数の外で宣言された変数は「グローバル変数」と呼ばれ、どこからでもアクセス可能ですが、多用するとコードの依存関係が複雑になり、リファクタリングを妨げる要因となります。開発者としては、可能な限りグローバル変数の使用を避け、必要なデータは引数として関数に渡す設計を心がけるべきです。AIに対しても「グローバル変数を使わずに、引数でデータを受け渡す設計にしてください」と指示することで、結合度の低い、独立性の高い関数を作成させることができます。
戻り値(return)の活用と「副作用」の分離
引数が関数の入り口だとすれば、「戻り値(もどりち)」は出口です。関数内での処理結果を呼び出し元に返すためには return 文を使用します。例えば、return total; と記述すれば、その関数を呼び出した場所には total の値が返されます。これにより、const tax = calculateTax(price); のように、関数の実行結果を変数に格納して後続の処理に利用することが可能になります。
初心者がよく混同するのが、「ログ出力(Logger.log)」と「戻り値(return)」です。ログ出力は開発者が確認するための画面表示であり、プログラム的なデータの受け渡しではありません。一方、戻り値はプログラム内でデータとして活用されるものです。関数を部品として機能させるためには、計算結果や処理結果を必ず return で返す必要があります。
さらに、高度な設計概念として「副作用(Side Effects)」の理解が必要です。副作用とは、関数が自身のスコープ外の状態を変更すること(例:スプレッドシートへの書き込み、グローバル変数の変更、メール送信など)を指します。副作用のない関数、つまり「同じ引数を与えれば常に同じ戻り値を返し、外部に一切影響を与えない関数」を「純粋関数(Pure Function)」と呼びます。
純粋関数はテストが容易で、バグが発生しにくいため、リファクタリングでは可能な限りロジック部分を純粋関数として切り出すことが推奨されます。例えば、「スプレッドシートからデータを読み込み、計算して、書き込む」という処理なら、「計算する」部分だけを純粋関数として独立させます。そうすれば、その計算ロジックはスプレッドシートに依存せず、どこでも再利用可能になります。AIに「計算ロジックを純粋関数として切り出して」と指示することは、コード品質を高めるための非常に有効なプロンプトです。
JSDocコメントの威力:未来の自分とチームへの手紙
自分以外の開発者、あるいは未来の自分がコードを読んだ時、その関数が何をするものなのか、引数には何を渡せばいいのか、戻り値は何なのかを即座に理解できるようにするために、「JSDoc」という標準的なコメント形式を活用しましょう。GASのエディタはJSDocに対応しており、適切に記述することで、関数を使用する際にツールチップで説明が表示されるようになります。これは開発効率を劇的に向上させます。
JSDocは /** で始まり */ で終わるコメントブロックの中に記述します。主要なタグには以下のようなものがあります。
• @param {型} 名前 説明: 引数の型、名前、説明を定義します。
• @return {型} 説明: 戻り値の型と説明を定義します。
• @customfunction: スプレッドシートのカスタム関数として使用する場合に記述します(後述)。
例えば、消費税計算関数であれば以下のように記述します。
/**
* 金額と税率を受け取り、税込金額を計算します。
* @param {number} price 税抜金額
* @param {number} rate 税率(例: 0.1)
* @return {number} 税込金額
*/
function calculateTax(price, rate) { ... }
このように記述しておくと、別の場所で calculateTax と入力した瞬間に、エディタが自動的に引数のヒントを表示してくれます。これは、チーム開発において「使い方のマニュアル」をコードの中に埋め込むようなものです。AIにコードを書かせる際も、「JSDocコメントを含めてください」と指示するだけで、詳細なドキュメント付きのコードが生成されます。これはプロフェッショナルな開発者として必須のマナーと言えるでしょう。
実践リファクタリング1:モノリシックなコードの分解
それでは、具体的なリファクタリングの手順を見ていきましょう。例えば、これまでの章で作成してきたような、データの取得、加工、判定、書き込み、メール送信などが一塊になった数百行のコードがあるとします。これを「関数の抽出(Extract Function)」という手法を使って整理します。
まず、コード全体を眺めて「意味のあるまとまり」を見つけます。
1. スプレッドシートからのデータ取得部分
2. データのフィルタリングや加工部分
3. 条件に応じたメール文面の生成部分
4. メール送信部分
5. 処理結果の書き込み部分
それぞれの部分を独立した関数として定義し、メイン関数から呼び出す形に書き換えます。例えば、メール文面の生成部分は createEmailBody(data) という関数にし、引数としてデータを受け取り、完成した本文を戻り値として返すようにします。
この作業をAIとともに行う場合、元のコードをAIに提示し、「このコードを機能ごとに関数に分割し、メイン関数ですっきりと呼び出す形にリファクタリングしてください。各関数にはJSDocを付与してください」と指示します。AIはロジックの依存関係を解析し、適切な引数と戻り値を設計してくれます。
分割されたコードは、それぞれの関数が短くシンプルになるため、バグがあった場合の特定が容易になります。「メールの文面がおかしい」なら createEmailBody だけを見ればよく、「送信されない」なら送信関数を見ればよいのです。この「関数の責任分界点」を明確にすることが、リファクタリングの最大の目的です。
実践リファクタリング2:共通処理の関数化と再利用
リファクタリングを進めていくと、複数のスクリプトファイルで似たような処理を行っていることに気づくことがあります。例えば、「最終行を取得してデータを追加する処理」や「特定の日付フォーマットに変換する処理」などです。これらを「ユーティリティ関数」としてまとめ、プロジェクト内のどこからでも呼び出せるようにしましょう。
GASでは、同一プロジェクト内の別の .gs ファイルに記述された関数は、特別なインポート作業なしに呼び出すことができます。そこで、utils.gs というファイルを作成し、そこに汎用的な関数を集約することをお勧めします。
例えば、日付のフォーマット変換は頻出処理ですが、毎回 Utilities.formatDate(...) を書くのは面倒ですし、タイムゾーンの指定ミスなども起こり得ます。そこで、以下のようなラッパー関数を作ります。
/**
* 日付オブジェクトを指定の形式の文字列に変換します。
* @param {Date} date 変換する日付
* @param {string} format フォーマット(デフォルトは 'yyyy/MM/dd')
* @return {string} フォーマットされた日付文字列
*/
function formatDateString(date, format = 'yyyy/MM/dd') {
return Utilities.formatDate(date, Session.getScriptTimeZone(), format);
}
このように共通化しておけば、プロジェクト全体で日付処理の統一が図れます。また、修正が必要になった場合も utils.gs だけを直せば済みます。これを「DRY原則(Don’t Repeat Yourself:繰り返しを避ける)」の実践と呼びます。AIに対して「この処理は汎用的なので、独立したユーティリティ関数にしてください」と指示することで、再利用性の高いコード資産を蓄積していくことができます。
スプレッドシートのセルで動く「カスタム関数」の作成
GASで自作した関数は、実はスプレッドシートのセルから直接、Excel関数(SUMやVLOOKUPなど)と同じように呼び出して使うことができます。これを「カスタム関数」と呼びます。リファクタリングによって純粋関数(副作用のない計算ロジック)を切り出しておけば、それをそのままカスタム関数として利用できるケースが多くあります。
カスタム関数を作るためのルールはシンプルです。
1. スクリプトエディタで関数を定義する。
2. 関数名の前にJSDocで @customfunction タグをつける(推奨)。
3. スプレッドシートのセルで =MY_FUNCTION(A1) のように入力する。
例えば、複雑な条件で送料を計算するロジックをGASで calculateShipping(region, weight) として定義しておけば、スプレッドシート上では =calculateShipping(A2, B2) と書くだけで計算結果が表示されます。数式が複雑になりすぎて解読不能になる「Excel方眼紙」問題を解決する強力な手段です。
ただし、カスタム関数にはいくつかの制約があります。
• setValue や Generic などの、スプレッドシートの値を書き換えるメソッドは使用できません(計算して値を返すことしかできない)。
• 実行時間に30秒の制限があります。
• 関数名は慣例として大文字とアンダースコア(例:CALCULATE_TAX)を使うことが推奨されますが、必須ではありません。
AIに「この計算ロジックをスプレッドシートのカスタム関数として使えるように調整してください」と依頼すれば、JSDocを含めた適切な形式でコードを出力してくれます。これにより、GASの活用範囲は「裏側の自動化」から「表計算の拡張」へと広がります。
開発者としての品質基準:可読性と保守性の追求
本章の最後に、改めて「開発者」としてのマインドセットについて触れます。市民開発者が増える中で、プロフェッショナルな開発者(あるいはそれを目指す者)を分ける決定的な要素は、「コードの品質」へのこだわりです。動くコードを書くことは誰にでもできますが、「読みやすく、修正しやすく、壊れにくいコード」を書くことは技術です。
リファクタリングは、一見すると生産性を生まない時間の浪費に見えるかもしれません。しかし、長期的な視点で見れば、開発速度を維持し、バグによる手戻りを防ぐための最も効率的な手段です。関数を小さく保つ、引数と戻り値を明確にする、適切なコメントを残す、共通処理をまとめる。これらの習慣は、GASに限らずあらゆるプログラミング言語に通用する普遍的なスキルです。
また、AI時代においては、AIに「どのようなコードを書かせるか」というディレクション能力が問われます。あなたがリファクタリングの知識を持っていれば、AIが出力した荒削りなコードに対して「ここは関数に切り出して」「この変数は引数で渡して」と修正を指示し、品質を高めることができます。AIは優秀なコーダーですが、優れたアーキテクト(設計者)になれるかどうかは、使い手であるあなたの指示にかかっています。
整理整頓されたコードは美しく、読む人に安心感を与えます。あなたの書くコードが、自分だけでなくチーム全体の資産となるよう、本章で学んだリファクタリング技術を日々の開発に取り入れていってください。次章では、この整理されたコードを基盤として、さらに高度な外部サービスとの連携に挑戦していきます。
