(10) Good programmers don't repeat themselves, true, and to continually improve as a programmer it is important to patiently do everything correctly the first time. However, obsessing over a few bits of code duplication is often a complete waste of time.
Applying good coupling and cohesion principles is not obsessing. It's as much legitimate programming as writing an equality test.
Perfect programmers may manage to avoid duplicating code on the first try. But unless we have dozens of Mozarts posting here I'd guess that no one qualifies. That means we make mistakes, and the question of whether correcting those mistakes is good practice or obsession depends on the circumstances.
I think from your comment, we can agree that there are two scenarios to consider: when the programmer is in control of the product and when the schedule is in control of the product. "Programming is not always so time-and-performance-sensitive, but it's not time-insensitive either."
Here's the deal: when you stop your design process and kick the compiler up and start coding, you haven't stopped your design work at all, you've simply switched from top-down to bottom-up design. Every line of code you write is a validation/denial of the assumptions and planning you're supposed to do before you start. If you're not refactoring aggressively as you go along, you're not taking the chance to see where your code is supposed to be going: you're simply trying as hard as you can to get to point B from point A. It's like driving a car from one side of the field to the other by letting go of the steering wheel and hoping it gets there okay. Most of the time you make it to the other side just fine, but you have zero control over where on the other side you're going to end up.
To me, that's hacking, and I mean in a bad way. I've been known to hack like a freaking electric weed-eater, trust me! But that's when something else is out of whack on the project. It's a sign that there are serious problems. When you're doing it right, your code is constantly evolving as you refactor to the point where changes become less and less of an issue. So good programming means you spend all the time necessary to refactor with the knowledge that you'll going to make it back in spades later on.
It's like when playing rummy, and you decide to pick up a huge stack of cards. At first, it seems like a crazy move, but if you know how to play the game, you realize that the only way to win is to constantly redefine the problem domain of what you have in your hand. Hacking doesn't get you that -- it just gets you to point B no matter what. It may not be the sign of poor programmer, but it's definitely a red light that something is out of whack somewhere.
My opinion only. Your mileage may vary. Do not attempt to drive heavy machinery after reading my comments. Consult a physician before beginning a regimen of refactoring. Etc.
A simple dilemma:
You have to perform an operation in C that accepts up to 5 parameters and will be executed when a given 50 input sets are found (out of thousands of possibilities).
You can write one function that accepts variable parameters and paste it into the code for each of those 50 conditions. (Assuming you know how to do that in C to begin with -- I don't, actually)
Or you can write 5 different functions for each possible number of parameters, with some duplication in each function. Or you can write one function with 5 parameters and just pass 'NULL' in the unused slots and test for it inside the function.
Or you can come up a few meaningful sub-categories and write 15 different functions that also handle some processing that would otherwise happen further down the road.
Or you can write 50 different functions that correspond to each input condition, that do mostly the same thing at this point, but in the future if you needed to change any particular input set side-effects will be limited.
How do you answer this question without being able to see the future? (And, is it even worth worrying about, when any of the answers will work?)
For you, maybe. But what about the guy who applies enhancements 3 years later and has trouble getting through testing because the exact same code was somewhere else and difficult to find because of the 67 mods since then.
Minutes "obsessing" today usually saves hours testing tomorrow.
This also squares with a lot of IBM's experience in The Mythical Man Month, i.e. code needs at least three clients to really be reusable.
True, but the app has to make it to tomorrow for testing to matter at all. It's better to be in the championship trying to figure out how to manage a tired team, than it is to conserve energy during the semifinals and wind up sitting in the bleachers.
Doing things according to effective established principles saves a little time today and a lot of time tomorrow.
Quick and dirty usually ends up being neither.
I don't have that idea. There is sometimes, though, a tradeoff between current needs and future needs; and that tradeoff can not always be predicted. When that happens, it's logical to give current demands priority.
I can't for example, predict whether a future maintainer of my code would prefer code written with multiple returns, or code that sets temporary values, or code that uses a set of nested ifs, or code that assigns function pointers to a matrix and calls code based on the indices. Each technique has potential drawbackss in terms of future maintainability, likelihood of errors, and speed of immediate implementation. Trying to guess what a future coder (other than myself) will think is just not important in that case.
Effective Principles can only be Established based on a given development environment, and it's the responsibility of those in charge of the environment to make sure the established principles are effective and followed by the developers.
The fact is, there's almost always a trade-off between "quick" and "right". That's why so many people choose the former, to the detriment of the latter.
Wow. Not every problem calls for recursion. If you really hate early exits from loops, maybe you should just code the loop's condition differently.
Exactly.
Like I mentioned before, I'm the poor schmuck that has to clean it up because someone didn't bother to take your (common sense) advice years ago.
http://www.csd.uwo.ca/~yuri/Papers/pami04.pdf
It's really great, for the record. Anyway, the algorithm they wrote uses an early exit. In fact, they use a while(true), the "worst case" example.
initialize: S = {s}, T = {t}, A = {s, t}, O = {(Empty Set)}
while true
grow S or T to find an augmenting path P from s to t
if P = {(Empty Set)} terminate
augment on P
adopt orphans
end while
In programming this algorithm, sure, I could probably move the first grow before the loop, and grow at the end. But, now its harder to understand, and violates the afore mentioned copy paste rule.My point is this, take any such "rule" with a grain of salt. There are plenty of examples where it makes sense to do something atypical.
Put it in a common function. Sure, it may look a little frazzled today, but generations of future hackers will thank you.
while(readLine()) { process lines... }
where readLine() encapsulates the repetition and returns eof as well.
I was trying to point out that the idiom becomes while(line=readline()) which I'd rather avoid since = and == often get confused in a conditional.
It would normally be silly to get into this level of detail but I use this so often.
Which is more expensive, a few milliseconds of machine time a few million times or a whole bunch of human time once or twice?
Meaning what exactly? A language that automatically optimized for tail recursion? Are there languages which don't support recursion "properly?"
edit: I just realized what I actually mean. Some programming languages, or the culture/community around them, do not seem to encourage certain things that can be done with the language. In the case of C/C++, while/do/for loops are used before recursion. In the case of Scheme, you try a recursive solution first and then go for a do-loop if you really need to...or something like that.
10. The exact same code is in multiple places because you didn't bother to put in into a common function.
9. You have error codes in functions, but never bother to look at them (a sure sign that testing was never finished).
8. You have early exits from loops because you don't know how to properly code a recursion.
7. You execute too much code because you don't know the difference between "if" and "case".
6. You use 10 lines of code when 1 line would do because you don't know any better.
5. You need to run batch jobs from time to time to clean up your data base because you don't know how to debug well enough to find what's messing it up.
4. A third grader doesn't know what your variable names mean.
3. You think "Third Normal Form" is paperwork you need to fill out for one of your 8 bosses.
2. No programmer who has to maintain your code would ever want to work with you.
1. Any programmer that has to maintain your code would be more than happy to accept the consequences of first degree murder.