プロシージャを分割したマクロを用意する方法を解説
VBEの中で1つのモジュールに複数のプロシージャが書かれている事があります。
1つのプロシージャで用意するコードをあえて分割しているケースです。
スキルとの関係で考えてみると中、上級者様が用意したマクロはプロシージャが分割されている傾向がありますね。
なぜこんなことをするのでしょうか?スキルがある方が取り組むという事はきっとメリットがあるはずです。
早速メリットを深堀りしていきましょう。今回は分かりやすく説明する為に基礎編と応用編に分けて解説していきます。
- 基礎編:プロシージャの分割とは
- 応用編:事例をもとにプロシージャの分割を考える
まずはプロシージャを分割するという作業を理解しその後に実際に作業をすることでメリットを体感してみます。
プロシージャを分割する事ができるとコードの開発力が上がります。
「値渡し」や「参照渡し」などを勉強する事はレベルアップに繋がります。
「値渡し」、「参照渡し」という新しいキーワードも出てきました。全てまとめて解説します。
応用編では事例を用意しました。具体的なコードをもとにマトリクスを使って検証します。
一連の検討プロセスを見ていく事で「なぜプロシージャの分割をするのか」が分かる様になりますよ。
関連記事
サブルーチンについて解説した記事です。サブルーチンについては以降で説明します。
サブルーチンVBAサブルーチンの考え方と作り方共通作業は別プロシージャに書く
関連書籍
プロシージャの分割には正解はなく用意するコードや人によって様々な形になる事が想定されます。
こちらの書籍はプロシージャの分割について分かり易い説明が掲載されています。
私は会社の机の上にこの本を置いています。アイデアをもらいたい時に使っています。
基礎編:プロシージャの分割とは
プロシージャの分割とは「サブルーチン」と同じ意味だと思って良いです。
以降Wikipediaから引用した文章をご覧ください。
繰り返し利用されるルーチン作業をモジュールとしてまとめたもので、呼び出す側の「主」となるもの(メインルーチン)と対比して「サブルーチン」と呼ばれる。
プログラムのソース中で、繰り返し現れる処理をサブルーチン化することで、可読性や保守性を高く保つことができる。繰り返し現れる処理でなくても、意味的なまとまりを示すためにサブルーチン化することもある
Wikipedia:サブルーチンから抜粋
なんらかの意図をもとに1つのプロシージャを分けた際のサブ側のコードを「サブルーチン」と呼んでいます。
サブルーチンには意味がある
引用の中では「繰り返し現れる処理」や「意味的なまとまり」をサブルーチン化すると記載があります。
サブルーチンとしてコード分ける際は決まった意図があるわけではないです。
裏を返すと色々なバリエーションがあるという事になり画一的な正解は無いです。
以降で1つの事例を紹介します。参考にしていただき皆様の仕事にあったサブルーチンを組んでみてください。
応用編:事例をもとにプロシージャの分割を考える
事例となるのは冒頭でも紹介したこちらの記事に掲載されているコードです。
繰り返し処理マクロで特定文字を含むシートを選択|For Each ~ Next & Like
リンク先には3つのプロシージャが用意されています。このプロシージャを1つにまとめます。
その後で3つのプロシージャの時と1つのプロシージャにまとめた時のメリット、デメリットをマトリクス化します。
3つのプロシージャを1つにまとめる
リンク先にある3つのプロシージャを1つのプロシージャにまとめました。
Sub 特定文字を含むシートを選択_全()
Dim wbk As Workbook
Dim sht As Worksheet
Dim cnt As Long
Set wbk = ActiveWorkbook
For Each sht In wbk.Worksheets
With sht
If .Name Like "*1" Then
.Range("A1").Interior.ColorIndex = 4
.Range("A1").Value = "みどり"
End If
If .Name Like "Sheet*" Then
cnt = cnt + 1
End If
If .Name Like "*2" Then
.Name = "2番目"
ElseIf .Name Like "*3" Then
.Name = "No.3"
End If
End With
Next
MsgBox "特定文字を含むシートは" & cnt & "枚です", vbInformation, "お知らせ"
End Sub
今回は1つのループ内に3つのプロシージャを並列につなぎました。結果は変わらないのでこの仕様にしました。
プロシージャ自体はそんなに長くないので問題はなさそうに感じますね。
しかし3つのプロシージャがそれぞれ100行程度のコードをもっていたらどうでしょうか?
非常に読みにくいプロシージャになる事が想像できます。
ではどうしたら良いのでしょうか。対策を考える為に一度現状を可視化してみましょう。マトリクスを用意します。
メリット、デメリットをマトリクス化する
プロシージャを1つにまとめた時と3つに分けた時のメリットとデメリットをマトリクスにしました。
どちらも優位性がありますが不利な点もあります。可視化したのですが対策が見えませんね。
プロシージャ | メリット | デメリット |
---|---|---|
1つ | 1ヶ所で完結するので理解しやすい | プロシージャが長くなり可読性が下がる |
3つ | プロシージャが短くなり可読性が上がる | 同じコードをプロシージャ分用意する箇所があるのでメンテナンス性が下がる |
しかしマトリクス化した事でプロシージャを1つ、もしくは3つという単純な結論ではダメな事が分かりました。
別の方向からプロシージャについて考える必要がありそうです。次は作業性という軸で対策を考えてみます。
作業性という軸で考えてみる
マトリクスの検証結果を参考にして作業性「だけ」に絞って事例に対し対策を考えてみましょう。
「こうなったら作業性が上がる」と思う内容を3つ挙げました。
- 短く_実行するプロシージャは「1つ」かつ「短いコード」の方が分かりやすい
- 見易く_各プロシージャは分けて見易い状態にする事でメンテナンス性を上げたい
- 少なく_変数は1ヶ所で定義して使い回す事で重複するコードを少なくしたい
「短く、見易く、少なく」という軸でやりたい事を書き出した次第です。
マトリクスで書き出した内容から「良いとこ取り」をしています。これが実現できればうれしいですよね。
作業性を考慮してプロシージャの分割を進めましょう
マトリクスでの検討結果を受けたうえで「やりたい事」だけを優先してプロシージャの分割を考えました。
やりたい事は決まったので次は実現可能性を考えます。いきなり結論になりますがこの3つは一緒に実現可能です。
多少結論ありきになってしまいましたが作業性という軸で検討を進めてほしかったのには理由があります。
「プロシージャの分割は結果であって本来やりたい事は別にある」という事です。
プロシージャは分割する事や統合する事がメインではなくて作業性を上げる為に分割・統合をします。
よって検討時のマトリクスの様にプロシージャを分割・統合する事から考えてもメリットは得られません。
この考え方をもって結果を見てみましょう。このまま作業性という軸でプロシージャの分割を進めます。
以降に用意したコードを確認ください。その後に解説を用意しておきます。
アウトプット(メイン1、サブ3のプロシージャを用意)
最終的なアウトプットは以下のような形態をとります。
一番上の「特定文字を含むシートを選択_メイン」プロシージャを実行する事で全ての作業に対応する事ができます。
'****************************************************************************************************
Sub 特定文字を含むシートを選択_メイン()
Dim wbk As Workbook
Dim sht As Worksheet
Dim cnt As Long
Set wbk = ActiveWorkbook
Call 特定文字を含むシートを選択_1改(wbk, sht)
Call 特定文字を含むシートを選択_2改(wbk, sht, cnt)
Call 特定文字を含むシートを選択_3改(wbk, sht)
MsgBox "特定文字を含むシートは" & cnt & "つです", vbInformation, "お知らせ"
End Sub
'****************************************************************************************************
Sub 特定文字を含むシートを選択_1改(ByVal wb As Workbook, ByVal sh As Worksheet)
For Each sh In wb.Worksheets
With sh
If .Name Like "*1" Then
.Range("A1").Interior.ColorIndex = 4
.Range("A1").Value = "みどり"
End If
End With
Next
End Sub
'****************************************************************************************************
Sub 特定文字を含むシートを選択_2改(ByVal wb As Workbook, ByVal sh As Worksheet, ByRef cn As Long)
For Each sh In wb.Worksheets
With sh
If .Name Like "Sheet*" Then
cn = cn + 1
End If
End With
Next
End Sub
'****************************************************************************************************
Sub 特定文字を含むシートを選択_3改(ByVal wb As Workbook, ByVal sh As Worksheet)
For Each sh In wb.Worksheets
With sh
If .Name Like "*2" Then
.Name = "2番目"
ElseIf .Name Like "*3" Then
.Name = "No.3"
End If
End With
Next
End Sub
'****************************************************************************************************
先頭のプロシージャを起点に3つのプロシージャを出し入れして作業を行うという建付けです。
詳細は以下解説をご覧ください。
解説
検討段階で用意した以下3つを軸に解説していきます。
- 短く_実行するプロシージャは「1つ」かつ「短いコード」の方が分かりやすい
- 見易く_各プロシージャは分けて見易い状態にする事でメンテナンス性を上げたい
- 少なく_変数は1ヶ所で定義して使い回す事で重複するコードを少なくしたい
1.短く_実行するプロシージャは「1つ」かつ「短いコード」の方が分かりやすい
「特定文字を含むシートを選択_メイン」プロシージャにあるコードは3つのプロシージャを呼び出すのが仕事です。
Callメソッドで3つのプロシージャを順番に呼び出しています。
呼び出されたプロシージャはそのプロシージャに用意されたコードを実行します。
End Subまで到達したら呼び出し元のプロシージャに戻ります。
これを繰り返す事で3つのプロシージャの仕事に対応出来ているうえでより簡潔なプロシージャを用意できます。
2.見易く_各プロシージャは分けて見易い状態にする事でメンテナンス性を上げたい
各プロシージャは3つに分割したままにします。この方が見易いです。
各プロシージャ毎にループを構えていますが変数等は「値渡し」、「参照渡し」により効率良く運用出来ています。
作業毎にプロシージャが分かれている為不具合発生時や変更時に作業ヶ所を特定する事が容易になります。
よってメンテナンス性が上がるという考え方になります。
3.少なく_変数は1ヶ所で定義して使い回す事で重複するコードを少なくしたい
3つのポイントの中でキーになる要素です。「参照渡し」、「値渡し」というキーワードを覚えて下さい。
この機能によって3つのプロシージャで共通の変数をメインプロシージャで定義し使い回しています。
具体的にどうやっているのかを説明します。作業の流れをご覧ください。
- 1メインプロシージャで変数を定義
Dim ○○ as ×× のかたちで変数を定義する
- 2メインプロシージャのCallメソッドの引数として変数を用意
他のプロシージャに持っていきたい変数をCallメソッドの引数として記入
- 3サブルーチン側で引数を受ける
- 変数を受け渡す際の順番には規則性がある
- 変数を受ける際は送り先の文字列と違う文字列で受ける(推奨)
- サブルーチン側で「値渡し」で受けるのか「参照渡し」で受けるのかを記入
- 4メインプロシージャに戻る
参照渡しで運用した変数の値はメインプロシージャに引き継がれます
3つ目の「サブルーチン側で引数を受ける」という内容だけ少し難しいです。
ここも3つにポイントを絞って詳しく説明します。
1_変数を受け渡す際の順番には規則性がある
Callメソッドでは(wbk,sht)という記載で変数をサブルーチンに送っています。
1番目がwbk、2番目がshtです。
サブルーチン側では(ByVal wb As Workbook, ByVal sh As Worksheet)というかたちで受け取っています。
1番目がwb、2番目がshです。
順番をしっかり書いたのは理由があります。
Callメソッドの1番目で渡した変数をサブルーチンの1番目の変数で受け取るというルールになっている為です。
- メインプロシージャのwbk → サブルーチンではwb
- メインプロシージャのsht → サブルーチンではsh
これでサブルーチン内では「wb」を使うことでメインプロシージャで定義したワークブックを使う事ができます。
同様にサブルーチン内では「sh」を使うことでメインプロシージャで定義したワークシートを使う事ができます。
2_変数を受ける際は送り先の文字列と違う文字列で受ける(推奨)
2つ例を挙げます。総じて言える事は作業性を考慮しているという事ですね。
- ローカルウインドウで変数の値を追う時に楽になります
- エラーが発生した時の立ち回りが楽になる事です
使い方のポイントとしては全く違う文字列で変数を受け渡すのは止めましょう。
理由は関連が分からなくなる為です。おすすめは「1文字違いで変数を使い分ける」です。作業しやすいですよ。
3_サブルーチン側で引数を受ける
サブルーチン側で参照渡しで変数を受けた際はメインプロシージャに戻る際値を引き継ぎます。
サブルーチン側で値渡しで変数を受けた際はメインプロシージャに戻る際値を引き継ぎません。
この理屈を前提として「特定文字を含むシートを選択_メイン」プロシージャで定義した変数は以下の様に使います。
- wbk ・・・ メインプロシージャで定義した変数をサブルーチン側で借りて使うだけ
- sht ・・・ メインプロシージャで定義した変数をサブルーチン側で借りて使うだけ
- cnt ・・・ サブルーチン側で作った値をメインプロシージャのメッセージボックスで表示させる
サブルーチンから「特定文字を含むシートを選択_メイン」プロシージャに値を持ち帰りたいのは変数cntだけです。
よって変数cntを受け取るサブルーチン「特定文字を含むシートを選択_2改」では変数を以下の様に受け取ります。
変数cnだけはByRefの参照渡しで受け取りましょう。残りの変数の受け渡しは全てByVal(値渡し)でOKです。
4_メインプロシージャに戻る
各モジュールでコードを実行じ作業を終えたらメインプロシージャに戻ります。
1つ上の3番の項目でも説明した様に参照渡しの値だけはメインプロシージャに引き継がれます。
値渡しの値はメインプロシージャに戻った際にリセットされます。
以降はメインプロシージャのCallメソッドに従い各サブルーチンで1~4の作業を繰り返す事になります。
その他
まずはワークブックに状況を再現してプロシージャを実行させてみてください。
プロシージャの分割は自分の手でプログラムを実行させて自分の目で動きを確認しないと理解が進みません。
手間ですがローカルウインドウを見ながらステップインで1行ずつコードを実行させる様にしてください。
トレーニングに使える応用編として1つ記事を紹介します。文字数が多く長いプロシージャを3つに分割しています。
画像を操るVBA|コードが長いプロシージャを短く切り分ける|マクロを部品化する
まとめ
プロシージャの分割を解説しました。分割ありきではなくあくまで「作業性」をあげる為に実施しましょう。
効率が上がるので是非お試しください・・・と言っても最初は難しいですよね。
自分に置き換えると初心者の頃は作業性が上がると言われてもプロシージャを分割する事自体が負担でした。
よって最初は1つのプロシージャでコードを作成してください。多少コードが長くなったり重複しても良いです。
その後「ここは分割出来るかも?」という思考で取り組むことをおすすめします。
分割する時は作業が楽になるとか考えなくて良いです。まずは分割することを優先して取り組みましょう。
繰り返し作業する事で作業効率もあげつつプロシージャを分割できる様になってきます。
今回の記事の内容はトレーニングが進めばクラスモジュールの理解にもつながる要素です。リンクを用意しておきます。
クラスモジュールとはVBA|クラスとは何か|本当に難しいのかを3つのポイントから検証