I have to give credit to Jason Doucette, of xona.com, for reminding me of the topic I wanted to blog today. He said, "To be productive, you truly have to choose what not to do, and avoid it, even though you'll want to do it. Otherwise, it never gets done."
The difference between a good software project and a bad one is that the bad one is in a free-fall of runaway complexity, while a good one merely teeters continuously on the edge of it. There doesn't seem to be a way to build software that doesn't run the continual risk of running amok in it's own sprawling subroutines. It is not the software that has a problem - it's perfectly happy growing wild and wooly. But people can only take so much complexity before their heads explode.
If you just don't care about quality, if you don't have any passion for your work, the statement I quoted about being productive probably doesn't apply to you. You're not going around writing extra code as it is, and that's ok - there's not much I can say that will affect you.
The people I'm trying to reach are those who want to be great programmers, but who perhaps haven't yet found the path. Because you can be very smart and work very hard, only to find that the amount of work required to introduce each change to your beautiful software architecture grows faster than you can keep up with. What happens is that each feature suggests a symmetrical feature as well. If you have a list and a function to add to the list, don't you also need a delete? Not necessarily - if your application doesn't ever delete items from the list, don't write that code. You can write it when and if you do use it - who knows; in ten days you may change your architecture, and not even use that list at all!
You have to be something of a perfectionist to write solid code - you also have to be willing to put up with incompleteness if you want to finish software projects, and that can be a bitter pill to swallow.
I think the reason I had so much trouble with this, is that I learned to program by reading libraries. In the many years I spent learning to program before I had internet in my home, the finest code examples I had were contained in the standard language libraries and headers which came with my compilers. The quality of the code that goes into compiler libraries and language frameworks is usually exceptionally good. What I'm worried about is that smart programmers will fail to grasp the fact that code in a library is targetted for a completely different use-case than code in an application is.
When you look at only libraries and frameworks, you get a skewed idea of which things need to be done and which do not. You risk developing a lop-sided aesthetic sense that values the completeness of a class's API over completeness of the project. When I look at some of my older programs, I realize that what I had were layers upon layers of homemade libraries, building up like a pyramid until at the pinnacle you have one library call which sums up the whole program.
So what's wrong with that? Isn't that the holy grail of software design - to be able to issue one simple command and know that everything else is going to be taken care of?
It's wrong because it took me - f o r e v e r - to finish programs that way. When I look through the code I see dozens of functions which are never called anywhere in the entire program. I put them in, not because I needed them, but just to make the class API complete. Ridiculous!
I'm not alone. I once heard a computer science professor give a group of freshmen the bad advice that, "when you make a class, you should give it as many overloaded constructors as you can, as many as you can think of, in case you need them." Remember, kids, to stock up on constructors; that way you'll be prepared when the global function shortage hits...
The path I alluded to earlier is the way out of the quagmire of slow software development. Imagine the space of all programs as a huge, dark forest. Walking the path means not straying from what you need to write to accomplish your goals. You can write code, even good code, while meandering all over the place, but you will spend much time lost in the forest and may never reach the end. Every time you leave the path to go explore something interesting, you are covering a greater area in the space of all possible programs, making it ever harder to come back from your excursion.
So much for not writing what you do not have to. Yet I am advocating something beyond even that. You must continually re-examine everything you have already written, and eliminate dead code. Do not be wedded to code you have written just because you spent time on it. Instead, value the code only on how much it contributes to the final product.
When I work on software I run variations of the same question through my mind continuously: Can I do without this? Is this a necessary function? If I delete this piece of code, will the rest of the system continue to work?
You can't have bugs in code that is not there.