Jump to content

A weird way to improve your object-oriented programming skils


Recommended Posts

Ok... this is just weird. Up front: I have not tried this exercise. I have heard positive things about it from other programmers.

The link below is to an article that recommends writing 1000 line C++/Java/C# program with 9 insane restrictions on the code you write. These are restrictions that would probably be harmful if rigorously enforced in production code -- performance hits, slow development time, etc. However, as a learning exercise, they are intended to get you to think about your program in a more object-oriented manner. They force you to break certain habits you may have developed when programming procedurally.

http://binstock.blogspot.com/2008/04/perfe...-and-short.html

REMEMBER: These are not intended to be rules for actual production code. They are restrictions during practice.

If we translate this exercise into LabVIEW, the challenge would be as follows:

================================================================

Your challenge is to write a LV application with the following restrictions RIGOROUSLY enforced. Consider writing a VI Analyzer test to make sure they're enforced.

I'll offer this as your challenge application: <labview>\examples\general\queue.llb\Queue Stack - Solve Maze.vi

Don't look at the block diagram before attempting this exercise. This example has shipped with LV since LV6.1, so everyone should be able to find a copy.

  1. No nested structure nodes. If you want to put a structure inside another structure, you need to create a subVI for the inner structure. The challenge is to meaningfully name that subVI. An exception can be made for the Event Structure, the Inplace Element Structure, and the two code disable structures. You can also have an exception for the Case structure that tests "error in" to skip a subVIs execution.
  2. Any Case structure must have a boolean input or an error input. In the "true" or "no error" case, you may write code. In the "false" or "error" cases, you may only pass inputs through to outputs. This prevents if-else chaining; and every routine does just one thing.
  3. No controls or indicators on any front panel may be connected to the VI's connector pane except LV class controls/indicators and error code clusters. This directly addresses "primitive obsession." If you want a function to take an integer as an input, you first have to create a class that wraps that integer to identify it's true role. This means that zip codes are an object not an integer, for example. This makes for far clearer and more testable code. And it prevents operations (such as Add) from being performed on zip codes.
  4. This restriction is already enforced by the LV language normally. Use only one dot per line. This step prevents you from reaching deeply into other objects to get at fields or methods, and thereby conceptually breaking encapsulation.
  5. This restriction is really not applicable to LabVIEW, since we don't type the text names of VIs. But the sentiment about naming your VIs well is still relevant. Don't abbreviate names. This constraint avoids the procedural verbosity that is created by certain forms of redundancy—if you have to type the full name of a method or variable, you're likely to spend more time thinking about its name. And you'll avoid having objects called Order with methods entitled shipOrder(). Instead, your code will have more calls such as Order.ship(). In LabVIEW, if you have a class named Matrix, you might have a member VI named DotProduct.vi. You should not name it MatrixDotProduct.vi

  6. Keep entities small. For any class, the private data control's front panel AND the block diagrams of ALL member VIs must be able to fit side by side, without overlapping, on a 1024x768 screen. This restriction forces concision and keep classes focused. It means that developers can take in the whole class in a glance.
  7. Don't use any classes with more than two elements in the private data control. So if you think you need 4 elements in the private data control, consider how you could create a class to wrap three of them and then include that other class as the second element. This is perhaps the hardest constraint. Bay's point is that with more than two instance variables, there is almost certainly a reason to subgroup some variables into a separate class. Commentators noted classes such as 3DPoint -- which needs 3 elements for x, y and z axis. This was answered that you could as easily have a three-element array as the member. The similarity of the three elements means we can group them; if they were sufficiently dissimilar that such grouping was not possible, then we would probably be able to create another meaningfully named class.
  8. Use first-class collections. In other words, any class that contains an array in its private data control should contain no other elements in the private data control. The idea is an extension of stopping primitive obsession. If you need a class that subsumes the collection, then write it that way. A good example is that 3DPoint mentioned earlier -- the array needs to be checked at the API boundaries to make sure it never has more than three elements in it.
  9. Don't use accessor VIs. Any member VI that unbundles or bundles data from the class should do something with that data, not just set or get the value. This is a radical approach to enforcing encapsulation. It also requires implementation of dependency injection approaches and adherence to the maxim "tell, don't ask."
  10. This restriction is one I'm adding for LabVIEW -- ALL VIs in the application must be members of some LabVIEW class. The author of this challenge has this as an unspoken assumption, but I figured it should be explicit for LabVIEW programmers.

