構造化言語における関数分割の設計課題と手法
~業務フロー、データフローからエレガントな関数木を作成する~
Copyright © 2025 LWP 山中 一弘 本資料は、出典を明記いただければ、商用・非商用を問わず、ご自由に複製・改変・再配布していただけます。なお、著作権表示は改変せず、そのまま記載してご利用くださいますようお願いいたします。
第1章 前提知識と導入
1.1 業務システムとは何か
業務システムとは、企業や組織における日常的な業務(販売・在庫管理・給与計算など)を支援・自動化するための仕組みであり、情報処理を目的としたソフトウェアおよびその運用環境を指す。こうしたシステムは、主に「データの収集」「処理」「保存」「出力」という一連の流れで構成されており、業務における意思決定や業務遂行を効率化する役割を担っている。
このとき重要なのは、「業務の本質はデータの変化」であるという視点である。たとえば、受注が発生すれば「受注データ」が新たに生成され、商品が出荷されれば「在庫データ」が更新される。つまり、業務とはデータの状態遷移の積み重ねであり、業務システムとはその変化を正確に記録・管理・報告するための道具に他ならない。
1.2 構造化プログラミングとVBAの基礎
構造化プログラミングとは、プログラムを「順次実行」「分岐」「繰り返し」という3つの基本構造に則って記述する手法である。これにより、スパゲッティコードと呼ばれる制御の流れが複雑に入り組んだプログラムを避け、可読性・保守性の高いコードを書くことが可能になる。
VBA(Visual Basic for Applications)は、ExcelなどのMicrosoft Office製品に組み込まれたマクロ言語であり、この構造化プログラミングの考え方に基づいて記述される。VBAは「手続き型」の性質が強く、処理の流れを上から下へと順に実行する中で、必要に応じて関数やサブルーチンに処理を分割する。
たとえば、データの読み込み・集計・出力という一連の処理は、それぞれを独立した手続き(SubやFunction)に分けることで、全体の構造が明確になり、後からの修正や再利用も容易になる。
1.3 本書で扱う「関数分割」とは
本書では、構造化言語(特にVBA)を用いて業務システムを設計・実装する際に発生する「関数の分け方の問題」に焦点を当てる。
関数分割とは、ひとつの大きな処理を意味のある小さな処理単位に分けることであり、これにより以下のような効果が得られる:
・処理の見通しが良くなり、コード全体が読みやすくなる
・各処理の責務が明確になり、バグの原因箇所を特定しやすくなる
・同じ処理を別の場所でも使い回すことができる(再利用性)
しかしながら、実際の業務処理は「データがどう変化するか」という観点で進むのに対し、構造化プログラミングでは「処理の流れ」によってコードを記述する。この非対称性が、関数をどこで・どのように分けるべきかという設計上のジレンマを生む原因となっている。
本書はこの設計上の難しさを、実務に近い例や設計技法を通して丁寧に解説していく。
第2章 関数分割の役割と難しさ
2.1 なぜ関数に分けるのか
プログラムの処理を関数に分けることには、いくつかの重要な目的がある。第一に、処理のまとまりを明確にし、プログラムの全体像を把握しやすくするという効果がある。業務システムのように、処理の手順が多岐にわたる場合、すべてを1つの長大なコードとして記述すると、どこで何が行われているかを理解するのが難しくなる。関数に分けることで、処理の構造が視覚的・論理的に整理される。
第二に、関数単位での動作確認や修正が可能になる。バグの発見や修正は、関数が小さく、責任が明確であるほど行いやすい。また、ある関数を他の処理でも再利用できるようになれば、同じ処理を繰り返し書く必要がなくなり、保守性と効率が向上する。
第三に、複数人での開発や将来的な改修を見据えた設計としても、関数分割は重要である。誰が見ても処理の意図と範囲が明確になるように関数を分けておくことは、ソフトウェアの品質を支える基本的な作法である。
2.2 手続き言語における関数の設計単位
手続き型言語(VBA、C、Pascalなど)における関数とは、ある特定の処理手順をまとめたものである。これは、「何をするか(操作)」のまとまりを表現する手段であり、「何に対してするか(データ)」との結びつきは比較的弱い傾向がある。たとえば、「ファイルを開く」「データを集計する」「結果を出力する」など、処理内容が設計の基準になる。
そのため、関数の設計は往々にして「処理順」や「作業手順」に従ってなされがちであり、実際の業務に存在する意味構造とは乖離する場合がある。特に、ひとつの業務の中に複数の「操作対象(エンティティ)」が存在する場合、手続き言語では処理の都合でそれらを横断的に扱うことになりやすく、結果として関数の責務が曖昧になったり、再利用しにくい構造になったりする。
2.3 業務とコードのズレが起きる理由
業務の構造は「データの状態がどのように変化していくか」という視点で捉えるのが自然である。たとえば、「注文を受ける」「在庫を引き当てる」「出荷する」といった一連の業務は、それぞれのデータ(注文・在庫・出荷)の状態遷移として把握される。
しかし手続き型のプログラムでは、こうした状態遷移は「処理の流れ」の中で暗黙的に記述されることが多く、コード上では状態そのものが明示されない。代わりに、変数の値変更やフラグの立て方などで間接的に表現されるため、業務の流れとコードの構造が一致しづらい。
このズレは、関数をどの単位で分けるかという判断を曇らせる原因となる。処理手順を基準に関数を設計すると、業務的には意味が一貫していない関数ができてしまう。一方で、業務単位で分けようとすると、関数間でデータの受け渡しや状態の共有が煩雑になり、逆に設計が複雑になるという問題が生じる。
このように、業務構造と手続き構造の非対称性が、関数分割における根源的な設計課題を生み出している。次章では、このズレをどのように補正し、設計判断を支えるかという観点から、構造変換における具体的なジレンマを考察していく。
第3章 業務はデータでできている
3.1 現実世界の業務=データの状態変化
業務とは、人やシステムが協働して、一定の目的を達成するための一連の処理である。そしてその処理の対象は、ほぼ例外なく「データ」である。たとえば、注文、在庫、請求、社員情報など、業務に登場するものはすべて、何らかのデータとして定義され、管理されている。
そして業務の進行とは、これらのデータがある状態から別の状態へと変化していく過程である。注文が「受付中」から「処理済」へ、在庫が「有効」から「引き当て済」へ、社員が「在職」から「退職」へと移行していく。このような状態変化の積み重ねが、業務そのものを構成していると言ってよい。
すなわち、業務とは「意味のあるデータの変化」であり、この構造を理解しないままコードを書き始めると、処理の断片ばかりが並ぶ実装になりやすい。
3.2 操作はデータに従う:流れと意味の一致
現実の業務では、処理の順序はデータの状態によって決まる。たとえば、「商品を出荷する」という操作は「出荷可能な在庫」がある場合にしか行われない。逆にいえば、「出荷する」という処理の前提には「在庫の状態確認」が存在し、それが成立しない限り処理は進まない。
つまり、処理はデータの条件によって発動されるのであり、「業務の流れ」は「データの状態変化の流れ」に他ならない。業務の本質は、「このデータがこういう状態になったら、次にこの操作が行われる」という一連の条件付き操作で構成されている。
このような考え方に立つと、操作(関数)は独立して存在するのではなく、「ある種のデータのある種の状態」に対して意味を持つものであることが分かる。操作とデータは切り離して設計すべきではなく、むしろ密接に結びついた形で構造化されるべきである。
3.3 業務フローとデータフローの例
ここで、簡単な例として「発注業務」を考える。業務の流れとしては、おおよそ次のようになる。
・発注依頼を登録する
・承認された依頼を元に発注書を作成する
・発注書に基づき、納品を受け入れる
・納品された品を在庫に計上する
・請求書を発行し、支払い処理を行う
このような業務フローは、外形的には一連の操作の列であるが、裏側で起きているのは「データの状態変化」である。たとえば、「依頼→承認済」「発注書→送付済」「納品→完了」「在庫→増加」など、データの状態が順次書き換えられていく。
一方、データフローという視点で見ると、「依頼データ」「発注データ」「納品データ」「在庫データ」「請求データ」という複数のエンティティが、それぞれの段階で処理され、次の工程に渡されていく。データは状態を持ち、操作はそれに従って実行され、また状態を更新する。この循環こそが、業務フローとデータフローの一致である。
業務の流れを理解するとは、単に操作の順番を知ることではなく、「どのデータがどの状態からどの状態へ変わるのか」を正確に把握することに他ならない。これを前提として初めて、意味のある関数設計が可能になる。
第4章 コードは手続きでできている
4.1 構造化言語の制御構造(順次・分岐・反復)
構造化プログラミングでは、あらゆる処理を「順次」「分岐」「反復」の3つの基本構造で記述することが基本とされている。この原則は、プログラムの可読性と保守性を高め、複雑な処理も整理された形で記述できるようにするものである。
・順次(Sequence):上から順に処理を実行する
・分岐(Selection):条件に応じて異なる処理を実行する(If文、Select Caseなど)
・反復(Iteration):同じ処理を繰り返す(For文、Do Whileなど)
これらの構造を組み合わせることで、あらゆるアルゴリズムを記述することができるとされており、CやPascal、そしてVBAもこの構造に基づいている。
4.2 処理単位=関数という設計スタイル
構造化言語においては、処理のまとまりを「関数」や「サブルーチン」として分割し、再利用可能な単位にまとめることが推奨される。この関数という単位は、通常「ある一連の手続きを遂行する」という観点から設計される。たとえば、「ファイルを読み込む」「データを検証する」「画面に表示する」といった具体的な処理が、関数という形で表現される。
この設計スタイルでは、関数は処理の流れの中で必要な手続きを「まとめる」役割を果たすが、その対象となるデータ構造や状態との関連性は、設計者の裁量に任される部分が大きい。関数が何をするかは明確でも、それが「何に対して」行われるかがコード上で一貫しているとは限らない。
結果として、処理の構造に従ってコードが組み立てられ、業務の意味構造やデータの状態遷移が見えにくくなることがある。
4.3 関数木と業務の乖離
関数木とは、上位の関数が下位の関数を呼び出すという階層構造でプログラム全体を整理する設計であり、トップダウン設計や構造化設計の基本概念でもある。理想的には、上位関数は業務的な機能を表し、下位関数はそれを支える詳細な処理や共通処理となる。
しかし、業務の構造がデータと状態の変化を中心に構成されているのに対し、関数木は処理の制御構造によって設計されるため、両者の間にズレが生じることがある。たとえば、「売上登録」という業務は、注文・在庫・請求といった複数のデータにまたがる処理であり、その中で状態遷移が密接に関係する。これを関数木に落とし込もうとすると、下位関数が複数の業務データにまたがる処理を持つか、逆に業務のまとまりが分解されてしまうことが多い。
また、抽象度の設計も難所となる。上位関数の役割を「業務機能」と捉えると、その下位に位置する処理がどこまで汎用的で、どこまで業務に特化すべきかの判断が設計者に委ねられる。結果として、再利用性と意味の一貫性の間でバランスを取る必要が生じ、関数木が美しく構成されるとは限らない。
このように、関数木という構造そのものが、業務の意味構造を自然に反映できるとは限らず、むしろ非対称性を浮き彫りにすることがある。次章では、この非対称性を前提とした構造変換上のジレンマを詳しく扱う。
第5章 業務から関数へ:構造変換の壁
5.1 業務の意味構造 vs コードの制御構造
業務は、本来「意味のあるデータの流れと状態変化」によって構成されている。たとえば「発注」という業務には、「依頼の受付」「承認」「発注書作成」「納品確認」「在庫更新」といった一連の段階があり、それぞれが具体的なデータの状態変化を伴う。これらの段階は人間の業務認識に即した「意味構造」として理解されている。
一方、構造化プログラミングにおけるコードは、「処理の順序」「条件」「繰り返し」といった制御構造に基づいて組み立てられる。そのため、コードはどうしても「実行される順序」や「分岐処理」に意識が向きやすく、業務の意味的なまとまりはその中に埋もれてしまいがちである。
このとき、業務上は自然なまとまりである一連の処理が、コード上では複数の関数に分断されたり、逆に複数の業務的意味を持つ処理が1つの関数に混在したりするなど、「意味」と「制御」の非対称性が問題となる。
5.2 関数をどう切るか?抽象度と責務の調整
関数を適切に分割するには、処理の抽象度と責務(役割)を明確にする必要がある。しかし、この判断は簡単ではない。抽象度が高すぎると、関数の内部が多くの意味を内包しすぎてしまい、何をしているのかが見えにくくなる。一方、抽象度が低すぎると、細かすぎる関数が大量に発生し、全体像を見通すのが困難になる。
また、1つの関数が複数のエンティティ(たとえば「注文」と「在庫」)に対して操作を行っている場合、その責務は不明瞭になりやすい。業務上は1つのまとまりであっても、設計上はそれをどこまで分け、どのように命名するかで構造の理解しやすさが大きく変わる。
・抽象度を統一する(同じレベルの概念で関数を揃える)
・責務が1つに絞られるように処理をまとめる
・関数名にその意味や文脈が現れるようにする
これらの判断基準に基づいて関数を切ることが、構造変換を成功させる鍵である。
5.3 状態の管理はなぜ難しいか
業務において重要なのは、データがどのような状態にあるかを把握し、それに応じて処理を分岐・選択することである。たとえば「未承認」の依頼には「発注書作成」を行ってはならず、「在庫切れ」の商品には「出荷」を指示してはならない。このように、状態は業務ルールの根幹をなす。
しかし構造化言語では、状態を明示的に記述する手段が乏しく、多くの場合「フラグ変数」「文字列」「数値コード」などの形で処理の流れに埋め込まれる。そのため、状態の遷移や条件がコード中で分散しやすく、全体として状態設計があいまいになってしまう。
また、状態管理が関数をまたいで必要になる場合、グローバル変数の使用や戻り値の継ぎはぎ的な運用が発生し、設計が一気に複雑になる。これにより、状態に基づく処理制御がブラックボックス化し、バグの温床となることも多い。
関数設計においては、「状態の保持と遷移の責務」を明確に分離し、関数が状態を読み取り、更新するという役割をきちんと設計することが重要である。状態という視点を見失わずに関数分割を進めることが、業務とコードの構造を一致させる第一歩となる。
第6章 トップダウン設計の反復と調整
6.1 まず業務全体を描いてみる
関数分割の前提として、まず取り組むべきは「業務全体の構造を俯瞰すること」である。個々の処理に目を向ける前に、どのような業務がどのような流れで行われ、どのデータがどのように変化していくかを把握する必要がある。これには、業務フロー図や状態遷移図、関係図といった手法が有効である。
たとえば、「顧客からの注文→受注処理→出荷→請求→入金確認」という流れを図に起こし、それぞれの段階で関わるデータ(注文、商品、在庫、請求書、支払情報など)と状態(新規、処理中、完了など)を明示する。これにより、業務の意味的なまとまりが視覚的に明らかになり、後の関数設計における判断の土台となる。
6.2 コードに落とす:一度目の分割
業務構造を把握したら、それをコードとして実装可能な構造に変換していく。これが最初の関数分割である。ここでは主に、「機能」や「画面」単位、「処理の目的」単位で関数を切ることが多い。
たとえば、「受注処理」という機能に対しては「入力チェックを行う」「受注明細を登録する」「在庫を引き当てる」といった処理単位があり、それぞれを関数として切り出す。この段階では、処理手順に沿った比較的素直な分割となることが多く、制御構造の視点が優先されやすい。
ただし、この段階ではまだ責務が重複していたり、関数同士の境界があいまいだったりする場合が多いため、これで設計が完了とはならない。
6.3 意味を整理する:二度目の調整
一度目の関数分割をもとに試作や実装を進めると、処理の責務やデータの流れに矛盾や重複が見えてくる。ここで行うのが「意味に基づく再整理」である。たとえば、「在庫更新」という処理が複数の関数で繰り返し出てくる場合、それは共通化すべき対象かもしれないし、逆に業務上の意味が異なるので分離すべきかもしれない。
・この関数は何のために存在するのか?
・他の関数とどこで重なっているのか?
・処理の対象となるデータは何か?
こうした問いをもとに、業務の意味構造とコードの構造を再度照らし合わせる。結果として、関数名を変更したり、関数を分離・統合したりといった調整が行われる。この段階が、コードに意味を吹き込む工程と言える。
6.4 三度目でようやくまとまる理由
トップダウン設計は一回で終わるものではない。二度目の整理を経てもなお、細部の実装や運用上の要請によって、関数構成に修正が必要になることは多い。たとえば、データの受け渡し方法の変更、エラー処理の追加、外部システムとの連携といった要素が後から加わることで、再び設計に手を入れる必要が出てくる。
このとき行われる第三の調整では、「抽象度」「粒度」「再利用性」「保守性」といった観点で最終的なバランスを取ることが目指される。単に処理が正しく分かれているだけでなく、「変更に強く、意味が一貫していて、チームでも理解しやすい構造」に仕上げることが求められる。
トップダウン設計が3回繰り返される理由は、業務→処理→コードという構造変換の過程で、複数の視点と要請が段階的に統合されていくからである。関数分割とは、単なる分け方の話ではなく、意味と構造をすり合わせる思考のプロセスそのものである。
第7章 データを中心とした関数設計
7.1 構造体・配列・辞書:VBAのデータ構造
VBAでは、複数のデータをひとまとまりに扱うために、さまざまなデータ構造が用意されている。なかでも代表的なのが、構造体(ユーザー定義型)、配列、そしてDictionary(連想配列)である。
・構造体(Type)は、異なる型のデータを1つの論理単位として扱える。たとえば「受注」や「顧客」といったエンティティを表すのに適している(ただし、構造体は参照が取れないのでデータコンテナに格納できず(具象構造体配列を除く)、使用できるシーンは限定的になる。代わりにデータクラスを使用するとよい)。
・配列は、同一型のデータの集まりを扱うのに向いており、集計や繰り返し処理に強い。
・Dictionaryは、キーと値のペアでデータを格納する柔軟な構造であり、項目名で値を参照できる点が業務処理に向いている。
これらの構造を適切に使い分けることで、「何を処理しているのか」をコード上に明示しやすくなり、関数設計にも自然な枠組みが生まれる。
7.2 データに合わせて関数を作る
関数を「処理の手順」に合わせて作るのではなく、「対象となるデータ構造」に合わせて作るという発想は、関数の責務を明確にし、再利用性を高めるうえで有効である。たとえば、顧客情報を登録する関数、顧客情報を表示する関数、顧客情報を検証する関数といったように、関数の粒度と命名を「対象データと操作の組み合わせ」に基づいて定める。
このアプローチでは、関数群が自然と「あるデータ型(エンティティ)に紐づく処理セット」として整理されるため、処理の意味や流れが分かりやすくなる。また、複数の業務にわたって同じデータ構造を共有する場合にも、処理の共通化がしやすい。
データの意味単位で関数を構成することで、コードは業務構造に近づき、保守やテストの単位も合理化される。
7.3 VBAでもできる“オブジェクトっぽい”設計
VBAは純粋なオブジェクト指向言語ではないが、Classモジュールを用いることで“オブジェクトっぽい”設計を行うことができる。たとえば、「顧客」というデータ構造に対して、clsCustomerというクラスを定義し、その中に登録・更新・出力といったメソッド(Sub/Function)を持たせる設計が可能である。
このように、データとそれに関連する操作を1つの単位にまとめることで、自然なカプセル化が実現される。これにより、関数がどのデータに関係するのかが一目で分かるようになり、他の処理との結合度も低くなる。
・クラスを使うことで、状態(プロパティ)と振る舞い(メソッド)を一元的に管理できる
・業務エンティティごとに責務が分離され、役割の明確な構造になる
・フォームとの連携やイベント処理にも柔軟に対応できる
このような“疑似OOP”的手法は、VBAにおける関数設計の発展形であり、構造化言語であっても意味志向のコードを実現する有力なアプローチである。データを中心とした設計を実践するためには、VBAの持つ構造的な制限を理解しつつも、データと処理の結びつきを強める発想が欠かせない。
ただし、VBAにおけるクラス機能は限定的であり、オブジェクト指向の主要な利点を十分に活かすことは難しい。また、クラス設計には構造化言語とは異なる方法論が求められるため、全面的な導入には慎重を要する。現時点では、クラスは「データクラス(エンティティ)」として定義し、コンテナに登録する用途にとどめることを推奨する。
第8章 やりがちな失敗とその回避法
8.1 処理順に切っただけの関数
初心者から中級者にかけてよく見られるのが、「処理の流れに従って機械的に関数を分ける」というスタイルである。たとえば、「Step1_データ読込」「Step2_検証」「Step3_出力」といった具合に、業務の手順そのままを関数名として記述する例がある。
一見、段取りが明確に見えるが、こうした関数は「何を対象に、どのような意味で処理しているのか」が分かりにくく、関数の中身を見ないと本質的な役割が読み取れないという問題を抱える。また、再利用性や独立性にも欠けるため、他の場面で流用しにくく、メンテナンス時の見通しも悪くなる。
回避策としては、「処理順」ではなく「対象データと操作」に基づいて関数を定義することが重要である。たとえば「受注データの検証」「在庫の引き当て」といった命名・分割を行えば、意味と構造が一致しやすくなり、関数が独立した役割を持ちやすくなる。
8.2 なんでもできるユーティリティ関数
次にありがちな失敗は、「便利そうだから」という理由で多くの処理を1つの関数に詰め込んでしまうこと、いわゆるユーティリティ関数の肥大化である。たとえば「WriteLog」という関数にログ出力だけでなく、メッセージボックス表示やファイル保存まで含めてしまうと、その関数はもはや何を目的にしているのか不明瞭になる。
このような関数は、使い回しは効いても責務が不明確であり、ある変更が他の機能に影響を与えるリスクも高い。特に、関数内部で複数のエンティティや状態を扱うようになると、バグの温床となりやすい。
対策としては、「1関数1責務(Single Responsibility Principle)」を意識することが基本である。目的の異なる処理は明確に分割し、必要があれば複数の小さな関数を組み合わせて機能を実現する。関数は“何ができるか”よりも“何のためにあるか”が重要である。
8.3 意味が薄い名前と再利用不能な構造
関数名が「Sub1」「FuncA」「ProcessX」などのように意味を持たない記号的な命名になっているケースでは、その関数の役割がコードを読まないと理解できない。このような曖昧な名前は、構造の再利用を妨げるだけでなく、コード全体の理解を困難にする。
また、関数の中で特定のシート名、特定のセル範囲、特定の処理順に強く依存している場合、その関数は他の文脈ではほとんど使えず、「一度きりの処理」に埋没してしまう。これは再利用性だけでなく、テストや保守の観点からも望ましくない。
回避するには、命名に業務用語やデータ構造の名称を含めることが有効である。たとえば「CheckOrderData」「ExportToCsv」「UpdateInventoryStatus」など、動詞+対象+目的語の形式を取ることで、関数の意図と文脈が明確になる。
また、関数内では具体的なシート名やセル参照を避け、引数として渡すことで構造の汎用性を高める。意味と汎用性を両立させた関数は、業務システムの長期的な品質と柔軟性を支える重要な構成要素となる。
第9章 現場ではどう対応しているのか
9.1 OSSプロジェクトの構造化例(Apacheなど)
オープンソースソフトウェア(OSS)の世界では、構造化言語を用いながらも高度な設計手法によって保守性・拡張性を確保しているプロジェクトが多数存在する。その代表例の一つがApache HTTP ServerなどのC言語ベースのプロジェクトである。
こうしたプロジェクトでは、処理の流れに沿った単純な関数分割ではなく、「構造体を中心に関数をグルーピングする」という擬似オブジェクト指向的な設計が一般的である。たとえば、HTTPリクエストに関する処理群はrequest_recという構造体を中心に関数がまとまり、データと処理の対応関係が一目で分かるようになっている。
このように、「処理」ではなく「データ」を軸にした構造化が、複雑な業務ロジックや状態制御を効率よく整理する鍵となっている。
9.2 構造体中心の関数設計
関数設計を構造体中心で行うとは、「あるデータ構造に属する処理を、その構造に寄せて整理する」ことを意味する。具体的には、構造体の第1引数としてその構造自体(またはポインタや参照)を渡し、その内部の値に基づいて処理を行う関数群を定義する形になる。
たとえば「顧客(Customer)」構造体があれば、それに対応する関数として「Customer_Validate」「Customer_Save」「Customer_Update」などを定義する。このとき関数は、構造体のフィールドに直接アクセスしながら、責務に応じた処理を行う。
このスタイルでは、関数のグルーピングが自然に「データの単位」に沿って行われるため、保守性・再利用性に優れる。関数名の命名規則にも一貫性が出るため、開発者間での理解も早く、コードの探索やナビゲーションも容易になる。
9.3 状態を含めた設計で保守性を高める
現場での関数設計において、しばしば見落とされがちなのが「状態の明示的な扱い」である。多くの業務は状態遷移を前提に成り立っており、それに応じて処理内容や条件が変化する。たとえば「出荷可能かどうか」は在庫の状態に依存し、「支払い処理」ができるかどうかは請求の状態に依存する。
このような状態を、単なるフラグや文字列としてコード中に散らすのではなく、設計段階で明示的に扱うことが重要である。たとえば、状態は列挙型(Enum)で定義し、状態ごとに処理を分岐させる関数を用意することで、コードの意味が明確になると同時に、バグの混入も防ぎやすくなる。
また、関数が状態を更新する責務を持つ場合は、状態変更前後の整合性や業務ルールの遵守を関数内部でチェックするなど、業務制御の厳格さを担保することができる。
結果として、状態を明示的に設計に取り込むことで、システムの保守性は飛躍的に向上する。特に、業務が複雑化しやすい中〜大規模なVBAシステムでは、このような構造的工夫がシステム全体の健全性を左右する重要な要素となる。
第10章 よりよい設計を支える技法と考え方
10.1 二名法(分類学的命名)の考え方
関数の命名に一貫性と意味を持たせるための有効な方法として、「分類学的命名(Binomial Naming)」、いわゆる二名法の考え方がある。これは生物分類における「属名+種名」のように、「対象+操作」あるいは「操作+対象」という形式で関数名を構成する手法である。
たとえば「顧客データを検証する」関数であれば、「ValidateCustomer」や「Customer_Validate」といった命名になる。重要なのは、関数が何に対して、どのような操作をするのかが名前から明確に分かることである。
このスタイルを一貫して適用すれば、関数の役割や位置づけが名前だけで把握でき、メンテナンス性やチーム内の共有理解が飛躍的に高まる。関数はコードの入口であり、言葉による構造整理でもある。設計は命名に始まり、命名で決まるとも言える。
10.2 ドメイン駆動設計(DDD)のさわりだけ
ドメイン駆動設計(Domain-Driven Design:DDD)は、業務の意味構造を中心にシステム全体を設計するアプローチである。その核となる考え方は、「ソフトウェアの構造を業務知識(ドメイン)に密着させる」という点にある。
DDDでは、エンティティ、値オブジェクト、サービスなどの概念を用いて、現実世界の業務とシステム内の構造を対応づける。VBAのような構造化言語でも、この考え方の一部は応用可能である。たとえば、業務エンティティごとに専用の関数群やクラスモジュールを設けることで、操作とデータを一体化させ、意味に基づく構造を実現できる。
業務の言葉でコードを書くという姿勢は、関数設計にも大きな影響を与える。たとえ本格的なDDDでなくとも、ドメインに忠実な構造を目指す姿勢が、設計品質を根本から支えることになる(ただし、DDDをVBAマクロに全面的に適用しようとすると、いわば“牛刀で鶏をさばく”状態となり、かえって生産性を損なう可能性がある点には留意が必要である)。
10.3 Jackson Structured Programming(概要)
Jackson Structured Programming(JSP)は、Michael A. Jacksonによって提案された設計技法であり、業務データの構造をもとにプログラム構造を構築するという点で、非常に実務的かつデータ志向の設計法である。
JSPではまず、対象となるデータの論理構造を「構造図」として表現し、それに対応する制御構造(順次、選択、反復)を明確にしたうえで、プログラム構造を生成する。これにより、データと処理の対応関係がはっきりし、意味論的な整合性の高いコードが実現される。
構造化言語での関数設計においても、このアプローチは応用可能である。データの流れや繰り返し構造をもとに関数を設計すれば、業務の実態に即した、破綻の少ないコード構成が得られる。特に、帳票出力やCSV処理など、データ構造が強く影響する場面では、JSPの視点が有効に機能する。データ構造を関数木へと変換するJSPは、関数木の上流構造に対してではなく、データ構造の解析と処理を担う下層レベルでの適用に効果を発揮する。
10.4 VBAでクラスを使う簡単な例
VBAにはClassモジュールという機能があり、オブジェクト指向の一部を実装することができる。たとえば「受注」という業務データを表すclsOrderというクラスを定義し、その中にValidate・Save・PrintReportといったメソッドを持たせることができる。
クラスを使うメリットは次のとおりである。
・状態(プロパティ)と振る舞い(メソッド)を一体で管理できる
・関数群を目的別にグルーピングできる
・他のエンティティと明確に切り分けられる
VBAでは「Set obj = New clsOrder」としてインスタンスを生成し、obj.Validateのようにメソッドを呼び出す。これにより、従来の手続き型に比べてデータと操作の関係が明確になり、構造も自然と整理されていく。
ただし、すでに述べたとおり、VBAには独特の制限があり、学習面でも難易度が高いため、全面的な採用には慎重を期すべきである。
第11章 まとめ:意味と構造の一致を目指して
11.1 業務から始め、構造を設計する
関数設計は、単に処理を分けることではなく、「業務の意味をどのようにコードに反映させるか」という本質的な設計行為である。プログラムを書く前に、まず業務とは何かを把握し、どのようなデータが存在し、どういう状態遷移が起こるのかを明らかにする必要がある。
業務を抽象的な操作やステップではなく、「意味あるデータの変化」として捉え、それを前提としてコード構造を考えることが、設計の出発点となる。この順序を誤ると、たとえ見た目に整った関数構成でも、業務から見たときに意味をなさない、使いにくいコードになってしまう。
関数分割は、業務理解をもとに行うべき設計作業である。
11.2 トップダウン+反復で精度を上げる
意味のある関数構造は、いきなり完成するものではない。業務全体を俯瞰し、仮の関数構成を作り、それを実装しながら調整していく。こうしたトップダウン設計と実装の往復、すなわち「反復的設計プロセス」こそが、実践における精度の高い関数設計を支える。
1回目の分割では処理順中心の構成となることが多く、2回目で意味を整理し、3回目で抽象度や再利用性を調整する。この3段階の試行錯誤を通して、ようやく「処理として正しく、意味としても自然」な構造に仕上がる。
この反復プロセスは、VBAのような構造化言語でも有効であり、むしろ構造的な制約があるからこそ、意味とのズレを丁寧に調整していく設計的姿勢が求められる。
11.3 保守性と可読性を両立する設計とは
実用的なシステム設計においては、単に正しいコードを書くこと以上に、「読みやすく、直しやすい」ことが求められる。関数設計はその中心的な手段であり、意味の一貫性、命名の明確さ、責務の分離が揃って初めて、保守性と可読性を両立できる。
・1つの関数が1つの責務を持つ(単一責任原則)
・関数名が操作と対象の関係を明示する(二名法)
・状態やデータ構造に即した設計と処理の配置
これらを実現するには、業務知識・設計技術・命名力といったスキルの総合力が求められる。特に、コードが業務文脈を失わないように設計することで、変更や拡張が生じたときにも柔軟に対応できる。
関数設計とは、単なる技術作業ではなく、「業務と構造をつなぐ翻訳作業」である。意味と構造が一致したコードは、使いやすく、保ちやすく、育てやすい。それこそが、本書を通して追求してきた関数設計の核心である。
付録
A.1 本書で登場した用語とその解説
・業務構造:現実の業務の流れやデータの状態変化を整理・体系化した構造。エンティティとその状態遷移から構成される。
・構造化言語:処理を順次・分岐・反復の3基本構造で記述するプログラミング言語。VBA、C、Pascalなど。
・関数分割:大きな処理を意味のある小さな処理単位に分けること。保守性・可読性・再利用性向上が目的。
・意味論的凝集:関数やモジュールが、1つの明確な目的や意味を中心に構成されている状態。
・関数木:関数同士の呼び出し関係を階層構造として捉えたもの。トップダウン設計に用いられる。
・構造体:複数の異なる型のデータをひとまとめに扱うユーザー定義型。VBAではTypeで定義。
・二名法(分類学的命名):関数名を「操作+対象」または「対象+操作」で命名する手法。
・トップダウン設計:全体を俯瞰した大枠から、段階的に詳細に落とし込む設計スタイル。
・ドメイン駆動設計(DDD):業務知識(ドメイン)を軸にシステムを構造化する設計手法。
・Jackson Structured Programming(JSP):業務データの構造を出発点にプログラム構造を設計する技法。
・疑似OOP:構造化言語において、構造体と関数をセットにし、オブジェクト指向的な設計を行う手法。
・単一責任原則(SRP):1つの関数やモジュールは、1つの目的または変更理由のみに責任を持つべきとする原則。
A.2 もっと学びたい人のための参考文献
・P.J. Plauger『Programming on Purpose(邦題:プログラミングの壺Iソフトウェア設計編)』
・Steve McConnell『Code Complete(邦題:コードコンプリート)』
・飯泉純子、大槻繁『ずっと受けたかったソフトウエア設計の授業』
・Brian W.Kernighan『The Elements of Programming Style(邦題:プログラミング作法)』
・Eric Evans『Domain-Driven Design(邦題:ドメイン駆動設計)』
