今回はVBAのStopとDebug.Assertステートメントの違いについてお話します。
VBAのデバッグモード
プログラム開発で欠かせないのが、デバッグモードである。
デバッグモードでは、ソースコード上でプログラムが次にどこを実行するかを、ある程度操作することができる。
デバッグモードがあるかどうかで、言語の習得効率や開発効率が大きく異る。
幸い、VBAにはVBEという必要最小限の開発環境が整っているため、デバッグ自体は特に不自由なく使うことができる。(もちろん不満はあるが、許容できるレベルだと私は考える)
VBAで実行中のプログラムに対して強制的にデバッグモードに入るためには、以下の様な方法が存在する。
- ブレークポイント
- ウォッチ式
- Debug.Assert
- Stop
- MsgBox
- Err.Raise
- VBEの「中断」ボタン
ESC
やCtrl+Break
による「中断」
今回はその中のうちDebug.Assert 条件
とIf 条件 Then Stop
について比較を行った。
先日色々と調べる機会があったため、分かったこと整理しておく。
関連ツイート
https://twitter.com/hoehoe1234/status/1170939094177734656
Debug.assert
If 状態 then sotp
err.Rais 例外
どれもその場では止まるから同じと言えばおなじか?
https://twitter.com/hoehoe1234/status/1170916681704599552
VBAにてDebug.assertの使い所がわからないのですが、みなさんは使ってますか?
使っているとしたらどのようなつい買い方をしてますか?
StopとDebug.Assertの違い
以前から同じことが実現可能な両者の存在意義が疑問であった。
結局のところ違いは2つしか見つからなかった。
- 記述と考え方が違う
- 効果音の有無が違う
その結果、開発者にとっては3つの違いがある。
- 停止条件の考え方が逆(Assertのほうが直感的かも?)
- If~Stopは同時にRange.Select、Debug.Printするなど応用が効く
- Debug.Assertでは効果音を鳴らす事ができる
停止条件の考え方が逆
当たり前だがStopとDebug.Assertでは記述が違う。
StopはStop
という4文字だけで機能する。
ブレークポイントのコーディング版と言っても過言ではない。
Stop
また、Ifと組み合わせて使えば、条件を満たしたとき中断する
(言い換えれば、条件を満たさないとき通過する)
'真の時、停止 If True Then Stop '中断 If 10 > 5 Then Stop '中断 '偽の時、通過 If False Then Stop If 10 = 3 Then Stop
対して、Debug.Assertは、条件を満たしたとき通過させる
(言い換えれば、条件を満たさないとき中断する)
'真の時、通過 Debug.Assert True Debug.Assert 10 > 5 '偽の時、停止 Debug.Assert False '中断 Debug.Assert 10 = 3 '中断
つまり、考え方が真逆なのである。
ただし、両者ともNotで否定すれば逆の動きをさせることも出来るので、中断だけであれば両者で出来る事には違いはない。
'真ではない時、通過 If Not True Then Stop If Not 10 > 5 Then Stop '偽ではない時、停止 If Not False Then Stop '中断 If Not 10 = 3 Then Stop '中断
一応、実際の風景を動画にしてみた。毎秒F5を押した様子
If~Stopは応用が効く
If~Then Stop
では中断前に処理が追記できる点を考慮すると、Stopの方が汎用性は高いと考えられる。
たとえば、停止前にDebug.Print
を行いたいケースは多いはずだ。
If Not 10 = 3 Then Debug.Print hensu : Stop
また、Excel VBAに於いてはRange.Select
を行うと、デバッグが非常にやりやすくなる。
If Cells(i,1)=1 Then Cells(i,1).Select : Stop
他にも様々なケースに活用することが考えられる。
Debug.Assertでは効果音が鳴る
教えて頂いて、初めて知った。
この2つの挙動の差、をためしたところ
— ほえほえ@ヒューズプログラミグスクール (@hoehoe1234) 2019年9月9日
assertは音がなる
stopは音がならない
だったw。どちらももういちど実行(ステップ)すると次の行にいける。assert派がすくなそうなので、とりま採用してみるのもいいかも? pic.twitter.com/VSoVYLSumH
たしかに、
Stopは音が鳴らない
Debug.Assertは音が鳴る
Windows10の場合「ぽわゎん♪」って感じの音。
MsgBoxで言うと「vbExclamation」に相当する。つまり警告音だ。
そもそもDebug.Assertは使わないし、使ってみた時は音無しだったので気が付かなかったのだろう。世紀の大発見だ!
StopとDebug.Assertの共通点
早くも「違い」のネタが尽きてしまったので、現時点までに私が比較して共通の結果となった事柄を記載しておく。
もし他の検証方法を思いついたら、試すなりコメントするなりしてくれると嬉しい。
VBEが強制的に開かれる
VBEが開かれていないときにStopやDebug.Assertを通過すると、強制的にVBEが開かれて該当の行でデバッグモードとなる。
ブレークポイントと違い、ブックを閉じても状態が続くので、開発者的にはすごく便利である。
でもユーザーからしてみれば、突然英語の書かれた意味不明な画面が出てきた!ウイルスか!?となるため、StopやDebug.Assertを残すのは危険である。
知ってか知らずか、ユーザーがプログラムを破壊して上書き保存されたら大惨事だ。
従って、リリースする時はErr.Raiseに置き換えるか、コメントアウトを忘れずに。
プロジェクトがロックされている場合は働かない
「プロジェクトのロック」により保護されている状況では、通常時と異なる挙動となる点に注意しなければならない。
ロックされている場合は、Stop
もDebug.Assert
も、何事もなかったかのようにスルーされる。
つまり、配布時だけプロジェクトをロックすることで、以下のようなメリットがある。
- 開発・テスト中は(一時的にロック解除するため)異常を検知したらVBEを開いてデバッグに入れる。
- 中断してもユーザーの画面でVBEが開かれない。
- 配布のためにStopやDebug.Assertを無効化する必要がない。
一方で配布時はStopやDebug.Assertの安全措置が働かないことを踏まえて
- Stop等がスルーされても後の動作に影響しない書き方をする。
- Err.Raiseによりエラーをスローする書き方をする。
と言った対策も必要となりそうだ。
個人的にはプロジェクトのロックを使えば、配布のためにコメントアウトや条件付きコンパイル制御をしなくても良いので、すごく便利ではないかと感じているところ。
どう使うか
さて、それでは、どうやってStopやDebug.Assertを使うべきだろうか。
今の私はどう使っているのか振り返ってみた。
Stop
Stopは条件を満たしたとき中断する
開発者は「こういうのはダメなので止まって!」と思いを込めてタイピングするはずだ。(私だけ?)
例えば私は「もし、こんなデータ来たらバグるかもな。後で対処プログラム書かないとな」と閃いたら、すぐにストッパーを仕込んでおく。
'「# タイトル」という文字列から「#」を数値化して返す Function GetID(s As String) As Long If s Like "*e+#*" Then Debug.Print s: Stop'指数表記はちょっと待った! On Error Resume Next GetID = CLng(Left(s, InStr(s, " "))) End Function Sub test_FuncHoge() Debug.Print GetID("12 あいう") Debug.Print GetID("1 あいう") Debug.Print GetID("123e+3 あいう") End Sub
おかげで、//いつか直す
、//いつかやる
が大量に蓄積してしまうのだが、目の前の課題を早くこなす必要があるため、存在しない入力パターンまで想定した例外処理を作っている時間はない。
かといって、これを無視してしまうと、関数の信頼性が揺らぐ。つまり再利用しづらいという問題が起こる。
不思議なことに書いている時は次から次へとイレギュラーに気がつくのに、読み返した時は何も浮かばないということが多い。
言うなれば、Twitterではポンポン会社への文句を言いたいことが浮かぶのに、会議の場で話せと言われると何も浮かばないような状態だ。
だから、私は、思いついたら必ずメモをする。ドキュメントやコメントに残すのではなく、実際にプログラムが止まる形で残すのだ。たぶん。
ところが、この文章を書いていて気がついてしまった。
これ、Err.Raiseしたほうがイイんじゃない?と。
(プロジェクトをロックしたらスルーされてしまう為←未完成のままリリースする奴が悪い?)
他にも以下のように、「想定されるパターンを潰していって、想定外のものを検知したときに続きのプログラムを書く」ためにStopを使うことがある。
特にテストデータが不足しており、完全な分岐パターンが網羅しきれていないと感じたときに、安全措置としてStopを挟んでおくような使い方をよくする。
'文字列sを加工してコネコネする関数 Function StrFmt(s As String) As String '丸括弧が含まれている時は、カッコ内を取得 If s Like "*(*)*" Then s = Right(s, Len(s) - InStr(s, "(")) s = Left(s, InStr(s, ")") - 1) 'アンダーバーが含まれている時は、前半を取得 ElseIf s Like "*_*" Then s = Left(s, InStr(s, "_")) '想定外のフォーマットは、後で追記する Else Debug.Print s Stop 'ここでデバッグモードへ End If StrFmt = s End Function 'テストコード Sub Test_StrFmt() '丸括弧 >> cde Debug.Print StrFmt("ab(cde)fg") 'アンダーバー >> abcd Debug.Print StrFmt("abcd_efg") '想定外 >> ? Debug.Print StrFmt("ab<cd>efg") End Sub
このような場合も、Err.Raiseのほうがイイんじゃないか?と、気がついてしまった。
やっぱり、Stopが適しているのはブレークポイントを、ブックが閉じられても有効な状態で残したいと思った場面くらいなのかもしれない。
Private Sub Worksheet_SelectionChange(ByVal Target As Range) '後で作る Stop End Sub
とか。
Debug.Assert
さて、問題はDebug.Assertだ。私は実務で使ったことがない。
Debug.Assertは、条件を満たしたとき通過する
言い換えれば、条件を満たさなければ止めない。続行する。とも考えられる。
もし使うとしたら、「こうでなくちゃダメなんだよ!ダメなんだよっ!」と思いを込めてタイピングするのだろうか。
たとえば、複数のAssertを並べて書いて、動作に必要な条件をひたすら並べていくような感じか。
Private Sub Worksheet_SelectionChange(ByVal Target As Range) Debug.Assert Target.Row > 1 Debug.Assert Target.Row < 100 Debug.Assert Target.Column > 1 Debug.Assert Target.Column < 10 '処理 End Sub
だが、Debug.Assertは止めることに特化しており、If True Then Stop
のような拡張性は無いのが惜しい。
例えば上記Debug.Assert Target.Row < 100
をIf 条件 Then Stop
へ置き換えると、以下のような変形が容易にできる。
Private Sub Worksheet_SelectionChange(ByVal Target As Range) If Target.Row >= 100 Then Stop '100以上の場合の検証コードをココに書く。 End If '処理 End Sub
まあ、この場合、Debug.Assertの使うべき場所ではなかったということだろう。
で、Debug.Assertが必要な場所はどこ?
デバッグモードに入った後、ローカルウィンドウとウォッチウィンドウとイミディエイトウィンドウを駆使して何かをするのだろうか。
最大のメリットである警告音を役に立たせる方法はあるのだろうか。
まとめ
StopとDebug.Assertの違いは
- 停止条件の考え方が逆(Assertのほうが直感的かも?)
- If~Stopは同時にRange.Select、Debug.Printするなど応用が効く
- Debug.Assertでは効果音を鳴らす事ができる
です。
デバッグモードに入るための機能を整理すると以下の表のような感じになります。
デバッグモードの入り方 | ブックを閉じても効果が持続 | プロジェクトをロックしても効果は持続 | 条件停止 |
---|---|---|---|
ブレークポイント | × | × | × |
ウォッチ式 | × | × | ○ |
Debug.Assert | ○ | × | ○ |
Stop | ○ | × | × |
If~Stop | ○ | × | ○ |
If~MsgBox | ○ | ○ | ○ |
If~Err.Raise | ○ | ○ | ○ |
他に両者の違いに気がついた人や、私はこういう時にStop使いますよ!というのがあれば、コメント等に書き込んでいただけると助かります。
補足
ウォッチ式によるデバッグモードの入り方
ウォッチ式を使った中断を、使いこなしている人は少ないと思う。
一つだけ利用事例をお見せしよう。
繰り返し文の中で、カウンタが50になった時の状態をチェックしたいとする。
そんな時は、ウォッチウィンドウにi=50
、式がTrueの時に中断
として登録しておくと、実際にiが50になった瞬間にデバッグモードに入ることができる。
Sub Test_Watch() Dim i As Long, n As Long For i = 1 To 100 n = n + i Next End Sub
MsgBoxによるデバッグモードの入り方
最初に上げたデバッグモードに入る方法の中に、MsgBoxを記載している。
MsgBoxで一体どうやって・・・?と思うかも知れないが、実は裏技があるのだ。
実はMsgBoxでCtrl+Pause/Breakキー
を押すと、コードの実行が中断されました。
と出て強制的にデバッグモードに入ることができる。
この時、MsgBoxの次の行にフォーカスがあたった状態で中断される。
Sub Test_MsgBox() MsgBox "MsgBoxはCtrl+Pause/Breakで中断できます。" : End Sub
メッセージボックスを出すためにVBEが隠れてExcelが全面に出てくるのが気持ち悪いが、ユーザーからしてみればごく自然な動きとなるので、ユーザービリティとしては一番優れているかもしれない。
尚、コロンを使う裏技については、次の記事で解説しているので読んでみて欲しい。
尚、Stop
、Debug.Assert
、ブレークポイント
以外の場所で止めたいという場合は、ESC
やCtrl+Pause/Break
の長押しが有効である。それでも止められない時は我が奥義を試すと良いだろう。
以上
何か御座いましたらコメント欄、またはTwitterからどうぞ♪
それではまた来週♪ ちゅんちゅん(・8・)