Controlling complexity is the essence of computer programming.
This has been bugging me for years, and it's getting worse. In some type of ironic paradox, it seems like the worst programmers are (in some ways) the best programmers.
So what do I mean by that? I mean that poor developers write crap code and somehow, through perseverance, determination and probably dumb-assed luck, they cobble together a steaming pile of crap that somehow works. If you randomly changed a line of code in it I could never get it working again, and that's how I'm amazed at how good (ahem) they are.
The steaming pile of code only ever works in the expected cases though. Adding a feature or fixing a bug is nigh on impossible, which brings me to this post. Actually what brings me to this post was that I recently removed 1000 lines of code (out of 6000) and the functionality never changed. How is that even possible? What's even more shocking is that I expect to do it again, likely several more times.
I'm convinced that an adequately intelligent person can take any problem and break it down into simple and easy to understand sub-problems that are in turn easy to solve.
One approach I like to take is assuming that software development is easy. So if I'm working on something and struggling, then I stop and assume that I'm doing something wrong, or somebody else did something wrong. Scenario a or b is almost always the case.
I really wish I wrote this down since I can no longer remember the details, but one day I was writing some new piece of code and was really having a tough time. It just wasn't making sense and I was spinning my wheels. In other words, the code was being a lot more difficult than I would have assumed, given the problem. I really wish I could remember what the actual problem was.
Anyhow, after a half hour or so I stopped and just went back to first principles. Well it turns out that I misunderstood something because one of my variables was named wrong. When I changed the variable name the code just wrote itself. Well I had already written 90% of the code, but it did need some tweaking, a function got broken up, another merged, that sort of thing. The point being that five minutes later I was done and it was perfect. As near as I could tell it was bug free and also, there was no way in which the code could fail. All because of misnamed variable.
That's a big difference between crappy code and awesome code. Crappy code only has one way in which it works, good code has no way in which it fails. It's a lot harder getting to that second level, but when your problems are small enough, the solutions simple enough, it's by no means unachievable.
Another point that has been said many times but apparently needs to be said again, is that you write code for people, not computers. If the code compiles then the computer can run it, the computer doesn't care. Anybody in the future, including yourself, does care. Style matters. Aesthetic matters. Simplicity matters.
How did you get there?
Since so much of software development is munging existing code I'm often flabbergasted at what I find. If I had a nickel for every time I've seen find-the-index-of-the-element-in-the-list loop copy and pasted over and over I would have a whole lot of nickels. Sometimes that find-the-index loop would be repeated multiple times in the same function!
It just boggles my mind that by the third time Johnny Programmer was writing that loop it never occurred to them to write:
int FindElement( const collection& coll, const element& elem);
pretend for a second that there aren't 1000 existing libraries that already do this. eg. std::find(coll, elem)
I understand that requirements change, sometimes on the same day. The challenge is to fix the design to incorporate the new requirements, not fix the code for the new requirements. What do I mean by that?
I mean that if the initial design had certain assumptions, glomming on some code with duct tape and binder twine will hurt you severely in the future. So stop, revise the design and then start coding. Chances are that thinking at a whiteboard for five minutes will save you hours (perhaps days) at the keyboard.
Another technique I find works really well is to refactor the moment you're done. This is more effective the harder the problem was to solve. So lets say you've been banging away at the keyboard and you finally get something working. Awesome! Time to get to the next task and off you go.
Nope. Stop and take a couple of minutes and do a code review on yourself. Look for things to improve upon. This is very easy to do since all the code and all the issues are fresh in your mind. Look for unused variables. Look at the aesthetic flow of the code. Look for duplicated code, duplicated variable names. Look at your important variables, should that vector be a map? Who really owns that pointer? Does this class do more than one thing? How many methods can you make const, can you change parameters to const references.
So, as soon as you finish something, immediately “rewrite” it. If you find anything that smells a bit off, or looks a bit ugly, take a moment to fix it. Here's the beauty, if all you do is move lines around it's very unlikely you'll break anything. If something does break then you're closer to only-works-one-way and further from no-way-it-can-fail.
Code is the enemy
One of my most productive days was throwing away 1000 lines of code.
Deleted code is debugged code.
As a developer you probably think your job is to write code. It is, but you'd be wrong. The best code is no code. If a line of code exists, then it might have a bug in it.
Is it bad code?
- Hungarian notation. In particular they follow Microsoft's coding standards from 1992.
- typedefs. So simple and so rarely used.
- Obvious clues that they have no idea how C++ classes actually work.
CString str = CString(T(""));. There are several things wrong with that affront to the OOP gods.
- Copy constructor esque code that is everywhere except in a copy constructor. Did you know you can make a copy ctor (or operator=) that doesn't take the same type of class as its parameter? Well I know that you know, but feel free to share that little tidbit around the office.
- Functions that are hundreds of lines long.
- Member variables that are the same in multiple classes.
- Multiple classes that are almost identical but don't inherit.
- Classes that rely on other classes to muddle with their innards.
- Code that was copy-paste'd. If I ever run a software company I'm going to install some type of driver that causes a siren to go off whenever it detects a copy and paste inside a text editor.
This list is by no means exhaustive.
Since each of these topics deserves its own post, that's what I'll do. Over the next days/weeks/infinity I'll talk about each item and likely a few more as I think of them.