================================================================

As I said before, I have not tried this exercise myself, so I cannot testify to its effectiveness. If anyone tries this, please post about your experience.

Link to comment

QUOTE (Aristos Queue @ Jul 15 2008, 02:22 PM)

Ok... this is just weird. Up front: I have not tried this exercise. I have heard positive things about it from other programmers.

The link below is to an article that recommends writing 1000 line C++/Java/C# program with 9 insane restrictions on the code you write. These are restrictions that would probably be harmful if rigorously enforced in production code -- performance hits, slow development time, etc. However, as a learning exercise, they are intended to get you to think about your program in a more object-oriented manner. They force you to break certain habits you may have developed when programming procedurally.

http://binstock.blogspot.com/2008/04/perfe...-and-short.html

================================================================

As I said before, I have not tried this exercise myself, so I cannot testify to its effectiveness. If anyone tries this, please post about your experience.

I could set this up as the next coding challenge but I would need volunteers to help with the judging. If anyone is interested in judging and or participating please respond to this post. This sounds like a great NI WEEK topic over a few beers.

Thanks Aristos you are still my Hero.

Link to comment

A smart-alec co-worker pointed out that the rule about "only classes can be on the conpane" can be easily handled by passing everything to subVIs using global variables/queues/etc. This suggests that an additional restriction should be added for the hackers among us. However, since it is the nature of us programmers to see how far we can get within a set of constraints, adding that restriction would only lead to a long list of hack-prevention measures. So, let me say that the challenge can only have positive benefits if you willingly accept the restrictions. If you see evading the challenges as the goal, you will develop yourself as a hacker, but not as a software engineer.

Link to comment

QUOTE (Aristos Queue @ Jul 15 2008, 11:22 AM)

[*]This restriction is really not applicable to LabVIEW, since we don't type the text names of VIs. But the sentiment about naming your VIs well is still relevant. Don't abbreviate names. This constraint avoids the procedural verbosity that is created by certain forms of redundancy—if you have to type the full name of a method or variable, you're likely to spend more time thinking about its name. And you'll avoid having objects called Order with methods entitled shipOrder(). Instead, your code will have more calls such as Order.ship(). In LabVIEW, if you have a class named Matrix, you might have a member VI named DotProduct.vi. You should not name it MatrixDotProduct.vi

I would register a minor objection to this one, because LabVIEW won't allow static methods in a class to have the same name as static methods in an ancestor or child class. I regularly name VIs things like "Matrix Dot Product.vi" (if it was a member of Matrix.lvclass) instead of "Dot Product.vi" simply to prevent myself from clobbering that name later.

The overarching point about naming, however, is still relevant.

Link to comment

QUOTE (Justin Goeres @ Jul 15 2008, 04:06 PM)

I would register a minor objection to this one, because LabVIEW won't allow static methods in a class to have the same name as static methods in an ancestor or child class. I regularly name VIs things like "Matrix Dot Product.vi" (if it was a member of Matrix.lvclass) instead of "Dot Product.vi" simply to prevent myself from clobbering that name later.

The overarching point about naming, however, is still relevant.

Objection not accepted on the grounds that two VIs with the same name should be performing the same operation. If the parent class has a method called "Dot Product.vi" and the children also need a method named "Dot Product.vi", then the children are presumably overriding their parent's behavior -- thus the method should be dynamic dispatch, not static dispatch, and there's no problem. If the children are not overriding -- and thus the desire is for static dispatch -- then the VIs have no business being named the same thing. The reason for the "can't have same name as static in parent" rule is that the functionality "Dot Product" has been defined by the parent and the child must accept that or it isn't really a proper child.

