VBAでは互いに関連するプロシージャをひとつのモジュールに纏めることが多い。
このとき、呼び出す側と呼ばれる側のどちらを先に書けば良いだろうか。
以前の私の考えでは、例えばProc1がProc2を呼び出すとき、呼ばれる側を先に定義しておくという意味で先に書くことがあった。
Sub Proc2() MsgBox "Hello" End Sub Sub Proc1() Call Proc2 End Sub
(ただし、VBAの場合はどの順で書いても動くので、過去のコードで厳密にこの原則を守っていたわけではない)
最近Clean Codeという書籍を読んで、完全に考えが変わった。
- 作者: Robert C.Martin,花井志生
- 出版社/メーカー: KADOKAWA
- 発売日: 2017/12/18
- メディア: 単行本
- この商品を含むブログ (1件) を見る
Clean Codeによると、呼び出し側を上に、呼ばれる側が下になるのが望ましいとのこと。
たしかに、物事の説明の流れからしても概要→詳細となるのでコードにおいても粒度の大きいものを先頭に持ってくるというのは理にかなっている。
そして、可能な限り呼ばれる方のプロシージャは、呼び出すプロシージャの直下に置くこと。
マクロが複雑化し、プロシージャが増えれば増えるほど、プロシージャ同士の垂直方向の距離に気を配らないと読みづらくなり、理解に支障をきたす。
例えば次のプロシージャ群をひとつのモジュールに纏めるとする。
Sub Proc1() Debug.Print "Proc1" Call Proc2 Call Proc3 Call Proc4 End Sub Sub Proc2() Debug.Print "Proc2" Call Proc5 End Sub Sub Proc3() Debug.Print "Proc3" Call Proc6 End Sub Sub Proc4() Debug.Print "Proc4" Call Proc7 End Sub Sub Proc5() Debug.Print "Proc5" End Sub Sub Proc6() Debug.Print "Proc6" End Sub Sub Proc7() Debug.Print "Proc7" End Sub
呼び出し関係を図で表すと、こうなる。
冒頭に述べた「呼ばれる側を先に定義しておく」とは矛盾するが、私は階層順という考えも取り入れていた。
まさに先ほどのコードは階層順に並べたものだ。
しかしClean Codeを読んで以来、プロシージャは以下のように呼び出し順に並べるのが最も理解しやすいと考えるようになった。
改善したコードがこちら。
Sub Proc1() Debug.Print "Proc1" Call Proc2 Call Proc3 Call Proc4 End Sub Sub Proc2() Debug.Print "Proc2" Call Proc5 End Sub Sub Proc5() Debug.Print "Proc5" End Sub Sub Proc3() Debug.Print "Proc3" Call Proc6 End Sub Sub Proc6() Debug.Print "Proc6" End Sub Sub Proc4() Debug.Print "Proc4" Call Proc7 End Sub Sub Proc7() Debug.Print "Proc7" End Sub
最近の例では、自動スクリーンショットを取るためのマクロでプロシージャの並び替えを行った。
呼び出しマップ関係図はこちら。
並び替えのビフォーアフターはこのようになった。
Before
Private Sub clearClipboard() Private Sub animateCaption() Public Sub StartAutoScrap() Public Sub StopAutoScrap() Public Sub OnTimeScrap(Optional ByRef void = Empty) Private Sub popUpWindow() Private Property Get prevWindow() As Long Private Property Get lowestShapeEdge() As Single
After
Public Sub StartAutoScrap() Public Sub StopAutoScrap() Public Sub OnTimeScrap(Optional ByRef void = Empty) Private Sub animateCaption() Private Property Get lowestShapeEdge() As Single Private Sub popUpWindow() Private Property Get prevWindow() As Long Private Sub clearClipboard()
OnTimeScrapは4つのプロシージャを呼び出しているが、コードに登場する順に並べた。
呼び出し関係と並び順だけ取り出して図示すると、こんな感じ。
Before
After
変更後は呼び出しの矢印が常に右に伸びていてわかりやすい。
図中のBだけ独立しているのが気になる方がいると思う。
このBはStopAutoScrapを表しており、StartAutoScrapと機能的に対になるマクロなのでStartAutoScrapのすぐ下に置いている。
呼び出しの原則に従うと、こうするか、
Public Sub StopAutoScrap() Public Sub StartAutoScrap() Public Sub OnTimeScrap(Optional ByRef void = Empty) Private Sub animateCaption() Private Property Get lowestShapeEdge() As Single Private Sub popUpWindow() Private Property Get prevWindow() As Long Private Sub clearClipboard()
こうすることになるけれど、
Public Sub StartAutoScrap() Public Sub OnTimeScrap(Optional ByRef void = Empty) Private Sub animateCaption() Private Property Get lowestShapeEdge() As Single Private Sub popUpWindow() Private Property Get prevWindow() As Long Private Sub clearClipboard() Public Sub StopAutoScrap()
どちらも微妙なので、今回は呼び出し関係よりも機能的な相対関係を優先した。
以上