|laziness, impatience, and hubris|
(Ovid) Re: Linear programming for linear programs?by Ovid (Cardinal)
|on Mar 25, 2002 at 17:51 UTC||Need Help??|
petruchio had some good points and I love a rebuttal like this. It forces one to really dig into things. In the case of this post, any single good programming practice, when stripped of context and meaning, can become "cargo cult" programming. As a result, breaking things out like I have can be a bad thing if you don't understand other good programming practices. Thus, the objections that I see you raise become reasonable if the programmer doesn't understand the bigger picture.
Another consequence of 'storybook programming' is that there is a strong temptation to use global variables. The novice understandst that Irwin needs to deal with @expenses in most of the chapters, so he simply deals with it directly.
Global variables are not evil. They're just typically misused. Have you ever passed $x to &foo, which passed it to &bar, which in turn passed it to &baz? If the only purpose of passing $x to &foo was to let it get, untouched and unused, to &baz, then you can what's called "tramp data", which is just hanging around for the ride. It might be better to make that tramp data a global (with proper accessors, of course). Configuration information such as whether or not your program is running in debugging mode is often another reason to use globals. Globals are just misunderstood.
Since you write that, in this style of programming, the programmer is tempted to make everything global, that's merely because they don't really understand programming and my little suggestion isn't going to help them, anyway. Further, your argument that the programmer is going to be passing 20 variables to every function just furthers my point: the person has bigger programming problems than just learning how to modularize things.
It is my opinion that, most of the time, things which are done only once are better off not separated into subroutines... though I'm certainly not dogmatic on the point. I've seen cases where it seemed aesthetically sensible.
It all depends upon why the new subroutine is being created. It can often be good to hide complex pieces of code this way. Do you want the following?
Code like that really shows up. We've all had it sooner or later. The programmer who maintains that will often just glance at it and say "I'll figure that out later if I need to". Now, what if I take those simple tests and move them into subroutines, but never reuse them? My code is still much easier to read:
Note how the code has grown considerably, but it is much easier to understand the intent of the conditional. These small, easy to understand subroutines are self-documenting as is (now) the if statement. Of course, complicated conditionals are just one example. There are plenty of areas where hiding the data or process is a good thing.
Regarding your pseudo-code snippet:
Now, if the project changes, and you have to do A and B more than once, or C and D more than once, you're set. But perhaps task E gets added, and you need to do A to get ready for it...
Just to make sure that I understand what you're saying, for function &w above, you have actions A and B. Now, if you later need to do E, but have A happen first (but not B), then you have to refactor something that was possibly poorly factored in the first place.
In your example, this is correct. In fact, in the real world, this happens all the time. However, you have set up a bit of a straw man. The functions you describe are not cohesive. In function &w, as you describe it, B quite possibly requires A as a predecessor, but A does not necessarily require B as a successor. As a result, it probably should not have been grouped with it in the first place (though I admit that, in the real world, this is not always so obvious).
I tend to hold with the idea that each function should do one thing and do it well. That "thing", though, may be hideously complicated. That's okay if we have something like this:
In that simple little example that I made up, the arrow notation means that the left action requires that the right action succeed it, and the right action requires that the left action precede it. In the above example, what if we realized that, while E requires D for a predecessor, D does not require E as a successor? Then we have two functions:
The trick, though, is to figure out how to call function bar. You clearly need foo first, though foo is not required to have bar following. There are various ways to approach that, but at least the functions are correct and they're probably cohesive.
In my example in the parent node of this thread, each of the three functions is small and does pretty much one set of logically related activities (as per the notion of what I said above).
To summarize, you raise excellent points. A programmer who doesn't know how to structure a program well may very well be worse off following my advice. But I wouldn't have people shy away from good practices because they don't know other good practices. They'll never learn good stuff! :) They have to learn to put things together as a coherent whole.
Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.