この記事では、LabVIEWでプログラムを書く際によく使用されるデザインパターンにおける「初期化」の役割について、どのようなパターンがあるかの例を紹介しています。
デザインパターンは、プログラムを書くときに、「この構造を意識すれば書きやすい、読みやすい」といったノウハウが詰まったテンプレートです。
当然、書きたいプログラムによってそのテンプレートに適用する具体的な処理の中身は異なりますし、テンプレートをそのまま適用するというよりは、その考え方を参考にプログラムを組んでいくことの方も多いかと思います。
ただ、デザインパターンの種類に依らずに多くのプログラムでみられるのが初期化という処理です。
でも初心者の人にとっては何をしていいのかわからない状態と思われるかもしれません。
そんな初期化について、どのような処理を行うことが多いのかを挙げてみました。
初期化とは?
このブログでもLabVIEWによる多くのプログラムの実装例を紹介していますが、それらのプログラムは「こうするとプログラムが書きやすい」という決まった構造(デザインパターン)に基づいて作成しています。
そしてそんなデザインパターンの中でも基本的でありながら強力なものがステートマシンです。
ステートマシンは、プログラム中の様々な処理、段階を、「状態(ステート)」として区分し、それらの状態を自由に移り変わりながら、全体として目的を達成するプログラムの書き方です。
(ステートマシンを適用するにも、ある目的を達成するためにはどのような処理、段階が必要かということをあらかじめわかっている必要があり、それを洗い出すのが「設計」作業の一部ですね)
当然、目的に応じてどのような処理が必要かというのは千差万別、一概には言えないのですが、多くのステートマシンについては一番最初に「初期化」という状態が含まれています。
私がブログで公開している様々なプログラムでも、ステートマシンをベースにしたものは「init」とか「initialize」などといったステートを一番最初に実行するステートとして用意(実際のプログラムの中では、ケースストラクチャの中のケースの一つとして存在)しています。
ただ、この初期化というのは何となく意味は分かりそうなものの少し抽象的な感じもするかと思います。
特に(LabVIEWに限らないですが)プログラムを書くのに慣れていない状態だと「初期化というけれど何をすればいいわけ?」ということに悩んでしまう場合もあるかと思います。
これはこれで、プログラムの目的によって初期化として行うべき処理内容は異なるので一概に言いにくいものではありますが、それでもよく見る初期化処理は決まったものが多いように思います。
ここでは、初期化としてどのようなことを行うことが多いかについて例を挙げていきます。
初期化で行うこと
それでは、初期化で行うことの代表例を紹介していきます。
フロントパネルの表示を既知の状態に変える
まずは、フロントパネルの表示状態を既知の状態に変えることです。
ここでいう「既知」というのは、よくわからないランダムな値ではなく、あらかじめ決められた値、ということを意味しています。
恐らく初期化処理の中で最もよく行う処理の一つです。
重要なのは、ここでいう「表示」というのがいわゆるフロントパネルの表示器だけでなく、制御器のことも指す点です。
プログラム実行時に、常にある定まった値の状態から始めたい、という場合には基本的にプログラムから制御器や表示器の値を明示的に指定し、これを初期化処理として行います。
前回最後にどんな値で終わったとしても、毎回実行時に同じ値から始められるのであれば、「最初に変な値が入っていて期待しない動作をしてしまう」ということは回避できます。
(もちろん、前回最後に扱った値の続きから開始したいという場合もあると思いますが、その場合には次に紹介する方法で対応します)
制御器は本来、ユーザーが入力を行うためのものであるため、プログラムからその表示されている内容を変えることは頻繁には行いません。
ただ、最初に何々を表示させておく、あるいは何も表示させない、デフォルト値を表示させる、という場合には初期化の段階で制御器や表示器に特定の値を与える処理を行わせることが多いです。
そして、私が知る限り、このような場面はローカル変数の使用が有効である数少ない場面の一つです。
制御器へプログラム的に値を入れる方法はローカル変数やごく一部の限られた方法しかないのですが、例えばその他の方法である「プロパティノードの値プロパティ」はローカル変数と比べて実行速度が遅いなどの不都合があります(初期化状態において実行速度は気にならないということがほとんどかと思いますが、極力値プロパティを使うというクセを無くすためにも意識したいところです)。
ローカル変数とプロパティノードのパフォーマンスの差は以下の記事で紹介しています。
とにかく、初期化ステートにて制御器や表示器をデフォルトの状態に戻しておくのはお決まりの操作と言えます。
iniファイルから値を読んでクラスタに渡す
次に初期化でよく行うのは、iniファイル(に限りませんが、要は外部ファイル)から値を読みこんでそれをステートマシン中で使用するクラスタに渡すという処理です。
iniファイルには、そのプログラムで必要なパラメータのデフォルト値や参考値、あるいは前回実行時の最後の値の情報が入っていることが多いので、このiniファイルから読み取った値をステートマシン中の各ステートで読み書きしうるクラスタの初期値にする、といったことをよくします。
上の図の例では、iniファイルの読み込みはサブVIを使用してすっきりとした見た目にしています。
それこそ、最初に紹介した制御器の値をローカル変数で書き換えるというのも、そのプログラムを前回実行していたときに最後に制御器に入れていた値をiniファイルに記録させて次回実行時の最初にその「前回値」を制御器に渡すといった使い方もできます。
リファレンス作成
初期化の段階で、何らかのリファレンスを作成しておくことが便利な場合もあります。
ここでいうリファレンスとはいくつか種類がありますが、制御器や表示器のリファレンスを取得するといった操作が挙げられます。
リファレンスを取得しておく理由ですが、一度に多くの制御器のプロパティを変化させる場合にForループと使用するような使い方が挙げられます。
特に、操作を受け付けなくさせる無効プロパティとの使用はよく実装される例で、以下のプログラムのように、特定の制御器のリファレンスを取り出して配列とし、Forループでまとめてプロパティの設定を行うような書き方が有効です。
あるいは、ファイルI/Oやハードウェア操作に伴って必要なリファレンスもあります。
ステートマシンでシフトレジスタを使って各ステートで使用するのはケースの情報(列挙体)だけではなく、各ステートで値を変更したり参照するためのデータも含みます。
リファレンスの情報を初期化状態で用意しておいてからシフトレジスタで回し、そのリファレンスを必要とするステート内で適宜参照します。
他にも、ハードウェア操作におけるリファレンスを作る(DAQでいうチャンネル作成に相当)ことを初期化状態で行える場合もあると思います。
フロントパネルの位置変更
LabVIEWのフロントパネルはユーザーインタフェースの役割を果たし、このウィンドウはユーザーが任意の位置や大きさを設定することができます。
ただ、何らかの理由があって、実行時には毎回モニターの特定の位置に表示させておきたいという場面もあるかと思います。
そんなときに、プログラマーによる「ユーザー操作方法の初期化」の意味も込めて、ウィンドウの位置、サイズを初期化するという操作を行います。
自分自身のVIのリファレンスをプロパティノードに入力し、フロントパネルのウィンドウの境界プロパティに値を書き込むことで、特定の大きさのウィンドウで始められるようにします。
何でも初期化に詰め込めばいい?
ここまで、代表的な初期化処理の内容をいくつか紹介しました。
ここまでの話で、「一つの初期化処理で複数のことやればよくない?」と思われた方もいるかと思います。
場合によってはもちろんアリです。
ただ、初期化処理に何でもかんでも含むのが好ましくない場合もあります。
例えば、フロントパネルの制御器を既知の状態にするためにローカル変数で値を渡すという処理と、iniファイルからプログラムで使用する変数を読み込むという処理を一つの初期化ケースに入れたとします。
ケースストラクチャは当然、その中身すべてが実行されないと追われないので、この場合には「制御器の値がローカル変数によって上書きされる」ことと、「iniファイルから読み込んだ値でプログラムで使用する変数の値が上書きされる」といったことが同時に起こることになります。
もし、プログラムの中で、これら二つの操作が常に同じタイミングで起こることしかない、のであれば一つの初期化処理にいれることは問題ありません。
ただし例えば、iniファイルを読み込んで変数を書き換える処理は複数回行う可能性がある、となると、特定の処理だけ同じケースに入っている別の処理よりも実行したい回数が多いことになるため、この場合には処理(ケース)を分けた方がいいということになります。
(ケースストラクチャの中にさらにケースストラクチャを入れてといった工夫をすれば対応できないことはないですが)
そういった場合には、いわゆるプログラム実行直後に必ず実行しておくべき処理群を「初期化プロセス」として決めた順で自動的に行えるように個別ケースに用意しておき、そのプロセス後にあらためてプログラム終了までに実行しうる処理は別途実行させる形をとったほうがより整理されたプログラムとして修正なども行いやすくなります。
上記の例では、元々が「制御器の値の初期化」と「iniファイルからの読み取り」を一つのinit displayケース(これがこのステートマシンの初期化ステート)で行って、eventステートに進んでいます。
これを修正し、init displayケースでは制御器の値の初期化しか行わず、その後にset variableステートに移ってiniファイルを読み取り、それらが終わってからeventステートに進むようにしています。
どちらもeventステートに進むことは同じですが、修正後のプログラムの方が、プログラムの途中で「iniファイルの読み込みだけやり直したい」という場合にset variableステートだけ実行させればよくなります。
同じことを行うステートが名前を変えて複数存在している状態はメンテナンス性も悪くなり好ましくないため、敢えてステートを細かく分けることも便利なことは覚えておくといいかなと思います。
本記事では、LabVIEWでプログラムを書くときのベースとなるデザインパターンの多くでみられる「初期化」の役割について具体的な例を交えて紹介しました。
プログラムによってやりたいことは様々ですが、とはいえ初期化処理としてよくやることはどれも似ていたりするので、特にまだステートマシンやその他デザインパターンに慣れていない方には参考になればうれしいです。
ここまで読んでいただきありがとうございました。
コメント