Let's take the very common case of "Init.vi". If the parent class needs an Init -- which does not have a class in but does produce a class out -- then you inherit from it, you may very well want an Init.vi on the child class. But this is a case of a concrete class inheriting from another concrete class, and the solution is to add an abstract parent class from which both of the concrete classes inherit.

Link to comment

QUOTE (Aristos Queue @ Jul 15 2008, 02:31 PM)

Objection not accepted on the grounds that two VIs with the same name should be performing the same operation. If the parent class has a method called "Dot Product.vi" and the children also need a method named "Dot Product.vi", then the children are presumably overriding their parent's behavior -- thus the method should be dynamic dispatch, not static dispatch, and there's no problem. If the children are not overriding -- and thus the desire is for static dispatch -- then the VIs have no business being named the same thing. The reason for the "can't have same name as static in parent" rule is that the functionality "Dot Product" has been defined by the parent and the child must accept that or it isn't really a proper child.

Point taken, at least for the scope of the problem you're proposing.

This occurs more often for me with set/get methods, where one class will have a data member named Port that's say, a VISA resource, while a sibling of it has an item named Port that's a TCP/IP port number. But since you outlawed accessor methods in Rule #9, that's orthogonal to the problem. :P

Link to comment

Since we can't look at the block diagram, can you tell us how the maze will be defined? At first I thought the front panel defined the maze, but creating walls by changing the colors doesn't seem to do anything. It looks like the front panel array is simply a representation of a maze predefined on the block diagram.

Link to comment

QUOTE (mballa @ Jul 15 2008, 09:35 PM)

I could set this up as the next coding challenge but I would need volunteers to help with the judging. If anyone is interested in judging and or participating please respond to this post. This sounds like a great NI WEEK topic over a few beers.

Thanks Aristos you are still my Hero.

This is a good idea. I don't have that many experience in LVOOP, but I could help judge.

Ton

Link to comment

QUOTE (Aristos Queue @ Jul 15 2008, 09:22 PM)

No controls or indicators on any front panel may be connected to the VI's connector pane except LV class controls/indicators and error code clusters. This directly addresses "primitive obsession." If you want a function to take an integer as an input, you first have to create a class that wraps that integer to identify it's true role.

Don't use accessor VIs. Any member VI that unbundles or bundles data from the class should do something with that data, not just set or get the value. This is a radical approach to enforcing encapsulation. It also requires implementation of dependency injection approaches and adherence to the maxim "tell, don't ask."

Playing around with this, my largest challenge is to initialize a class. Suppose I have a 'collection.lvclass' which has an array of LVobject in its private data. I want to get an element from the collection by index. I can write a method 'get collection element by index' which would be implementing the 'array index' primitive, in the end, but I can't write an integer to it according to the rules. So I encapsulate the index in the private data of an Index.lvclass. Problem number one: how to set the index without using an accessor? I could work around this by writing a method "increment" that works on the index (by incrementing it...) so if I want to access element number by I just call "increment" 5 times. But this only works because its an integer. Turning to the collection: how do I initialize it? Say it contains strings: I can't write a string or an array of strings to an 'init' routine because of the rules. I could choose to encapsulate it, but that only moves the problem... I would have to init these new classes....

Edit 1: I just realized that a "constant" is an incomplete TypeDef and that a TypeDef is a LVclass with some default value and no methods. So I could just init my collection by first defining an enourmous lot of such classes and then adding them one by one to the collection

Edit 2: It seems that the collection problem is also raised in the origional post quoted by AristosQueue.

Link to comment

QUOTE (Daklu @ Jul 15 2008, 07:49 PM)

An excellent point. Let's use this...

