Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Linear programming for linear programs?

by Petruchio (Vicar)
on Mar 25, 2002 at 13:37 UTC ( [id://154086]=note: print w/replies, xml ) Need Help??


in reply to Linear programming is bad

It's always interesting when I see a good programmer espousing something with which I strongly disagree. More interesting, really, than reading things I agree with; it makes me consider my own position. Sometimes I wind up changing my opinion, sometimes the other fellow (or lass) does, and sometimes we come to recognize a fundamental difference in premises, which usually means we've come to understand the whole problem better. I ++ you for starting an argument, in the best Socratic sense of the word. :-)

The practice you describe I have called 'storybook programming', and I have gone so far as to tell people to avoid it. Each subroutine is only called once, and the main part of the program consists in calling each of them in order. It's very much like a storybook with a table of contents and chapters:


The Accountant and the Ledger

  1. Wherein our Hero gets the Expenses
  2. The Summing of the Expenses
  3. A Report is Written

Chapter 1: Wherein our Hero gets the Expenses

Once upon a time there was a little accountant named Irwin, who lived in a cubicle. One day the Lord High Fiduciary visited the cubicle, and asked Irwin to create a financial report. So Irwin opened up the Big Boring Book of Expenses, and...


You get the idea. The chapters describe what happens, in the order in which it happens, and are mostly there to divide things up by topic. There's never a point in a storybook where it says, 'And Then he wrote another report... please go read Chapter 3 again.' And the chapter titles seem to describe what goes on in the chapters... which itself is a point of dread for me. Like comments, reading subroutine names is no substitute for reading code, particularly since things, as you've noted, tend to change over time.

I find that novice programmers often think this way, and that, for the naive, it leads to a predictable set of problems which are not wholly unrelated to the point I'm trying to make to an experienced programmer.

Because the subroutines describe the events in sequence, rather than the events which happen repeatedly, they'll often have redundancy... Irwin does the same thing in chapter 10 that he did in chapter 2, for instance. It's like unrolling a loop without a good reason; the result is awkward and unnatural.

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.

Soon someone tells him that he should be using strict, and so he does, and everything breaks, and he eventually solves the problem by turning the globals into package-scoped lexicals, which he declares at the beginning of the program, though he doesn't quite see why that was helpful.

Then he's told that he should really be passing these variables along as parameters... and so he begins the tortuous task of passing the same 20 variables along to every subroutine in the program.. and if he's got any sort of intuition, he realizes his program is getting steadily worse, for having taken all this good advice.

Now the problem, of course, is that the programmer is looking at the problem in a less-than-helpful fashion. Sure, he's divided up the task into parts... but those parts don't reflect the aspects of the problem which are important to him as a programmer. It's as if a surgeon-in-training were analyzing a human body by saying, "Well, there's a top half and a bottom half, certainly. And there's a left half and a right half... the body must have both of those, or it's not complete." These things are true, of course, but it's simply more useful to divide the body into functional units, such as organs.

Likewise, a programmer would do well to recognize that when a single task, or closely related set of tasks, is done multiple times, it is a good candidate for being enshrined as a subroutine. 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.

But you're certainly not a novice programmer. One would expect you to have a pretty good idea as to how to structure a program... indeed, structuring programs is the point of your post. So what's the point of mine?

Well, what you're doing strikes me as being something like premature optimization... guessing as what's going to be needed down the road. As you note, project requirements change, and features are added; and the thing that makes that a challenge is that you don't know what's coming. In making a 'naturally' linear program non-linear, you've made a guess at which tasks might need to be repeated.

If things change, and you guessed correctly, all is well. On the other hand, what if the way you divided up the problem turns out not to reflect the nature of the new problem? For instance, say you have to accomplish tasks A, B, C and D once each. So you write a program that looks like this:

w(); x(); sub w { A; B } sub x { C; D }

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. So you say:

sub y { A; E }

Now it seems A should have been a subroutine. And then it becomes necessary to perform D multiple times for each C... suddenly you're refactoring a problem that didn't need 'factored' in the first place. At this point, life would be easier if you had some linear code you could simply sequester into the subroutines which are now obviously appropriate.

Now, this description is rather abstract, and I'll have to beg your pardon for not supplying concrete examples... at the moment, I'm pressed to finish this post and do other things. In light of real examples, I think several things would likely become clear. One of which is, there's a measure of common sense involved. I'm not suggesting that a programmer should strictly refrain from making reasonable guesses as to what has a high probability of being needed tomorrow. But I do think there's a danger in it and I think there are good reasons to prefer your original, simple program. In my opinion, the solution should fit the problem... and linear problems deserve linear solutions.

Replies are listed 'Best First'.
(Ovid) Re: Linear programming for linear programs?
by Ovid (Cardinal) on Mar 25, 2002 at 17:51 UTC

    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?

    if ( (defined $request and $request>0 and $request<2000) and $acco +unt_num=~/^[Gg]\d{3}-\w{5,6}+/) { ... }

    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:

    if ( expense_in_range($request) and is_expense_account($x) ) { ... } sub expense_in_range { my $expense = shift; return ( defined $expense and $expense > 0 and $expense < EXPENSE_ +LIMIT ) ? 1 : 0; } sub is_expense_account { my $account = shift; return $account =~ /^[Gg]\d{3}-\w{5,6}+/ ? 1 : 0; }

    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:

    w(); x(); sub w { A; B } sub x { C; D }

    You wrote:

    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:

    function foo: A -> B -> C -> D -> E -> F

    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:

    function foo: A -> B -> C -> D function bar: E -> F

    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.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://154086]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (3)
As of 2024-03-29 05:55 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found