コードが長いプロシージャを短く切り分けました
VBAが理解できる様になるとコードは短くスッキリ書けるのですが勉強初期ではプロシージャが長くなりがちです。
理由は色々あります。主にこの3つがコードが長くなる理由です。
- コピペで作ったプロシージャなのでどのコードを削れば良いのか分からない(内容を理解していない)
- 数か所でコードが重複している(似たような事を何度も書いている)
- レイアウトが良くない(コメントが過剰、スペースが多過ぎる、改行しないなど)
コーディングが「コピー&ペースト」だとスキルアップしていかないのでコードを減らす事もできません。
よってコードの重複も無くなりません。コメントが多いのは親切ですが過剰だとプロシージャは長くなります。
こうなるとシンプルに「読みにくい」ですよね。
コードが読みにくいプロシージャの解決策として「プロシージャを分割」する事を提案します。
私のブログ内に用意されている構成が長くなってしまったプロシージャを使って解説します。
本記事では長いプロシージャを3つに分けて読みやすくするのと同時にメンテナンス性も向上させてみます。
以降具体例を使って解説していきます。
関連書籍
紹介する書籍はサブルーチンについて詳しい説明があります。「マクロを部品化する」という表現をしています。
私は会社の机の上にこの本を置いています。行き詰った時に読んでアイデアをもらっています。
環境次第ですが電子書籍(Kindle)もおすすめです。
PC内でVBEと電子書籍を並べて表示させながら作業をするとコーディングがはかどります。
プロシージャを分割するとは(サブルーチンとは)
プロシージャを分割する事を「サブルーチン」と言います。
こちらの記事で解説しています。定義や使い方、値渡しや参照渡しについても解説しています。
サブルーチンの使い方【マトリクスで理解する】プロシージャを分割したマクロを用意する方法
マクロを部品化する
関連書籍として紹介した本の中に「マクロを部品化」するという記載があります。
個人的には「マクロを部品化」するというキーワードはサブルーチンをうまく表現出来ていると感じています。
あなたの作るマクロが100ステップを超えてくるようになると、1つのマクロで全ての処理を行うことにはっきりと無理が出てくるでしょう。
このようなときには、処理単位でマクロを作り、親マクロからそれらの部品マクロ(サブルーチン)を呼び出すようにした方がはるかに効率的でスマートです。
Excel VBAで本当に大切なアイデアとテクニックだけ集めました。 大村あつし:著
以降でマクロを部品化していきます。
改善前
具体例を使って実際に長いプロシージャを見易くしていきましょう。まずは改善前のプロシージャを紹介します。
元になるプロシージャはこちら
元のコードが記載されている記事です。1プロシージャに100行前後のコードが用意されています。
コード内ではFileSystemObjectや各種ループ、Like演算子を使っているので長いうえに複雑な仕様になっています。
階層構造のフォルダ群から任意の画像を取得VBAで画像を取り込む高速で画像自体を取得しエクセルに貼り付ける
改善後
改善前で紹介したコードを引き継いでプロシージャを3つに分割しました。
ただし作業環境やコードは少し変更させてもらいました。1つずつ説明していきます。
作業環境(ワークフロー)
最初にワークフローを紹介します。
- まずserch_picture.xlsmからフォルダ名「folder」にアクセスします。
- 続いてfolder内に配置された「fd1~fd10」という名前の10個のフォルダに順にアクセスします。
- 10個の各フォルダには2枚ずつ同じ名前の画像が用意されています。
- tnac,t-1.JPEG
- tnac,t-2.JPEG
- 10個の各フォルダには2枚ずつ同じ名前の画像が用意されています。
- 各フォルダ内で「-1」というキーワードを持つ画像を検索します
- 該当の画像だけ.xlsmに取り出して指定のセルにはめ込みます。
よって最終的には10枚の同じ画像がserch_picture.xlsmに持ち込まれることになります。
一息つきましょう2つの画像は国立新美術館です。(THE NATIONAL ART CENTER, TOKYO)
写真は自身が現地に行った際に撮ったものを使用しています。
この建物は外観にインパクトがあるのですが中はさらに良い空間です。光の入り方が素晴らしいです。
ワークシート
serch_picture.xlsm内の画像です。最終的に赤枠のセルの中に画像が格納されることになります。
操作方法
大きく分けて3工程です。1つ上で紹介した画像と合わせてみていただき操作方法を理解してください。
- セルA3に画像が収納されているフォルダのパスを記入
- セルF2付近にある実行ボタンを押下
- セルA5を先頭にA6、A7・・・に画像が挿入される
取得する画像は縦横の比率を保持したままセルに入る一番大きいサイズで挿入される様にしています。
裏を返すとセルの大きさを変えておけば画像のサイズを調整出来るという仕様にしています。
セルI2付近にある削除ボタンは取得した画像を一気に削除する事ができるボタンです。
実行後
赤枠の様にセルに画像が入ります。今回は10枚同じ画像が格納されますのでセルA14まで画像が入る事になります。
作業環境やワークシート、操作方法について説明しました。次はコードを紹介していきます。
プロシージャ、コードの紹介
コードを紹介する前に各プロシージャを紹介します。プロシージャは3つです。
- mainプロシージャ:変数の定義やフォルダやファイルの周回などを担当
- make_Pathプロシージャ:画像ファイルまでのフルパスの生成を担当
- get_Pictureプロシージャ:画像を取得し大きさの調整などを担当
以降コードを確認ください。実行するのはmainプロシージャです。
make_Pathプロシージャとget_Pictureプロシージャはmainプロシージャから呼び出されるサブルーチンです。
Sub main()
Dim base_path As String 'ファイルパスを格納 セルA3に\を足す
Dim file_name As String '画像名
Dim file_path As String '上2つの変数をセットにして格納
Dim idx As Long: idx = 2 ' 貼付け位置を管理する変数
Dim fso As Object ' ファイルシステムオブジェクト
Dim s_fd As Object '現状掴んでるフォルダを格納する変数
Dim rng As Range '貼り付けるセルを格納
Application.ScreenUpdating = False
'オブジェクト変数にファイルシステムオブジェクトをセット
Set fso = CreateObject("Scripting.FileSystemObject")
'GetFolderメソッドでフォルダを選択
For Each s_fd In fso.GetFolder(Cells(3, 1)).subfolders
Call make_Path(s_fd, base_path, file_name, file_path) 'サブルーチンで画像を選択
Do Until file_name = ""
If file_name Like "*-1*" Then '画像名に -1 が含まれているかを確認
Set rng = Cells(idx + 3, 1) '条件Trueなら貼り付けるセルを決める
Call get_Picture(rng, idx, file_path) 'サブルーチンで画像を取得&大きさ調整
End If
file_name = Dir() 'ポイント:サブルーチンで実行したDir関数の値を引き継いでいる
file_path = base_path & file_name '次の画像を選択 無ければループを抜ける事になる
Loop
Next s_fd '次のフォルダを取りにいく
Application.ScreenUpdating = True
MsgBox "処理が完了しました", , "作業完了"
End Sub
'****************************************************************************************************
Sub make_Path(ByVal fd As Object, ByRef bp As String, ByRef fn As String, ByRef fp As String)
bp = fd & "\" 'パスとファイル名を繋ぐための\を含めて変数にとる
fn = Dir(bp, vbNormal) '画像ファイルを選択
fp = bp & fn 'パスと画像ファイルの文字列を一緒にしてフルパスを生成する
End Sub
'****************************************************************************************************
Sub get_Picture(ByVal rg As Range, ByRef i As Long, ByVal fp As String)
'指定されたセルに画像を用意する(詳細は元記事に任せます)
With rg.Worksheet.Shapes.AddPicture(Filename:=fp, _
LinkToFile:=False, _
SaveWithDocument:=True, _
Left:=rg.Left, _
Top:=rg.Top, _
Width:=0, _
Height:=0)
.LockAspectRatio = True
.Placement = xlMoveAndSize
.ScaleHeight 1, msoTrue
.ScaleWidth 1, msoTrue
'ここから画像をセルに合わせ込む作業(詳細は元記事に任せます)
If .Width > rg.Width - 2 Then
.Width = rg.Width - 2
End If
If .Height > rg.Height - 2 Then
.Height = rg.Height - 2
End If
.Top = .Top + ((rg.Height - .Height) / 2)
.Left = .Left + ((rg.Width - .Width) / 2)
End With
i = i + 1
End Sub
コードの解説
まずはサブルーチンの意図について説明します。(サブルーチンについての解説はこちら)
サブルーチンを組む際の目的はその時により様々です。今回は以下のような観点からサブルーチン化しました。
- フォルダや画像を取りに行く作業(主にループ)をmainプロシージャで運用
- 変数に値を入れる(make_Path)、画像を取得する作業(get_Picture)をサブルーチンとして分ける
イメージですが対象物を取りに行く(振る舞い)作業と対象物を生成する(実務)作業で分けました。
サブルーチンの説明は以上です。以降はプロシージャ毎に簡単な説明とリンク記事を用意しました。
1_mainプロシージャ
ここでは変数の定義(宣言)と各フォルダへの周回に関するコードを用意しています。
ポイントはオブジェクト変数の使い方と各種ループです。
Do While ~ LoopステートメントなどVBA|Dir関数を使ってフォルダ内の複数ファイルを順番に処理する
For Each ~ Nextステートメントマクロで特定文字を含むシートを選択|For Each ~ Next & Like
2_make_Pathプロシージャ
名前の通りになってしまうのですが一連の作業の中で使うパスを生成するサブルーチンです。
必要に応じてループ内でパスを生成しmainプロシージャに返します。ポイントは値渡しと参照渡しです。
どの変数を値渡し(ByVal)で受けてどの変数を参照渡し(ByRef)で受けるのかを考える必要があります。
mainプロシージャに戻り値として持ち帰りたい変数は参照渡しで受ける様にしましょう。(詳細はこちら)
3_get_Pictureプロシージャ
serch_picture.xlsmの決められたセルに画像を挿入する為のサブルーチンです。
.xlsm側に画像を持ってきた後で画像の大きさを調整するところまでを担当しています。
ポイントはShapesオブジェクトが保有しているAddPictureメソッドです。
一部引数の設定方法で分かりにくいところがあります。詳細はこちらの記事をご覧ください。
引数を理解するAddPictureメソッドエラー対策|指定された値は境界を超えています
その他:ScreenUpdatingプロパティ
ApplicationオブジェクトのScreenUpdatingプロパティはサブルーチン側でも効果を発揮します。
よってmainプロシージャの中でScreenUpdatingのFalseとTrueを切り替える様にしましょう。
ScreenUpdatingプロパティとは
簡単に申し上げますと「画面更新を止める」という指示です。(詳細はMicrosoftのリンク)
具体的に申し上げますと「False」で画面更新を止めて「True」で画面更新を復活させます。
プロシージャの開始直後に「False」、終了間際で「True」をプロパティに代入しましょう。
これで画面更新に使うリソースを削除する事ができるのでマクロの実行速度が向上します。
まとめ
長くなってしまったコードを分割するという作業を事例を使って解説ました。
最後にプロシージャを分割する事によるメリットとデメリットの話を少しだけ書かせていただきます。
実施する事で可読性が上がりメンテナンス性も上がるのですが環境によっては実施しない方が良い事もあります。
特にグループで使っているマクロの時はサブルーチンを組む前にメンバーと十分相談してから行ってください。
「プロシージャを分けてコードが読めなくなった」となると本末転倒です。これがデメリットにあたります。
デメリットを回避するにはサブルーチンを組む事に対し理解活動を行ってから作業を実施する事をおすすめします。