This text block represents the initial array. It is a 2D array of uInt16 which I ran through "Flatten To String" and then displayed in hex mode. Paste this text into a string control that is set to hex mode, then use Unflatten From String to recover the 2D array.

0000 000F 0000 000F 0001 0001 0001 0001 0001 0002 0001 0002 0001 0002 0001 0001 0002 0001 0001 0002 0001 0002 0002 0001 0001 0001 0001 0001 0002 0001 0002 0001 0001 0002 0001 0001 0001 0002 0002 0002 0002 0001 0002 0002 0001 0001 0002 0001 0001 0001 0002 0001 0001 0001 0001 0002 0001 0001 0001 0002 0001 0002 0002 0001 0001 0002 0002 0002 0002 0001 0001 0002 0002 0001 0001 0001 0001 0001 0001 0001 0001 0001 0001 0001 0002 0001 0001 0001 0002 0001 0002 0002 0001 0002 0002 0002 0001 0002 0001 0001 0002 0001 0002 0001 0002 0001 0001 0002 0001 0001 0001 0001 0002 0002 0001 0002 0001 0002 0001 0001 0001 0002 0002 0001 0001 0002 0001 0002 0001 0001 0001 0001 0001 0001 0002 0002 0001 0001 0001 0001 0001 0002 0001 0001 0002 0002 0001 0002 0001 0001 0001 0001 0002 0001 0001 0002 0001 0002 0002 0001 0001 0001 0002 0002 0002 0002 0002 0001 0001 0001 0001 0001 0001 0002 0001 0002 0002 0002 0001 0001 0001 0001 0001 0001 0002 0001 0002 0002 0002 0001 0001 0001 0001 0002 0002 0002 0002 0001 0002 0001 0002 0001 0001 0001 0001 0002 0001 0002 0001 0001 0001 0002 0001 0001 0001 0001 0001 0002 0002 0001 0002 0001 0001 0001 0002 0001 0001 0002 0005

The front panel itself you should be able to use directly.

