こんにちは!VBAの基礎をマスターして、次のステージへ進もうとしているあなたへ。
これまで「マクロの記録」を使ったり、ネットで見つけたコードをコピペして何とか動かしたり……そんな時期を乗り越えて、「自分でゼロからコードを書いてみたい!」という熱意を持っている頃ではないでしょうか。
でも、実際に書き進めると、こんな壁にぶつかりませんか?
> 「気づいたら、1つのSubの中に100行も200行もコードが書かれてしまっている……」
> 「どこを直したら動くのか、自分でも分からなくなってきた……」
> 「変数があちこちで使い回されていて、頭がパンクしそう!」
これ、VBAを学び始めた人が全員通る「巨大スパゲッティコードの迷宮」です。
今日は、ここから抜け出し、プロのエンジニアも実践している「プロシージャの分割」と「単一責任の原則」についてお話しします。ここをクリアすれば、あなたの書くVBAコードは見違えるほど美しく、エラーに強くなりますよ。
—
1. なぜ「20行の壁」なのか?(マクロの記録からの脱却)
初心者が書きがちなコードは、上から下へ、すべての処理が1つのプロシージャにベタ書きされています。
- シートを開いて
- データをクリアして
- ループを回して計算して
- 条件分岐で色をつけて
- メッセージを出して保存する
これらをすべて `Sub Main() … End Sub` の中に書くとどうなるでしょう?
コードの縦幅が長くなりすぎて、全体像が把握できなくなります。これを私たちは「見通しの悪い密林」と呼んでいます。
プロの現場では、「1つのプロシージャは20行以内(画面スクロールせずに全体が見える範囲)に収める」という不文律があります。
なぜなら、人間の脳が一度に理解できる情報の単位には限界があるからです。20行という制限を設けることで、自然とコードが整理され、メンテナンスしやすい構造に生まれ変わります。
—
2. 「単一責任の原則」とは何か?
プロシージャを分割するときの絶対的な指針が「単一責任の原則(Single Responsibility Principle)」です。
難しく聞こえますが、要するにこういうことです:
> 「1つのプロシージャは、仕事場(役割)を1つだけ持て」
例えば、レストランの厨房を想像してください。
シェフが1人で「食材の買い出し」「調理」「皿洗い」「接客」すべてを同時にやっていたら、お店はパニックになりますよね。だから、それぞれの担当(責任)に分割されています。
VBAのコードもまったく同じです。
- 「データを取得する係」
- 「計算する係」
- 「シートに出力する係」
これらをきっちり分けること。これが単一責任の原則の本質なのです。
—
3. 【実例】スパゲッティコードを美しくリファクタリングする
百聞は一見に如かず。実際に「悪いコード」と「良いコード」を見比べてみましょう。
やりたいことはシンプルです。
「売上データシートからデータを読み込み、消費税を計算して、集計シートに書き出す」という処理です。
❌ 悪い例:すべてを詰め込んだ「モンスタープロシージャ」
Sub ProcessSalesData()
Dim wsRead As Worksheet
Dim wsWrite As Worksheet
Dim lastRow As Long
Dim i As Long
Dim subtotal As Double
Dim tax As Double
‘ 1. シートの準備
Set wsRead = ThisWorkbook.Sheets(“売上データ”)
Set wsWrite = ThisWorkbook.Sheets(“集計”)
‘ 2. 集計シートのクリア
wsWrite.Range(“A2:C1000”).Clear
‘ 3. 売上データを読み込んで計算して書き込み(ここに処理が集中!)
lastRow = wsRead.Cells(wsRead.Rows.Count, “A”).End(xlUp).Row
For i = 2 To lastRow
subtotal = wsRead.Cells(i, 2).Value
tax = subtotal 0.1
‘ 書き込み
wsWrite.Cells(i, 1).Value = wsRead.Cells(i, 1).Value ‘ ID
wsWrite.Cells(i, 2).Value = subtotal ‘ 小計
wsWrite.Cells(i, 3).Value = subtotal + tax ‘ 税込合計
Next i
‘ 4. 完了メッセージ
MsgBox “処理が完了しました!”, vbInformation
End Sub
動きますが……これ以上仕様変更(「やっぱり軽減税率8%の判定も入れて」など)があったら、どこを直せばいいか迷ってしまいますよね。
—
⭕ 良い例:役割ごとに分割された「チームワーク・コード」
それでは、このコードを「20行以内」&「単一責任の原則」に従って美しくリファクタリング(再構築)してみましょう。
‘ 【司令塔】全体の流れをコントロールするだけ(責任:全体の統括)
Sub MainProcessSales()
Dim wsRead As Worksheet, wsWrite As Worksheet
‘ シートの定義
Set wsRead = ThisWorkbook.Sheets(“売上データ”)
Set wsWrite = ThisWorkbook.Sheets(“集計”)
‘ 各専門プロシージャを呼び出す
ClearExistingData wsWrite
CalculateAndWriteData wsRead, wsWrite
MsgBox “処理が完了しました!”, vbInformation
End Sub
‘ 【専門家A】データをクリアする(責任:消去のみ)
Sub ClearExistingData(ByRef ws As Worksheet)
ws.Range(“A2:C1000”).Clear
End Sub
‘ 【専門家B】計算と書き込みを行う(責任:データ処理のみ)
Sub CalculateAndWriteData(ByRef wsRead As Worksheet, ByRef wsWrite As Worksheet)
Dim lastRow As Long
Dim i As Long
Dim subtotal As Double
lastRow = GetLastRow(wsRead, “A”)
For i = 2 To lastRow
subtotal = wsRead.Cells(i, 2).Value
‘ 書き込み処理をさらに細分化してもOKですが、今回はシンプルに
wsWrite.Cells(i, 1).Value = wsRead.Cells(i, 1).Value
wsWrite.Cells(i, 2).Value = subtotal
wsWrite.Cells(i, 3).Value = CalculateTaxIncluded(subtotal) ‘ 消費税計算を分離!
Next i
End Sub
‘ 【専門家C】税込み金額を計算する(責任:計算ロジックのみ)
Function CalculateTaxIncluded(ByVal subtotal As Double) As Double
‘ 10%の税込み計算をカプセル化(税率が変わってもここを変えるだけ!)
CalculateTaxIncluded = subtotal 1.1
End Function
‘ 【便利屋】最終行を取得する(責任:位置特定のみ)
Function GetLastRow(ByRef ws As Worksheet, ByRef colStr As String) As Long
GetLastRow = ws.Cells(ws.Rows.Count, colStr).End(xlUp).Row
End Function
どうでしょう?
メインの処理である `MainProcessSales` を見れば、「シートを準備して、クリアして、計算して、終わりのメッセージを出す」という全体像が、コードを読んだ瞬間に頭に入ってきますよね。
それぞれの細かい仕事は、下にある短いプロシージャ(サブルーチンやファンクション)に綺麗に任せられています。
—
4. プロシージャ分割の3大メリット
このようにコードをパーツに分けることで、あなたの開発ライフは劇的に変わります。
1. 「バグの犯人」がすぐ見つかる
計算結果がおかしいなら `CalculateTaxIncluded` や `CalculateAndWriteData` だけを見ればよく、シートの消去処理を疑う必要がなくなります。
2. 部品の「使い回し(再利用)」ができる
`GetLastRow` なんて、他のマクロでも100回使えますよね。一度書けば、コピペすらいらずに呼び出せます。
3. 仕様変更に圧倒的に強い
「消費税率が10%から変わるかも」と言われた時、良い例なら `CalculateTaxIncluded` の ` 1.1` を ` 1.08` に書き換える(あるいは新税率に対応させる)だけで終わりです。他のコードに影響を与えません。
—
5. 初心者が陥りやすい罠:変数のスコープ(通用範囲)
プロシージャを分けるとき、一つだけ注意すべきルールがあります。それが「変数のスコープ」です。
例えば、`Sub A` の中で宣言した変数は、`Sub B` の中では使うことができません(それぞれ別の部屋にいるようなものです)。
データを別のプロシージャに渡したいときは、「引数(ひきすう)」という仕組みを使います。
‘ 呼び出し元
Sub ParentProc()
Dim myName As String
myName = “VBAエンジニア”
‘ 子プロシージャに「myName」の値を渡す
ChildProc myName
End Sub
‘ 呼び出し先(受け取る側)
Sub ChildProc(ByVal nameVal As String)
MsgBox “こんにちは、” & nameVal & “さん!”
End Sub
「引数って難しそう……」と感じるかもしれませんが、「他の部屋に荷物をパスするパイプ役」だと思えば簡単です。最初は `ByRef`(参照渡し)や `ByVal`(値渡し)の厳密な違いで悩むよりも、「データをバトンタッチしているんだな」という感覚を掴むことが大切です。
—
まとめ:今日から実践できる一歩
いかがでしたか?
プロシージャの分割と「単一責任の原則」は、プログラミングを「苦行」から「パズルを解くような楽しさ」に変えてくれる魔法のスキルです。
- 1つのプロシージャは20行以内を意識する
- 「何をする係か」を1つに決める
- 長いコードを書きたくなったら、パーツに切り出す
ここをクリアすれば、あなたはもう「マクロの記録を貼り付けるだけの人」ではありません。立派なVBAアプリケーション・デザイナーです。
ぜひ、今手元にあるあなたのコードを開いて、「あ、ここ長すぎだな」と思うところをパーツに切り分けてみてください。その瞬間から、世界が変わって見えるはずですよ。それでは、開発の現場でお会いしましょう!
