Re: Linear programming is bad
by chromatic (Archbishop) on Mar 24, 2002 at 22:51 UTC
|
I am certain Ovid knows this, but it's worth presenting a counterpoint. I have two rules for this situation:
- Code should be as maintainable as possible at all points in the development cycle.
- Code should do only what is absolutely necessary at the current state of the development cycle.
By maintainable, I mean that the intent of the code is evident, there is no duplication, and the names of items and any comments are sufficient so that a decent programmer can fix bugs or add features.
By absolutely necessary, I mean that there is no code that "might be used at some point in the future". If you don't need a feature now, don't pay for it yet. If you're writing an IRC client, don't add a web browser until it's absolutely necessary.
The not-so-subtle difference is that I argue to put off taking advantage of the possibility of modularity until you actually need to re-use a function or an object elsewhere. It's hard to convince me to invest in something that doesn't have an immediate benefit. | [reply] |
|
Your points seem to fit rather well with Extreme Programming ;-)
I agree with you that unecessary features should be avoided. I also agree with you that code should be as maintainable as possible during all stages of development. What I disagree with you on is the priority of the two and how they interact.
I would have broken Ovid's code down into subroutines like he did in the second example in his post. I find code in such a format is far easier to maintain and ultimately saves a lot of time. So if the modular version of the code is more maintainable, then in order to adopt its use, the benefits of increased maintainability should outweigh the costs of adding a 'feature.'
Let's examine the costs then. There are three reasons why I avoid adding features that are not absolutely unecessary:
- It introduces more potential bugs and in doing so can greatly reduce the usability and security of the product.
- It wastes time and money for features that may not ever be used.
- It makes the codebase harder to maintain. This is especially important in open source projects where you want to attract developers. If your code is a bloated mess they're far less likely to get involved.
If at the beginning of a project you decide to modularize the code, you will not be introducing any additional bugs. In addition, if modularity is put off until later, and then required, programmers may introduce new bugs during the transition.
Number two may partially apply. It may take extra time to set up the program as modular (then again it may not), but this time is not spent on a wasted feature, it is spent on making the program more maintainable and reusable. Assuming you agree that modular code is more maintainable, number three gives the advantage to modularity as well.
So you have to weigh a possible small price/time increase against greater long term maintainability. I find that I consistantly value maintainability enough to spend the extra time modularizing my code.
| [reply] |
|
There is a definite tradeoff, and it would be silly (or arrogant) for me to write an authoritative rule. Your point about adding modularity potentially introducing bugs is spot on correct. Of course, test freaks would suggest that unit tests will catch that. (Hopefully...)
Besides the complexity concern, I hate to be stuck with a bad interface for backwards compatibility reasons. If you modularize too early, there are two yucky possibilities. Either your interface is too limited for the general uses and you'll have to rewrite code anyway, possibly creating bugs, or you'll have to write an overly generic interface, adding more complexity in the hopes that you'll catch all the ways you might want to call the function.
In my experience, by the third time you use a piece of code, you'll know enough to write a good interface.
Besides all that, the cleaner your code, the easier it will be to modularize. By the time I get over 100 lines, generally I'm writing functions anyway. There's a definite intuitive understanding that's not coming across in my posts very well...
| [reply] |
|
Linear programming for linear programs?
by Petruchio (Vicar) on Mar 25, 2002 at 13:37 UTC
|
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
- Wherein our Hero gets the Expenses
- The Summing of the Expenses
- 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.
| [reply] [d/l] [select] |
|
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. | [reply] [d/l] [select] |
Re: Linear programming is bad
by andreychek (Parson) on Mar 25, 2002 at 00:30 UTC
|
For just about every case, I would have to agree with Ovid's thoughts on this subject. Particularly his point in the following:
Ugh! What the heck have I done? I took a simple, straightforward program, added three subroutines and several lines of code. Why the heck would I do something like that? What happened to laziness?
This is a trick question, as it isn't actually laziness we're seeing here. Larry calls this false laziness! Laziness isn't about always trying to do the least amount of work now -- laziness is about having the correct program design so that we don't end up rewriting the whole thing every time you need to add or modify a feature. Laziness is about saving time in the long run, which possibly may require spending some extra time up front.
The following is a direct quote from the Camel book, page 993:
Laziness
The quality that makes you go to great effort to reduce overall energy expenditure. It makes you write labor-saving programs that other people will find useful, and document what you wrote so you don't have to answer so many questions about it. Hence, the first great virtue of a programmer. Also hense, this book.
I think Ovid does a fine job at expressing the intent of the Camel book, and more specifically, the 3 virtues of a good programmer, with his above thoughts. Two thumbs up!
-Eric
| [reply] |
Re: Linear programming is bad
by seattlejohn (Deacon) on Mar 24, 2002 at 22:59 UTC
|
Good comments on programming style, but you might want to choose a name other than "linear programming", as that term was long ago adopted (with no negative connotations that I'm aware of) by the kind of folks who try to solve optimization problems. More info here if you really want to know. | [reply] |
|
Aack! I just knew that was going to happen. It just seemed like the most straightforward description. I'm open for suggestions. A free, random ++ to the first monk to come up with a better name :)
It bugs me because there is a better name and I know I've seen it somewhere; I just can't remember ... grr!
Cheers,
Ovid
Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.
| [reply] |
|
| [reply] |
|
| [reply] |
|
|
| [reply] |
|
| [reply] [d/l] |
|
|
I've occasionally referred to it as "waterfall programming", since you start at the top of the code, and write straight down to the bottom. *shrug*
"As information travels faster in the modern age, as our days are crawling by so slowly." -- DCFC
| [reply] |
|
|
Ovid:
It bugs me because there is a better name and I know I've seen it somewhere; I just can't remember ... grr!
I recall a term Drop-through programming for what I think you mean with (Operational Analysis reserved word) "lineair" programming. A drop-through program is a program where the execution starts at the top of the file, drops through the code and stops at the end of the file (this is not to be named "top-down" which is also reserved allready). That makes sense I think - though english is not my native language. As soon as you introduce a subroutine you have to decide where to put it: on top? at the end? In the middle? Anyway somebody (the programmer or the language-programmer) has find a way to exclude it (this first subroutine) from the drop-through execution flow, so that it only gets executed when you want it executed.
HTH,
Danny
| [reply] |
|
narrative programming?
for its and-then-and-then-and-then structure.
but then i was thinking, maybe just 'programming'? most things seem to start this way and only deserve special names later when their true nature is clearer. but then i am given to 'making it up as i go along programming' . perhaps some people know what they're building :(
| [reply] |
|
How about crow flight programming, as in "as the crow flies" ???
Hmmm, maybe not.
Matt
| [reply] |
|
This would seem to be a slightly refined version of what I call "programming by typing".   You start at the top and take care of each thing as it comes up until you're done.   Perfectly good idea for programs less than, oh, say five lines.
  p
| [reply] |
Re: Linear programming is bad
by Stegalex (Chaplain) on Mar 25, 2002 at 14:00 UTC
|
While nobody likes to write or read excessive storybook comments and while the structure of a storybook (or top-down) program is equally uninteresting and simplistic (perhaps it may be too verbose), my opinion is that these dull practices make it simpler for *other* people to maintain your code. *Other* people are not always as brilliant as you or I and they don't have the same knowledge of your code that you have. Since you may be hit by a bus someday, you owe it to your clients to employ these dull and sometimes patronizingly simple programming techniques.
I like chicken. | [reply] |
Re: Linear programming is bad
by Buzz (Novice) on Mar 25, 2002 at 06:41 UTC
|
linear programming... I think its called top down programming, or top down design. Or at least that's what I was taught in high school. | [reply] |
Re: Linear programming is bad
by Maclir (Curate) on Mar 25, 2002 at 16:28 UTC
|
++ Ovid. Us old COBOL programmers would structure our programs using what Petruchio calls "Storybook" programming (and, in COBOL, would have been just as verbose).
The key is structuring the subroutines thougtfully, and to allow reuseability. | [reply] |
Re: Linear programming is bad
by muesli (Novice) on May 20, 2002 at 20:50 UTC
|
I totally agree with this attitude and programming style. The biggest effect I like is that the purpose and algorithm of the code is clearly written, at the start, in the form of the procedure calls.
But in reality, I can't remember thinking about this as an issue: I just automatically program like this - it wouldn't occur to me to do it another way, and I'd probably refuse to. Same thing with going on a larger scale and talking about OO programming or refactoring. I would never just write new functionality into an existing class that made no sense there. I just do it correctly at the beginning - say, creating two extra classes to implement orthogonal functionality. Same with documenting my work. As far as I'm concerned, the thing isn't done until it's documented.
(This has gotten me into trouble with previous employers, who've said they have no time for comments or documentation. To me, though, it's part of the product.)
I think that Ovid starts out with the right propositions (don't add functionality you don't need - features that might be handy in the future), but then draws a conclusion I disagree with: That this style of coding is a feature that's optional functionality.
An auto engineering example:
* XP Done right: You don't want to build 4 cigarette lighters into a car because we think some people might want to run 4 accessories at the same time, and therefore we ought to support that hypothetical case.
But my whole point is that this isn't what we're talking about:
* Analogy with proper coding/documenting: You do put in seatbelts in cars, although strictly speaking, you don't need them to get from point A to point B. Because, as professionals, that's just how cars are made.
PS: In software, the end user is usually another programmer. If a programmer gave me long, or un-idiomatic code that's all one procedure and not documented, I'd give it back to them and say that their job isn't done yet. (And I have done this!) | [reply] |