[by the way, the reason for not looking at the block diagram is that it is completely not an OO solution and for this exercise you don't want to taint your thinking.]

QUOTE (Dirk J. @ Jul 16 2008, 04:33 PM)

Edit 2: It seems that the collection problem is also raised in the origional post quoted by AristosQueue.

Hmmmmm.... Yes, this sounds like a classic "How do you write Matrix times Vector if neither Vector nor Matrix can touch the private data of the other one?"

For the index problem, I suppose you could write a function Get Value At Index.vi which does the following:

post-5877-1216253250.png?width=400

I mentioned earlier that you should try to work within the rules and not try to hack around the rules. Honestly, I am not sure if this solution is hacking around the rules or working within the spirit of the rules.

And then, after writing this, I realized that I still have a boolean output from one of my member VIs. This would be solved in C++/Java/C# by having this be a class output that had been taught how to behave as a boolean. We don't have that capacity with LabVIEW classes today*.

I think that some more contemplation of the rules of this challenge may be required before we can really attempt it.

* LV R&D does not make promises regarding future functionality.

Link to comment

QUOTE (Aristos Queue @ Jul 17 2008, 02:11 AM)

Naaah, I don't think this is hacking, because you're not obviously violating some rule (ok, the boolean, but see below). If I'd want to hack, global variables are obviously the way to go around any of the rules, (also the rules of dataflow :-) but that takes the sport out of it. Using a LV2 global or whatever it's called today, is getting trickier, because of the infamous rule #10 that everything should be part of a class. I can use the Ubergeek-Nirvana but that still requires me to initialize the action classes in some way which is hard without setters.

Also in the twighlight zone, is trying to get around the gettersetter issue by wrapping type in in array of type first, and wrapping that in a class. The getter would then return the first element of the array of type, the setter would replace the first element of the array of type and thus perform some action on the data. But then again, even such a class is difficult to initialize since you can't wire type to anything.

QUOTE

And then, after writing this, I realized that I still have a boolean output from one of my member VIs. This would be solved in C++/Java/C# by having this be a class output that had been taught how to behave as a boolean. We don't have that capacity with LabVIEW classes today*.

*
LV
R&D does not make promises regarding future functionality.

You can get around the 'boolean problem' by creating an other index class, increment that in the for loop, and test for equality with the input index class:

post-3523-1216278178.jpg?width=400

QUOTE

I think that some more contemplation of the rules of this challenge may be required before we can really attempt it.

The getter setter rule is also problematic if a child class wants to access data from its parent class. Rule #4 (naturally inforced) and #9 seem to clash here?

Maybe the language-specific overall hack prevention rule should be that everything should run by-wire and by-value. Globals are out, Queues are out, FileIO is out, ...

And maybe something like "each class is allowed a constructor and destructor in which getters and setters are allowed".

Link to comment

QUOTE (Dirk J. @ Jul 17 2008, 03:03 AM)

And maybe something like "each class is allowed a constructor and destructor in which getters and setters are allowed".

Although I tend to agree with the observation, I can see another possible hack with this: What if you create an object and destroy it right away just to get access to the get/set features of that class? Defeats the purpose of the exercise.

Link to comment

QUOTE (normandinf @ Jul 17 2008, 02:25 PM)

Although I tend to agree with the observation, I can see another possible hack with this: What if you create an object and destroy it right away just to get access to the get/set features of that class? Defeats the purpose of the exercise.

You're probably right. I just realized I started the sentence with "And maybe" ... that would probably invoke a lot more "And maybe"'s.

Still, can it be done then without accessors?

Link to comment

QUOTE (Dirk J. @ Jul 17 2008, 07:51 AM)

You're probably right. I just realized I started the sentence with "And maybe" ... that would probably invoke a lot more "And maybe"'s.

Still, can it be done then without accessors?

I kicked this around last night. I don't have any examples to post, but I think the answer might lie in focusing on this phrase: QUOTE

adherence to the maxim "tell, don't ask."

Suppose the answer is this: You shouldn't ever need to index the array from outside the array class. Why are you indexing the array? In order to do something to an element of that array. Shouldn't the index that we need to operate on be something that the array class itself calculated? If so, its own private data might contain an element for "active element", similar to how the multi-column listbox property nodes work.

Search around on Google for commentary about this challenge... you'll find some interesting tidbits. Those tidbits make me think we're trying to solve the wrong problem.

(PS: This actually is turning into a useful challenge, but you may not hear much from me on it for a while; I'll be traveling next week.)

Link to comment

QUOTE (Aristos Queue @ Jul 17 2008, 04:50 PM)

I kicked this around last night. I don't have any examples to post, but I think the answer might lie in focusing on this phrase: Suppose the answer is this: You shouldn't ever need to index the array from outside the array class. Why are you indexing the array? In order to do something to an element of that array. Shouldn't the index that we need to operate on be something that the array class itself calculated? If so, its own private data might contain an element for "active element", similar to how the multi-column listbox property nodes work.

Search around on Google for commentary about this challenge... you'll find some interesting tidbits. Those tidbits make me think we're trying to solve the wrong problem.

(PS: This actually is turning into a useful challenge, but you may not hear much from me on it for a while; I'll be traveling next week.)

I get the 'active element' solution. I still run into problems when I have collection of encapsulated strings, and want to display one of them in an indicator on the front panel. Somehow I have to get a string out, or get a reference to the indicator in.

Link to comment

QUOTE (Aristos Queue @ Jul 17 2008, 06:58 PM)

You know, a reference class is a perfectly valid form of class. I think the control references or any reference classes built with GOOP Toolkit would pass the test for being part of the conpane.

Ah, you said the magic words. That solves a whole lot of input/output problems.

(it does feel like hacking if a child class wants to access members of its parent class this way - but maybe I haven't thought long enough about that, yet)

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.