Refactoring of Dan Bookwalter's LabVOOP VISA controller
by Stephen R. Mercer, LabVIEW R&D
Dan Bookwalter posted his first LabVOOP project to LAVA
and asked for feedback. He got a bit, but he wanted me to look at it in
closer detail. I figure that it is useful to have a "before and after"
refactoring for comparison. asked me to look at his first LabVOOP
project and comment on it. I took his starting point and applied
several transforms to it to produce a new project that did the same
thing. You'll note that the lowest levels of his application haven't
been touched. What I've mostly changed is the state machine behavior.
Before anything else, yes, there is a bug such that when you run the
top-level VI, the classes all get reseverd, but when you stop the VI
they do not unreserve. You can still edit the static VIs, but you
cannot edit the classes or the dynamic VIs. It has been CAR'd.
Workaround: If you close the project and re-open, the VIs will come
back unreserved.
Major points:
- The use of the ring control for the state machine:
After my refactoring, there is one and only one instance of the ring
control any where in this application -- on the top-level VI. I've left
this instance because it is a useful interface if you want the start
state to be user configurable and because "Set Command from Ring.vi"
demonstrates a use of the Factory design pattern,
for your education. But in all other places, I just use "Set
Command.vi" which directly sets an instance Command class into the ROBO
object. The states are now defined each as an individual class. Dan,
your intuition here was excellent, having two inputs to your original
"Do Command.vi". But rather than make them two instances of ROBO, I
made two separate classes. By actually separating these into separate
hierarchies, I can make things a bit cleaner to read, and my Command
class isn't carrying around all the VISA communication data that ROBO
needs. I've also merged the string refnum updating into the Command
class. I've now isolated the string refnum. That wire doesn't pass
through the rest of your structure, and there is no "Get Display
Refnum.vi", so no one except the command class can update that string
refnum. This limits some of the dangers of refnums -- it can only be
used by the Command class in the ways that the Command class intends,
not hacked by some random programmer who decides that string control is
a convenient place to store information or other nonsense.
- State transitions: Each
"Do Command.vi" override includes its own state transition. The
encoding for which state to go to next is entirely in the Do Command,
rather than back up at the top level.This could be done in a regular non-OO state machine. What makes this a bit different is the ease of adding
new states to the system or to change the transition from state A to
state B without editing the top-level VI. Inherit a new class from
Command and edit the various Do Command.vis from other classes to
transition to the new state. You never have to update the top-level
VI's case structure. You may have some very expensive states that you'd
prefer not to load into memory unless you have to transition to them.
This lets you dynamically add states to the system if you need that.
- Including Command in ROBO: I've
placed an instance of Command inside the ROBO class. This lets ROBO
represent the entire "state of the system" at all times, and lets us
just have ROBO in the shift register in the top-level VI. If at any
point you want to know the complete state of the system, just probe the
ROBO wire. This is something that I prefer when working on state
machines -- even down in deep subVIs I can tell what state triggered
the subVI to be invoked. When compared with the VISA data cluster, the
memory overhead is minimal.Personal preference
- Including error cluster in ROBO?
: This is something that needs more thinking about, but at the moment
I'm against folding the error code cluster into other data structures.
This gets debate in non-OO programming too. Should every bit of data
carry the ability to record when it is in an error state? There are
pros and cons on all sides. For example, floating point has NAN (not a
number) state, but nothing like that exists for integers, and it would
certainly be useful sometimes.
- Private subVI: The common
subVI shared between Set Command from Ring.vi and Set Command.vi is
private. It doesn't have the error checking and it shouldn't be part of
the public interface. So I made it private.
That's it for now. There may be other comments that others can make.
--- Stephen R. Mercer