この記事では、プログラムを書く上で避けて通れない、デバッグ作業について、どのような方法があるのか、またどのようなアプローチでデバッグをしていくのかについて、個人的な経験も踏まえて紹介しています。
なお、ここでいうデバッグとは、「期待した結果と違う」という場合への対処方法です。
中には「プログラムの効率が悪い」などといったパフォーマンスの悪さを解消する手段も含まれますが、主眼は意図したとおりの動作となっていない、エラーが出る、という場合へのプログラムの見直し方を扱います。
なので、特定のエラーコードに対するエラー解消方法を紹介しているわけではありませんのでご注意ください。
デバッグは避けて通れない
まず大前提として、LabVIEWに限ったことではありませんが、プログラミングを行う上でデバッグは避けて通れないと思います。
もちろん、「書き慣れている組み方」があればその部分に関しては不要かもしれないですが(でもその部分を一番最初に書いたときにはデバッグしているかもしれないですし)、初心者の人はもちろん、どれだけ書き慣れている人でもそこそこの規模のプログラムを書いた場合には必ずミスします。だって人間だもの。
大事なのはミスをすることを前提にそのミスにうまく気づいて修正しようとすることなので、デバッグは面倒ですが、もうプログラムを書くという以上デバッグをすること自体は避けて通れないものとあきらめてください。
ただ、LabVIEWには便利なデバッグツールが多く揃っています。
デバッグ自体を避けられない以上、デバッグツールを最大限有効活用できるようにしておくのがいい心がけであり、プログラム作成の上達につながります。
LabVIEW自体の機能として存在しているデバッグツール以外にも、デバッグに役立つ考え方もいくつかあるので、様々なアプローチでバグを直し、目的の動作ができるように目指してみてください。
LabVIEWのデバッグツール
まずは、LabVIEWにもともと備わっているデバッグツールを紹介していきます。
実行のハイライト
最初に紹介するのは実行のハイライトです。LabVIEWのデバッグ作業でこれを使わない手はありません。
このハイライト機能をONにした状態でプログラムを実行することで、ブロックダイアグラムの動作を可視化することができ、各ワイヤでデータが流れる様子をアニメーションで見ることができます。
複雑なプログラムであっても流れを追うことで例えば「あ、ここ思った値と違っている」といったことを追いやすくなります。
ただし、アニメーションとして遅くなっているのではなく、本当にプログラムの動作が遅くなっていることには注意が必要な場合があります。
なお、この実行のハイライト機能は、私が知る限り長い間ずっと「変わらなかった」のですが、LabVIEW 2023 Q3からはこの実行のハイライトの仕様がこれまでと変わりました。
やれることは同じですが、実行のハイライト使用時の「早さ」を調整できたりアニメーションの様子が変わっています。
動的に実行のハイライト機能が動いている様子は静止画でお見せすることはできませんが、以下の記事で少しだけ紹介しています。
プローブ
次に紹介するのはプローブの機能です。
値を調べたい部分のワイヤにこのプローブを設定し、そのワイヤの値を表示したり、いつその値になったかの時間を表示することができます。
上で紹介した実行のハイライトの弱点である、実行そのものが本当に遅くなってしまう、という場合に対処するために有効です。
表示器をつけてもいいのですが、ワイヤの値の更新時間もわかるのが何気に便利です。
また、ワイヤを付ける場所の種類に依っては、複数の表示形式が選べたりもします。これには、カスタムプローブを選択します。
弱点としては、プローブは設置したところしか値を見れないということと、かといってここもあそこもとやっていくとそこそこ煩雑になる(管理が難しくなる)点が挙げられます。
また、プローブはブロックダイアグラムありきなので、ブロックダイアグラムを消すと削除されてしまいます(ブロックダイアグラムを最小化した場合は消えません)。
一応、似た機能に「ワイヤ値を保持」があり実行後にワイヤ値を確認できるという機能もありますが、そのワイヤ値を保持するのにメモリを消費していることになるため、おすすめはしません。
ステップ実行
実行のハイライトでは、ブロックダイアグラムをゆっくり動かすことによって各ワイヤ間のデータの流れを確認することができました。
ただ、場合によっては「それでも速い」と思うこともあり、そうこうしているうちにプログラムはどんどん進行してしまう、ということも当然あります。
プローブで値を見るにしてもプログラム実行時の処理の速さによっては値を追いきれず不便な場合もあります。
そんなときにはこのステップ実行を使って、各関数を一つずつ動作させるという操作をコントロールできるようにします。
感覚としては、テキスト言語で1行ずつ実行を順番に行っていくような状態です。
ステップ実行を使用することで、メインVIの中のサブVIの各関数の事項の様子もひとつひとつ確認することができます。
いちいち細かく見る必要のない処理は「飛び越える」ことができます(飛び越えてもその処理はもちろん実行されます。あくまで「ステップ実行として細かく見ることをしない」だけです)
ステップ実行の弱点としては、タイミングが関係するプログラムのエラーは確認できないということです。
それぞれの関数やノード、サブVIの実行一つ一つを制御して結果を確認しつつデバッグすることはできますが、実行速度によって結果が変わるような処理の状態を確認するには不向きと言えます。
ブレークポイント
プログラムの実行を強制的に一時停止するための機能です。
ワイヤ、あるいは関数自体に設定することで、プログラムがその部分の実行に差し掛かった時にブロックダイアグラムが点滅してその部分でプログラムが実際に停止します。
この一時停止をさせること自体が役に立つ場合もありますが、ブレークポイントは他のデバッグツールと組み合わせるとより真価を発揮します。
例えば、実行のハイライトはそのまま使ってしまうとプログラムの一番最初からゆっくり動作するようになります。
極端な話、プログラムの最初に全くデバッグをする必要のない、10000回繰り返すForループがあったとして、これが終わるまでデバッグしたい箇所に進まないということもあり得ます。
無駄にForループが終わるのを待つのではなく、Forループ終了後にプローブを置くことで、Forループ終了までは実行のハイライトを無効にしておいて、一時停止したら実行のハイライトを有効にし再びプログラムを一時停止以降の箇所から再開できます。
デバッグしたいところだけ見たい、という目的では、ステップ実行を行う際にも同じ考え方でできますね。
エラー表示器
これはデバッグツールというと少し他のツールとは経路が違うかもしれませんが、どこの時点でエラーが起きたのかを示しておくにはやはり大切です。
プローブでいいじゃんと思うかもしれませんが、プローブはブロックダイアグラムを消すと消えてしまうのに対し、エラー表示器はフロントパネル上のオブジェクトとして配置する表示器の一種なのでブロックダイアグラムを消しても当然消えません。
完成したプログラムでも、普段ユーザーが操作する画面では見えないところ(ウィンドウで表示されている範囲の外側)に敢えてエラー表示器を残しておき、何か期待しない結果が得られた時にはデバッグ用としてその表示を確認するといった使い方をすることも有効な場合があります。
エラー表示器はデバッグ用の画面として通常は表示させる必要がないのであればフロントパネルのウィンドウのサイズを調整して隠すことはできますし、例えばログインさせるような機能を設けて管理者としてログインしない場合にはウィンドウのサイズを変更できないようにしてエラー表示器を見えないようにするといった使い方もできます。
用途は似ているのですが、エラー表示器ではなく、シンプルエラー処理関数でエラーの内容を表示させるという方法も便利です。
シンプルエラー処理関数は、エラーのポップアップを表示させるときによく使用すると思いますが、単にエラー入力を行っただけだと、ポップアップが出てしまうためにプログラムがそこで中段してしまうことになります。
そうではなく、敢えてポップアップは出さないようにして、エラーのメッセージの内容を文字列表示器に表示させるという方法です。
エラー表示器の「ソース」には、エラーのソース、つまりどこの関数からエラーが出たかを知ることができますが、じゃあどういったエラーなの?という中身はエラーコードに集約されてしまいます。
これに対し、シンプルエラー処理関数のメッセージ表示は、そのエラーコードの内容をも文字列に表示してくれるため、いちいちエラーコードを確認しなくてもエラーの内容をすぐに見られる、というわけです。
デバッグツール以外のデバッグ方法
LabVIEWに備わっているデバッグ機能を紹介しましたが、ここからはデバッグのためのその他のアプローチを個人的な経験も踏まえて紹介していきます。
デフォルトを使用しない
特に「ストラクチャ」に関連するケースストラクチャ、イベントストラクチャの出力トンネルに対して、デフォルトを使用するのは避けた方がいいです。
デフォルトの指定の有無は見た目からも判断できますが、基本的には、全ての値を「意図的に」設定した方が無難です。
特にリファレンス関係のワイヤに対してデフォルトを使用してしまうと、リファレンス情報(refnum)を後続のプログラムに渡すことができなくなるため、意図した動作になりません。
「デフォルトで、デフォルト有効になる」イベントストラクチャは特に注意します。
ループの反復端子に表示器をつける
もし期待した動作になっていない時にその処理にWhileループやForループが絡んでいる場合、ループが本当に回っているか、あるいは意図した回数および速度で回っているかを確認するための方法として、反復端子に表示器をつけます。
複数の並列で実行される処理がループに入っていたとして、いずれかの処理が足を引っ張ることでループ全体の速度が遅くなることがあった場合、どの処理が遅くなっているかはこの方法ではわからないですが、ループ全体のパフォーマンスを評価する最初の一歩としては十分使えます。
ループが回っていたら反復端子の値が更新されるはずですし、その更新速度はループ速度に対応しているので、この値を見てループの動作を確認できます。
なんだそんなことかと思うかもしれませんが、意外と重宝することがあります。
値を記録する
エラー情報に限らず、特定の場所の特定のデータの履歴を記録しておくこともデバッグに役立ちます。
プログラムの中で、色々な箇所の値を調べてデバッグに活かしたい、ということであれば、簡単な機能的グローバル変数を作成するのが便利です。
例えば以下の図で示したような小さな機能的グローバル変数を用意し、writeの操作をプログラムの様々な場所で行わせれば、一つのファイルにプログラム実行中の様々な箇所の値を記録することができます。
ループの中に入れたいけれど、毎ループでの値が欲しいわけではない、といった場合には、「N回に1回」記録をする処理を実行すればよく、例えば以下の図のような形が考えられます。
(Falseケースは単にエラーの入出力に対してワイヤを通しているだけ、とします)
あるいは、データログの機能を使用するのが有効な場合もあると思います。データログについては別記事で紹介しています。
小さいプログラムで試す
使ったことのない関数はもちろんのことですが、使ったことのある関数に対しても、小さなプログラムにして動作を確認し、意図した処理結果になっているかを確認することはよくあります。
私個人では、配列操作について特にやります。配列に対する操作を行う関数はいろいろあるものの、行指定とか列指定とか、転置をした方がいいかどうかなど結構迷いますし、行と列が入れ替わっていると全然やりたいことができていなかったりするので特に注意します。
いきなり配列操作の関数を並べて実行し意図した結果にならないとどこの時点で処理結果が想定と異なるかわからなくなったりするので、部分部分で処理が意図したとおりになっているかを確認したりあるいは小さなサブVIに分割するのが有効なことが結構あります。
強制ドットの場所を確認する
LabVIEWはそれぞれの処理(関数)にデータを渡す際にワイヤを配線しますが、このワイヤの色と配線される側(関数の入力)の色を基本的に一致させないとワイヤが壊れて配線できませんよと表示される場合があります。
ただ、ワイヤが壊れない場合もあって、その代わりに配線した端子に赤いポチがつくことがあり、「データタイプを勝手に変換しましたよ」と知らせてくれます。これが強制ドットと呼ばれる機能です。
わざわざ色を合わせなくても、LabVIEWが勝手に判断してデータタイプを変換して便利だ、と思う場合もあるのですが、これでプログラムが動くと安心していると実際はこれが原因で思った通りの結果を得られなくなるというケースも起こりえます。
例えば、単純な足し算の例では以下のような結果となります。
意外と気づかないのが、符号付整数と符号なし整数の演算の結果がロールオーバー(その表示形式で表現できる最大の数を超えてしまうことで、最小の数からの増分値として結果が表れる状態)してしまう状態で、強制ドットがあって演算自体はできても本当に期待される値になっているかについてはよくよく注意する必要があります。
あるいはもう少しだけ複雑な例として、FFTを計算する際の値の入力にも注意が必要です。
使い方を知らずに、本来波形データを入力するべきところを配列データを入力してもプログラムが実行できてしまいますが、この場合「時間」の情報がないため期待通りの結果にならないことが起こりえます。
強制ドットの位置は、VIアナライザで調べることができます。
ただし、VIアナライザでは、「許容できる強制ドットの数」を指定することができ、この指定した数以下の強制ドットしかない場合には、VIアナライザによるテストに対しては「合格」したとみなされます。
強制ドットを持つviをハイライトさせる場合には、許容できる強制ドットの数を0とし、一つでも強制ドットがあれば「不合格」となるようにします。
VIアナライザの基本的な使い方は別記事で紹介しています。
キューステータスを見る
キューを使用したプログラムに限定されますが、生産者消費者のデザインでプログラムを組んだ際に思ったようにループが回っていない、特定のループ処理が遅い、停止ボタンを押したのに全然停止しない、などの問題が起きた際にはキューのステータスを確認することで状況を理解できる場合があります。
LabVIEWのヘルプメニューから「サンプルを検索」で開けるNIサンプルファインダ上で「キュー」と検索したときに見つかる「キューのオーバーフローとアンダーフロー」のサンプルで使い方の例が確認できます。
キューを使用するループ間でのデータの受け渡しの様子や受け渡し速度の検証を行うのに重宝します。
このサンプルのように、キューステータスを取得して例えば現在キューにたまっている要素数に表示器をつけて、思ったよりキューにデータがたまっている場合には、生産者ループが早すぎるか消費者ループが遅すぎるので、疑わしい部分を洗い出して修正するのに役立てます。
イベント検査ウィンドウでイベントの監視
今度はイベントストラクチャ限定の話ですが、イベントストラクチャで定義し検出されるイベントの様子はイベント検査ウィンドウで確認できます。
このイベント検査ウィンドウで記録できるイベントとは、ユーザーイベントも含みます。
テキストファイルに出力することもできます。
特に意図した動作をしていないことにユーザーの操作が絡んでいる場合、イベントストラクチャで定義したイベントが期待通りに実行されているか、あるいは特定のイベントが終わっていないためにフロントパネルのロックが起きているのかといったことを探れます。
ダイアグラム無効ストラクチャでコメントアウト
他のプログラムミング言語でいうところの、コメントアウトの機能をダイアグラム無効ストラクチャで実現します。
このストラクチャ範囲で「無効」として囲った部分は実行されなくなり「有効」の方が実行されることになります。
使い方で気を付けるのは、この「有効」の方が実行されることを忘れてデフォルト値を使用してしまうことで新たな問題が起きかねないことで、しっかりと意味のある配線をするように心がけます。
これらのデバッグ方法以外にもアプローチはあると思いますが、比較的とっつきやすく、それでいて期待した結果にならない原因を大まかに探る最初の段階では上記の方法いずれかが有効なことも多いので、最低限これらはいつでも使えるように覚えておくといろいろな場面で役立つと思います。
本記事では、LabVIEWに標準で備わっているデバッグツールの基本的な使い方や、デバッグの際にどういった方法を取るかの代表例を紹介しました。
デバッグがうまくなればプログラムを作るのは怖くないと思います。最初から完璧なプログラムを作れるなんてなかなかないので、もうバグは起こる前提で、いざデバッグするときに素早くバグを取り除けるよう日ごろからデバッグに対するいろいろなアプローチを身に着けるために本記事が参考になればうれしいです。
ここまで読んでいただきありがとうございました。
コメント