今回のVBScript入門記事では、プログラミングをするうえで重要な「デバッグ」について解説し、VBScriptでの効率的なデバッグ方法についても紹介していきます。
デバッグとはなにか
もし当ブログのVBScript入門記事をこれまで読んで頂いている賢明な読者であれば、「デバッグ」は既に経験済みだったりします。
参考として、いつものようにWikipediaのリンクを貼っておきます。
リンク先の記事を読むと、なにやら難しい記述が並んでいますが、要するに「プログラムの不具合を見つけて修正する」ことです。
更に広義な解釈で言えば、「作成したプログラムの処理状況を都度チェックしながら動作確認する」ことを指す場合もあります。
VBScriptでのプログラミングでは、作成したプログラムの実行時にエラーが発生した場合、エラー処理を実装していない場合はエラーの発生行とエラー内容がポップアップで表示されます。
その場合はそのエラー発生行の処理を精査することで原因が特定できるケースは多いですが、その行だけ見ても原因がわからないケースもあります。
また、プログラムを実行してもエラーは発生しないが、明らかに想定している動作と異なったり、演算結果が違うとなった場合に、プログラム実行中の内部的な状態をチェックしながら原因を突き止める作業をしていくことになります。
これが「デバッグ」です。
一般的なプログラミング言語におけるデバッグ方法とVBScriptの問題点
当項ではVBScript以外の一般的なプログラミング言語におけるデバッグ方法を紹介し、VBScript固有のデバッグに関する問題点を紹介します。
VBAでのデバッグ方法
一般的なデバッグ方法の例として、当ブログでも度々紹介しているVBAでのデバッグ方法を紹介します。
VBAではVBE(Visual Basic Editor)と呼ばれる、Microsoft Office製品に共通で搭載されている機能を利用してプログラムを記述します。
VBEでは、記述したプログラムを実行することはもちろんのこと、処理を特定の行で一時停止させたり(ブレークポイント)、プログラムを一行ごとに止めながら実行して(ステップイン)いくこともできます。
例えば、特定の処理でエラーが発生し、その原因がすぐにはわからない場合に、そのエラーが発生する箇所の少し手前でプログラムが一時停止するようにしておきます。
一時停止中でもそれまでの処理で変数に格納した値はVBEで確認することが可能ですし、プログラムを一行ごとに止めながら実行していくことで、変数の値の移り変わりを追ったり、条件分岐やループ処理の動作状況を確認していくことも可能です。
これはVBAだけではなく、他のプログラミング言語でも同様の操作は行え、この操作の実現自体はIDE(Integrated Development Environment)、統合開発環境上で実施することができ、プログラミングをするうえで、無くてはならないものの一つです。
VBScriptのデバッグに関する問題点
前項では、ブレークポイントを設定してプログラムの実行を途中で止めたり、ステップインで一行ずつプログラムを進めて実行状況を確認する仕組みはプログラミングにとって無くてはならないと説明しましたが、実はVBScriptではブレークポイントの設置やステップインでの実行といった、一般的なデバッグ処理ができません。
※2020年9月 加筆
探したらデバッグができるVBScript用のIDE的なエディターが有りました。
以下の記事でインストール手順や使い方を紹介しています。
正確に言えば、VBScriptをサポートするIDEが存在しないため、一般的なIDEが提供するそれらの機能も使えないということになります。
よって通常の環境でプログラムを作成する場合は「VBScriptは一般的なデバッグができない」というのが世の中の共通認識です。
VBScriptでの一般的なデバッグ方法
VBScriptでのデバッグのやり方をいくつか実際のサンプルコードと併せて紹介していきます。
メッセージボックスを使用する
これはVBScriptでプログラミングをする際の定番のデバッグ方法です。
ちょっとしたプログラムの調査や検証時にサクッと調べたい場合に使います。
使い方は調べたい対象の変数の値や演算処理などをMsgBox関数の引数に指定し、プログラムの記述の一部に挿入します。
Option Explicit Dim Hensu Hensu = 10 + 10 Msgbox Hensu
こうすることで、調査対象の値がMsgBoxで表示されるようになります。
メッセージボックス使用時の問題点
メッセージボックスで値を出力することで手軽にデバッグができますが、例えばループ処理内の値を取得してデバッグをしようとした場合、そのループの回数分メッセージボックスが表示されてしまい、更にループ処理が終わるまで何度もメッセージボックスが表示されることになり、非常に効率が悪いデバッグになってしまいます。
MS OfficeのVBEを使用する
インターネットで「VBScript デバッグ」などのワードで検索すると、通常VBAの開発環境であるVBEにVBScriptのプログラムを記述してデバッグする方法を紹介している記事もいくつか見かけます。
これも使い方によっては便利な方法です。
過去の記事でも何度か記載していますが、VBAとVBScriptはプログラムの文法や構文が類似しており、VBScriptで作成したプログラムをVBE側へコピペしてもある程度同じように動作します。
VBE上でVBScriptで記述したプログラムが動作すれば、VBAと同様にブレークポイントを置いて、一行ごとにステップインしながらデバッグが行えるようになります。
VBE側では独立した一つのSubプロシージャを作成し、そのSubプロシージャ単体で実行することができます。
そのSubプロシージャ内にVBScriptのプログラムを移植する感じです。
VBAでは変数の宣言時にデータ型も指定しますが、データ型を指定しなくても宣言はできます。
その場合は「バリアント(Variant)型の変数として生成されます。
VBScriptでは変数の宣言時にデータ型の指定ができず、変数宣言直後はすべてバリアント型として生成されます。
よって、結果的に変数の宣言時の扱いについても同じ動きをします。
VBE使用時の問題点
VBAとVBScriptはプログラミング言語として共通点が多く類似しているとお伝えしましたが、まったく同じではなく、VBScriptの構文だとVBAではエラーになるケースもあります。
また、VBScriptで作成したプログラムをVBAに移植するということは、VBScriptとVBAの違いをある程度理解し、両言語に精通していないと、下手したらそのVBScript→VBAへの移植作業自体に労力が掛かってしまうことも考えられるます。
要は比較的上級者向けのデバッグ方法です。
プログラミングを習得しようと学習されているようなケースでは、このデバッグ方法はおすすめできません。
個人的におすすめしたいデバッグ方法
私自身がVBScriptで込み入った処理を実装する場合などによく使用しているデバッグ方法を紹介します。
ログ出力用のSubプロシージャを作成してそれを使用する
まずはVBScriptのプログラム内に以下のようなSubプロシージャを作成してください。
サンプルコードをそのままコピペしてもらえれば結構です。
Public Sub SysWriter(str) Dim objWshShell Dim fso, fi Dim LogFileName Dim wkNow 'ファイル名に設定する日付をyyyymmdd形式で取得します。' wkNow = Year(Now()) wkNow = wkNow & Right("0" & Month(Now()) , 2) wkNow = wkNow & Right("0" & Day(Now()) , 2) 'カレントディレクトリを取得して、カレントディレクトリのlogsフォルダ内にlogファイルを作成します。' Set objWshShell = WScript.CreateObject("WScript.Shell") Set fso = CreateObject("Scripting.FileSystemObject") LogFileName = objWshShell.CurrentDirectory & "\logs\" & wkNow & ".log" 'ファイルを開く 'もしも存在しない場合には作成する Set fi = fso.OpenTextFile(LogFileName, 8, true) fi.WriteLine (Date() & " " & Time() & ": " & str) 'ログを書き込む Set fi = Nothing Set objWshShell = Nothing End Sub
このプログラムは、他所の技術系ブログで公開されていたものを多少の手直しをしてそのまま有難く使わせて頂いている処理です。
通常はVBScriptでバッチ処理を作成した際に、バッチ実行時のログを残す目的で使用しています。
自分自身のカレントディレクトリ内に「logs」フォルダがあれば、そのなかに今日の日付のファイル名でログファイルを出力します。
同名のファイルが既に存在していれば、そのファイルに追記します。
このSubプロシージャを使用してデバッグをする場合のサンプルプログラムとしては、過去に記事にしたSQL Serverへ接続してSELECTする処理をベースに実装してみましょう。
Option Explicit Call Main() Sub Main() Dim objCon Dim query Dim objRS Dim srvName, dbName, loginName, loginPass 'データベース接続情報を定義します。' srvName = "DBサーバ名" dbName = "DB名" loginName = "DBユーザ名" loginPass = "DBパスワード" 'SQLServerへ接続します。*************************************************************************************************** Set objCon = CreateObject("ADODB.Connection") objCon.Open "Driver={SQL Server}; server=" & srvName & "; database=" & dbName & "; uid=" & loginName & "; pwd=" & loginPass & ";" 'SQLを実行してレコードセットに格納します。********************************************************************************* query = "" query = query & "SELECT " query = query & " カラム1 " query = query & " ,カラム2 " query = query & " ,カラム3 " query = query & "FROM テーブル名 " query = query & "WHERE " query = query & " カラム1 = xx" '■デバッグ処理 SQL文の内容をログファイルに書き出します。 SysWriter(query) '定義したSQLを実行してレコードセットに格納します。' Set objRS = objCon.Execute(query) 'レコードセットのデータを表示します。*************************************************************************************** 'レコードセットのデータ件数が0件の場合は処理を終了します。 If objRS.EOF Then Msgbox("対象データが存在しない為、処理を終了します。") objCon.Close Set objRS = Nothing Set objCon = Nothing Exit Sub End If 'レコードセットの行数分ループします。 Do Until objRS.EOF '■デバッグ処理 レコードセットの値をログファイルに書き出します。 SysWriter("カラム1の値:" & objRS("カラム1").Value & " カラム2の値:" & objRS("カラム2").Value & " カラム3の値:" & objRS("カラム3").Value) '次のレコードセットに移動します。 objRS.MoveNext Loop '終了処理をします。。******************************************************************************************************* 'オブジェクトを破棄します。 objCon.Close Set objRS = Nothing Set objCon = Nothing End Sub '引数で渡された文字列をログファイルに出力します。 Public Sub SysWriter(str) Dim objWshShell Dim fso, fi Dim LogFileName Dim wkNow 'ファイル名に設定する日付をyyyymmdd形式で取得します。' wkNow = Year(Now()) wkNow = wkNow & Right("0" & Month(Now()) , 2) wkNow = wkNow & Right("0" & Day(Now()) , 2) 'カレントディレクトリを取得して、カレントディレクトリのlogsフォルダ内にlogファイルを作成します。' Set objWshShell = WScript.CreateObject("WScript.Shell") Set fso = CreateObject("Scripting.FileSystemObject") LogFileName = objWshShell.CurrentDirectory & "\logs\" & wkNow & ".log" 'ファイルを開く 'もしも存在しない場合には作成する Set fi = fso.OpenTextFile(LogFileName, 8, true) fi.WriteLine (Date() & " " & Time() & ": " & str) 'ログを書き込む Set fi = Nothing Set objWshShell = Nothing End Sub
上記サンプルプログラムの34行目ではSQL文が格納された変数をログファイルに出力させています。
サンプルプログラムでは固定されたSQL文の例を紹介していますが、実際にプログラミングで生成するSQL文では、WHERE句で指定するデータ取得条件を可変にする場合が大半で、複雑なSQL文を生成する場合は、実際に生成したSQL文が想定している文字列になっているかを、この様にログファイルに出力させて確認することが多いです。
また、同じくサンプルプログラムの52行目ではループ処理内でレコードセットの値をログファイルに出力させています。
前述したメッセージボックスを使用したデバッグでは、ループ処理内にメッセージボックスを仕込んでしまうと、ループする回数分メッセージボックスが表示されて非常に非効率ですが、このサンプルプログラムの様にログファイルへ出力させることで、プログラムを中断せることなくプログラムの内部的な値を確認することができます。
尚、ログ出力用のSubプロシージャ自体は名前の通り本来の実行ログを残すための処理なので、デバッグ目的が済めば、今度は実行ログを残す目的でそのまま使用できます。
是非活用してください。
今回も読んでいただきましてありがとうございます。
また次回もよろしくお願いいたします。
それではごきげんよう。