ある程度マクロ作成に慣れてくると、こんなことありませんか?
- いま作ってるマクロって、最近似たようなもの作った気がするなぁ。
- この処理も前に作成したマクロのあの部分と同じだなぁ。
コピペしたいけど、どこに置いたっけ? - 「最終行の取得」や「ファイル一覧の取得」などのよく使う処理って、
マクロ作成するたびに毎回同じコード書いてるなぁ。
確かにそういうこと、よくあるわね。
前のコードをコピペするにしても、結局同じコード書いてるわけだから、
なんだか効率悪いし。
そういう時は、よく使う処理を
「Subプロシージャ」や「Functionプロシージャ」で「部品化」して
「別モジュール」で管理
しておいて、必要なところから呼び出せるようにしておくのがおすすめね。
ということで「部品化して楽しよう」について説明していきます。
部品化とは
「部品化」とは、よく使う処理や同じような処理を部品として外出し(独立化)しておき、その処理を使いたいところから呼び出して汎用的な再利用ができるようにすることです。
部品化することの利点
部品化することには、以下のような利点があります。
同じ処理を新たにコーディングしないので、時間の節約ができます。
また、部品化されたものは少なくとも一度は使用され、ある程度精査されているわけですから、「書く」ということの時間だけでなく、「処理内容の確認時間」も節約できます。
マクロを作成する際に、同じことを何度も調べたりしていませんか?
今の時代、ググらずにコーディングすることはまずないかと思いますが、同じことを何度も調べるのは明らかに時間の無駄ですよね。
部品化しておき、それが何の処理なのかを明確にしておけば、使う側は呼び出すだけでいいので、これも時間の短縮になります。
新たにコーディングしないので、それだけ「ミスを発生させる機会が減る」わけです。
わざわざ部品化しないでも「コピペ」すれば、間違えることはないのでは?という意見もあるかもしれませんが、そうとも言えません。
コピペする際に1行だけコピペを忘れたとかなかったでしょうか。
コピペ元に修正が入った場合、同じ修正をコピペ先にもすることを忘れたことなかったでしょうか。
他の人が修正した場合、修正箇所が複数あることに気づきずらいのではないでしょうか。
作った本人でさえも、数日もすれば忘れてしまうことでしょう。
これはコピペしなければ避けられるミスです。
「コードを書くということは、その分だけバグが入る余地を増やしている」と考えるのがよいと思います。
「よく使う処理」ということは、コード上に「何度も出てくる処理」とも言えるわけです。
「何度も出てくる」とは「重複する」ということ。
重複コードはバグを発生させる温床となります。
同じようなコードが2つ以上あったら、それは「部品化すべきところ」です。
部品を使う側は呼び出しのコードを書くだけなので、メイン処理のSubプロシージャを短くできます。
ソースコードが全体的にスッキリして見やすくなります。
他の人が見ても、比較的理解しやすいコーディングになると思います。
ある処理にバグがあることが分かった場合、その処理がソースコードの3カ所に書かれていたら、その全てに同じ修正を入れないといけません。
このとき起こりやすいのが「1カ所だけ修正するの忘れてしまった」という修正漏れです。
この3カ所に散らばっている処理を部品化して1カ所にしていれば、修正は1カ所だけで済み、修正漏れも発生しないわけです。
部品化を進めていくことで、よく使う処理をだんだんと蓄積していくことができます。
新しく作成するマクロでは、その蓄積された部品を呼び出すだけでよくなります。
部品化は処理を外出しするので、少しだけ手間がかかるのは事実だと思います。
「数行だから部品化しないでも、また書けばいいや」と思ってしまうのもわかります。
ですが、数行のものでも、一週間後には忘れていることもあるのではないでしょうか。
そうすると、毎回同じようなコードを書き続けることになります。
部品化してみる
部品化の例
以下のような処理内容を例として、部品化をしてみます。
Sheet2とSheet3のデータをコピーしてSheet1に貼り付ける。
コードの書き方はいろいろありますが、処理をそのまま書き連ねると、以下のようなコードになります。
部品化前
Sub Main()
'----------------------------- ブロック1 -----------------------------
'画面表示と自動計算の停止
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'----------------------------- ブロック2 -----------------------------
'Sheet1の準備
Dim ws_1 As Worksheet
Set ws_1 = ThisWorkbook.Worksheets("Sheet1")
Dim endRow_1 As Long
'----------------------------- ブロック3 -----------------------------
'Sheet2の準備
Dim ws_2 As Worksheet
Set ws_2 = ThisWorkbook.Worksheets("Sheet2")
'Sheet2の最終行を取得
Dim endRow_2 As Long
endRow_2 = ws_2.Cells(ws_2.Rows.Count, 1).End(xlUp).Row
'Sheet2の最終列を取得
Dim endCol_2 As Long
endCol_2 = ws_2.Cells(1, ws_2.Columns.Count).End(xlToLeft).Column
'Sheet2のデータ範囲を取得
Dim targetRng_2 As Range
Set targetRng_2 = ws_2.Range(ws_2.Cells(2, 1), ws_2.Cells(endRow_2, endCol_2))
'Sheet1の最終行を取得
endRow_1 = ws_1.Cells(ws_1.Rows.Count, 1).End(xlUp).Row
'Sheet2のデータ範囲をコピーして、Sheet1に貼り付け
targetRng_2.Copy Destination:=ws_1.Cells(endRow_1 + 1, 1)
'----------------------------- ブロック4 -----------------------------
'Sheet3の準備
Dim ws_3 As Worksheet
Set ws_3 = ThisWorkbook.Worksheets("Sheet3")
'Sheet3の最終行を取得
Dim endRow_3 As Long
endRow_3 = ws_3.Cells(ws_3.Rows.Count, 1).End(xlUp).Row
'Sheet3の最終列を取得
Dim endCol_3 As Long
endCol_3 = ws_3.Cells(1, ws_3.Columns.Count).End(xlToLeft).Column
'Sheet3のデータ範囲を取得
Dim targetRng_3 As Range
Set targetRng_3 = ws_3.Range(ws_3.Cells(2, 1), ws_3.Cells(endRow_3, endCol_3))
'Sheet1の最終行を取得
endRow_1 = ws_1.Cells(ws_1.Rows.Count, 1).End(xlUp).Row
'Sheet3のデータ範囲をコピーして、Sheet1に貼り付け
targetRng_3.Copy Destination:=ws_1.Cells(endRow_1 + 1, 1)
'----------------------------- ブロック5 -----------------------------
'画面表示と自動計算の設定を元に戻す
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
分かりやすいように、ブロック1~5に分けました。
問題点を挙げてみると、こんなところでしょうか。
- Subプロシージャが長すぎる。(長くても20行~30行以内を推奨)
- 対象とするシートが違うだけの全く同じ処理が、「ブロック3」と「ブロック4」に重複して書かれている。
- 「よく使う処理」が部品化されていない。
部品化(その1) 同じ処理の部品化
まずは「問題点2」について変更をいれてみます。同じ処理の部分は、部品化すべきところです。
Subプロシージャとして外出ししてみましょう。
Sub Main()
'----------------------------- ブロック1 -----------------------------
'画面表示と自動計算の停止
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'----------------------------- ブロック2 -----------------------------
'Sheet1の準備
Dim ws_1 As Worksheet
Set ws_1 = ThisWorkbook.Worksheets("Sheet1")
'----------------------------- ブロック3 -----------------------------
'Sheet2の準備
Dim ws_2 As Worksheet
Set ws_2 = ThisWorkbook.Worksheets("Sheet2")
Call CopyData(ws_2, ws_1)
'----------------------------- ブロック4 -----------------------------
'Sheet3の準備
Dim ws_3 As Worksheet
Set ws_3 = ThisWorkbook.Worksheets("Sheet3")
Call CopyData(ws_3, ws_1)
'----------------------------- ブロック5 -----------------------------
'画面表示と自動計算の設定を元に戻す
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
'----------------------------------------------
'コピーシートから貼り付けシートにデータを貼り付ける
'----------------------------------------------
Sub CopyData(copyWs As Worksheet, pasteWs As Worksheet)
'コピーシートの最終行を取得
Dim endRow As Long
endRow = copyWs.Cells(copyWs.Rows.Count, 1).End(xlUp).Row
'コピーシートの最終列を取得
Dim endCol As Long
endCol = copyWs.Cells(1, copyWs.Columns.Count).End(xlToLeft).Column
'コピーシートのデータ範囲を取得
Dim targetRng As Range
Set targetRng = copyWs.Range(copyWs.Cells(2, 1), copyWs.Cells(endRow, endCol))
'貼り付けシートの最終行を取得
Dim endRow_pasteWs As Long
endRow_pasteWs = pasteWs.Cells(pasteWs.Rows.Count, 1).End(xlUp).Row
'コピーシートのデータ範囲をコピーして、貼り付けシートに貼り付け
targetRng.Copy Destination:=pasteWs.Cells(endRow_pasteWs + 1, 1)
End Sub
「ブロック3」と「ブロック4」の同じ処理を、Subプロシージャ「CopyData」として外出ししました。
改善された点として、以下が挙げられます。
- 「問題点2」が解消され、スッキリして見やすくなった。(可読性の向上)
- 「ブロック3」と「ブロック4」が、同じ処理をしていることが理解しやすくなった。
部品化前は一見したところ同じ処理をしているように見えるが、もしかしたらどこかが違うかもしれない。同じかどうか確かめるには、処理を1行ずつ追わないといけないが、部品化後はその必要がない。(可読性の向上) - もし「Sheet4からもデータコピーする」ことになった場合、Sheet4に対して「CopyData」を呼び出すだけの分かりやすく、簡単な修正で済む。(生産性の向上)(保守性の向上)
部品化(その2) よく使う処理の部品化
続いて「問題点3」の「よく使う処理」を外出ししてみます。
ここでは、以下の2点を外出しします。
- 画面表示と自動計算の設定
- 最終行、最終列の取得
Sub Main()
'----------------------------- ブロック1 -----------------------------
Call StartSetting
'----------------------------- ブロック2 -----------------------------
Dim ws_1 As Worksheet
Set ws_1 = ThisWorkbook.Worksheets("Sheet1")
'----------------------------- ブロック3 -----------------------------
Dim ws_2 As Worksheet
Set ws_2 = ThisWorkbook.Worksheets("Sheet2")
Call CopyData(ws_2, ws_1)
'----------------------------- ブロック4 -----------------------------
Dim ws_3 As Worksheet
Set ws_3 = ThisWorkbook.Worksheets("Sheet3")
Call CopyData(ws_3, ws_1)
'----------------------------- ブロック5 -----------------------------
Call EndSetting
End Sub
'----------------------------------------------
'コピーシートから貼り付けシートにデータを貼り付ける
'----------------------------------------------
Sub CopyData(copyWs As Worksheet, pasteWs As Worksheet)
'コピーシートのデータ範囲を取得
Dim targetRng As Range
Set targetRng = copyWs.Range(copyWs.Cells(2, 1), _
copyWs.Cells(GetEndRow(copyWs, 1), GetEndCol(copyWs, 1)))
'コピーシートのデータ範囲をコピーして、貼り付けシートに貼り付け
targetRng.Copy Destination:=pasteWs.Cells(GetEndRow(pasteWs, 1) + 1, 1)
End Sub
'----------------------------------------------
'画面表示と自動計算の停止
'----------------------------------------------
Sub StartSetting()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
End Sub
'----------------------------------------------
'画面表示と自動計算の設定を元に戻す
'----------------------------------------------
Sub EndSetting()
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
'----------------------------------------------
'最終行を取得
'----------------------------------------------
Function GetEndRow(targetWs As Worksheet, targetCol As Long) As Long
GetEndRow = targetWs.Cells(targetWs.Rows.Count, targetCol).End(xlUp).Row
End Function
'----------------------------------------------
'最終列を取得
'----------------------------------------------
Function GetEndCol(targetWs As Worksheet, targetRow As Long) As Long
GetEndCol = targetWs.Cells(targetRow, targetWs.Columns.Count).End(xlToLeft).Column
End Function
以下のような変更を加えています。
- 画面表示と自動計算の設定をSubプロシージャ「StartSetting」「EndSetting」とし、「Main」から呼び出すように変更。
- 最終行と最終列の取得をFunctionプロシージャ「GetEndRow」「GetEndCol」とし、「CopyData」から呼び出すように変更。
- 「CopyData」の最終行、最終列取得のコード部分が「GetEndRow」「GetEndCol」と短くなったので、変数を用意せず、使用箇所から直接呼び出すように変更。
改善された点として、以下が挙げられます。
- 呼び出しの1行を書くだけで、他のプロシージャからも使える。(生産性の向上)(精度の向上)
- 各プロシージャが20行~30行以内に納まり、「問題点1」が解消できている。
- 外出ししたプロシージャ名で処理内容を簡潔に表わすことにより、呼び出し元でコメントを付けなくても理解しやすい。(可読性の向上)
部品化する際の注意点
例を通して説明してきましたが、「部品化」する際の注意点があります。
他にもありそうですが、個人的によく気にしている点を挙げておきます。
外出ししたプロシージャを、個別のデータや処理に依存するようなものにしてしまうと、再利用の利点を生かしきれなくなってしまいます。
再利用する際に「これどんな処理だっけ」とならないようにするためと、コードの可読性の意味からも、処理内容が分かりやすい名前にしておきます。
いきなり修正を入れてしまうと、うまくいかなかった時に動かなくなったりするので、元に戻せるように部分的にでもバックアップしておくことをお勧めします。
自分用の部品集を作成する
「よく使う処理」を部品化したものは、他のマクロからも呼び出せるように、「部品集」として蓄積しておきましょう。
1.新しいモジュールを追加する
外出ししたプロシージャを別モジュールにコピーして、後で使えるようにします。
まず、標準モジュールを追加します。
モジュール名は変えても変えなくても構いません。私は「Util」という名前にしています。
2.部品化するコードをコピーする
部品化するコードをコピーします。
他のモジュールからも呼び出せるように「Public」を付けてください。
また、「Public」にするとボタンにマクロ登録する際に、登録候補として出てきてしまうので、コードの先頭に「Option Private Module」の一行を追加しておくとよいです。
3.追加したモジュールをエクスポートして、部品集として管理する
追加したモジュールをエクスポートします。
4.新しいマクロを作成する際は、部品集をインポートする
新しいマクロを作成したら、エクスポートした部品集をインポートします。
これで、部品集のプロシージャを新しいマクロで呼び出して使えば、新たにコーディングしないで済みます。
このとき、部品集を修正したり、新たに部品を追加した場合は、忘れずにエクスポートして、最新版として保持できるようにしてください。
また、ファイルが先祖返りしないように、ファイル名に日時など付けて履歴管理してもいいかもしれません。
まとめ
長くなりましたが、部品化しておくと、様々なメリットが得られると思います。
処理が分割されるので、はじめは読みずらいことがあるかもしれませんが、そのうち部品化せずにはいられなくなると思います。
ここで紹介した例は簡単なものですが、コードが大きくなるほどメリットも大きくなります。
部品化を進めて「楽」しましょう。