今回はVBAで好きな位置にブレークポイントを設置して、ストレス無く開発を進めるためのテクニックを紹介します。
前置き
昨日の記事の最後で「MsgBoxによるデバッグモードの入り方」を紹介した。
そこでは「コロン」だけを記載した行にブレークポイントを設定して見せた。
Sub Test_MsgBox() MsgBox "MsgBoxはCtrl+Pause/Breakで中断できます。" : '←ブレークポイントを設定しておく End Sub
この
- コロンだけの行を設置する理由
- ブレークポイントが設置できる場所とできない場所の違いは何か
- 一体このコロンは何者なのか
を掘り下げて説明していく。
なぜ好きな位置にブレークポイントを設置したいのか
実はVBAでブレークポイントが設定できる箇所には制限がある。
その詳細については後述するが、コロンだけを記載することでコードを汚さず好きな位置にブレークポイントが設置できるようになる。
またコロンという形で残す事で、「ここにブレークポイントを入れると良い」という意図をソースコード上に示す事ができる。
このような需要は一定数存在するようで、代替案として無意味なコードを記載する人もいるのだという。
i=i
しかし、本題のプログラムの中にこのようなコードを仕込んだ場合、これが意味のあるコードなのか、無意味なコードなのか見分けがつきづらい。
最悪の場合、バグの元となる恐れもある。
一方、私も昔はStopを記載して、コメントアウトして対応していた。
Stop ↓ 'Stop
しかし、コメント/コメントアウトが少しメンドクサイ。(一応、ショートカットキーを登録しているが)
人によっては、ソースコードが変わる事で差分を比較した時に不一致となるのが気になるかもしれない。
コロンだけの行を使えば、自動的に左端に寄せて記載してくれるので見やすいし、ブレークポイントが設定できるので重要なソースコードを汚さないで済ませられる。
:
Stopのようにブックを閉じても半永久に停止させる事はできないが、通常の開発ではブレークポイントで十分なはずである。
(まあVBAのバグでブレークポイントがブックに記憶されて、開き直しても解除されない時があるが)
で、具体的に何時使うかだが、例えばこんな時に有用である。
ループから抜けた直後で止めたい時
通常、NextやLoopにブレークポイントを設置すると、繰り返しのたびに中断が発生するため、開発者がやりたい「ループから抜けた直後で中断」ということができない。
そんなときに、ループから抜けた直後の位置にコロンがあると、ブレークポイントを入れる場所に困らない。
Sub Test6() '完成済みコード Dim i As Long For i = 1 To 10 Next '←ここでブレークポイントを入れると10回も止まってしまう。 : '←ここにブレークポイントを設置する 'ここから先をデバッグしながら追記したい。 End Sub '←コロンが無いとココで止めるしか無い。
一旦、その先で止めてから、次のステップを目的地までドラッグしても良いのだが、その手間を省くことができる。
また、テスト中にループを抜けた直後に一旦止めて、もう一度ループを回したい時に、次の処理が始まる前に止めるという時にも使える。
開発中はもちろん、テスト中にも便利なのである。
※単に処理の切れ目が明示されて分かりやすいというだけ。
ブレークポイントが設定できる箇所
VBEではF9でブレークポイントを入れる事ができ、プログラムがブレークポイントの設定された行に到達するとデバッグモードが始まるのは知ってのとおりだ。
しかし、ブレークポイントは基本的にプログラムが何らかの処理をする箇所にしか設定できないようになっている。
例えば、空欄の行、Dim
変数の宣言、Const
定数の定義、ラベルではブレークポイントを設定できない。
それら全てを網羅し、○×を付けたのが下記のプログラムだ。
Sub Test1() '○ Const PATH = "C:\hoge\" '× Dim i As Long '× '× For i = 1 To 10 '○ Debug.Print i '○x10 Next '○x10 : '○ label: '× i = 0: i = i + 1 '○ / ○ End Sub '○
基本的にブレークポイントが設置できるかどうかと、F8でステップした時にその行で止まるかどうかは、完全に一致している。
VBAにおけるコロンの意味
「コロンだけの行」が便利なのは分かったが、一体このコロンは何者なのだろうか。
それを理解するためには、まずはVBAにおけるコロンの意味を再確認する必要がある。
上記のプログラムを見て明らかなように、コロンには2種類の役割で使う事がある。
- ラベルとしてのコロン
- マルチステートメントとしてのコロン
ラベルとしてのコロン
label: '×
ラベルとは、Goto文でジャンプするための行き先を示す名前である。
多くのプログラマから忌み嫌われているGoto文ではあるが、VBAではエラー処理の為に避けては通れない機能だ。
例えば、下記のプログラムは「意図的にエラーを起こしエラーが起きた場合はn=1
を代入して元の流れに戻る」というサブルーチンが組まれている。
Sub Test2() '○ Dim n As Long '× '× On Error GoTo label '○ n = 10 / 0 '○ On Error GoTo 0 '○ '× '1を出力 '× MsgBox n '○ '× Exit Sub '○ '通過しない label: '× n = 1 '○ Resume Next '○ '通過しない End Sub '通過しない
なお、ラベルそのものはブレークポイントを設定できないため、エラー直後でデバッグモードに入りたい場合はn=1
の部分に設定することになる。
しかし、ラベルの後に更にコロンをつければブレークポイントを設置出来るようになるのも、覚えておくと便利かもしれない。
Sub Test8() '○ GoTo label '○ Exit Sub '通過しない label:: '× / ○ End Sub '○
マルチステートメントとしてのコロン
i = 0: i = i + 1 '○ / ○
積極的に使うのは褒められた行為ではないが、知っていると便利なのがマルチステートメント*1である。
例えば、以下のような場面でマルチステートメントを使うのは大変有用である。
変数の宣言と代入を纏めて書きたい時とか
Dim n As Long : n = 1 '× / ○ Dim fso As Object: Set fso = CreateObject("Scripting.FileSystemObject")'× / ○
非常に短い処理のIF文を一行で書き、複数の処理をしたいときとか
If i = 1 Then Debug.Print 1: Stop '○ / ○ / ○
非常に短い処理のFor文を一行で書きたいときとか
For i = 1 To 10: n = n + i: Next '○ / ○ x10 / ○ x10
Select Case文で非常に短い処理のCaseを書きたいときとか
i = 2 '○ Select Case i '○ Case 1: Debug.Print "いち" '○ / 通過しない Case 2: Debug.Print "に" '○ / ○ End Select '○
そう、かの有名なイミディエイトウィンドウなどで動かすワンライナーには必須の技術である。
なお、イレギュラーなケースとして、If文
の場合はコロンを付けなくてもマルチステートメントになる。
If i = 1 Then i = 2 '○ / ○ If i = 1 Then i = 2 Else i = 3 '○ / ○|○
ただし、マルチステートメントには少しだけ不便なところもある。
F8でステップした場合はそれぞれのステートメントで止まるのだが、ブレークポイントを設定した場合は常に行の先頭のステートメントで止まってしまう。
意図的に二番目のステートメントで止める事はできない。
どうしても止めたいならば、Stopを追記するしか無いと思われる。
コロンだけを書いたステートメント
: '○
コロンの意味は先の通り2種類だけだ。
ここまでで分かった挙動を整理すると、以下のようになる。
- ラベルだけの行はステップしない。
- マルチステートメントの行はそれぞれがステップできる。
- コロンだけを書いた行では、1回しかステップしない。
- コロンだけを書いた時、インデントが消えて左端に寄せられる。
敢えてここまで紹介しなかったが、実はこのような書き方も可能だったりする。
今までのルールを踏襲して、ラベルはステップしないが、代入文はステップする。
label: n = 1 '× / ○
この場合のコロンは、ラベルとしてのコロンなのか、マルチステートメントとしてのコロンなのか分からりづらい。
でも、ラベルにはコロンがないとラベルと認識できないことから、ラベルとしてのコロンだと見て間違いないと思う。
ここまで来ると、「コロンだけを書いた行」が何者か分かってきたのではないだろうか。
恐らく無名ラベルに続けて記載した空のステートメントである。
無名ラベルなんてものが存在するのかは知らないけれど、空のステートメントは確かに存在する。
例えば、変数の宣言の直後にコロンを入れて、その先の処理を何も書かないということもできる。
すると、何も処理をしないにも関わらずブレークポイントは設定できるし、F8でステップするようになるのである。
Sub Test5() '○ Dim i As Long: '× / ○ End Sub '○
また、ラベルに対してはマルチステートメント用のコロンを付けなくても良いという過去の風習があるので、そこから考えてもコロンは無名ラベル用なのではないか、と想像する。
※過去の風習:行ラベルのこと
Sub Test4() 10 Debug.Print "ラベル:10" 20 Debug.Print "ラベル:20" 30 GoTo 50 40 Debug.Print "ラベル:40" 50 Debug.Print "ラベル:50" 60 Debug.Print "ラベル:60" End Sub
実行結果
ラベル:10 ラベル:20 ラベル:50 ラベル:60
繰り返しますが、これは私の想像であり、正しい結論とは限りません。
(そもそも、どっちでもええやん!って話...)
参考
実はこの内容は前にツイートしたことのあるテクニックだったりする。
ちょっとマイナーなテクニック?
— ことりちゅん@えくせるちゅんちゅん (@KotorinChunChun) June 11, 2019
よくブレークポイントを入れるトコロには、「コロン」だけの空白行を置いておくというルールを定めておくと、後々のデバッグがやりやすくなる
特にループを抜けたあとで止めたい時に有効。
デバッグしながらコーディングしてる時とかにもおすすめかも#VBA pic.twitter.com/3ZfQ6hycIR
まとめ
というわけで、コロンだけの行を処理の合間に挟んでおくと、僅かなストレスが軽減されるとともに、デバッグがしやすくなりますよ。という話でした。
くだらない話を最後まで読んで下さった方は、本当にありがとうございます。
以上
何か御座いましたらコメント欄、またはTwitterからどうぞ♪
それではまた来週♪ ちゅんちゅん(・8・)