For a long time now, my day job has consisted of modifying and maintaining existing code. I'd make the changes I needed to make to get my job done, and then type 'make' to compile and link the program. Most of the time the changes were pretty straightforward, and the code worked on the first or second compile.
Then last week I was trying to do something complex with the Unix system calls 'fork()', 'nice()' and 'execl()'. I read up on the functions in the man pages and in a book I have, but I still didn't really understand how to use them in my present situation.
Never one to let lack of knowledge hold me back, I dived in and wrote the code in the way I thought should work. I compiled the project and ran the code to test the routine in question (a 20-minute process). Of course, it didn't work the way I thought it would.
I made some changes, compiled again, and still had trouble. The third compile didn't go any better.
By now I'd wasted an hour and a half on three compiles and the editing in between, and was no closer to a solution to my problem. It's funny how you always think it's only going to take "One More Compile" to fix the code.
Time to take a step back and think about the problem. The trouble I was having was caused by my lack of understanding about how 'fork()', 'nice()' and 'execl()' worked. If I could sort them out, the solution would be simple.
So I wrote a short (about 15 lines) test program to experiment with. Most of it was just copied from the real program I was working on.
Then, for the life of me, I couldn't remember how to compile a standalone program like this! After all that time working on existing code and using Makefiles, I'd forgotten how to use the compiler! It turned out to be as simple as:
$ aCC junk.cpp -o junk
Now I could experiment with the three functions, and my test program only took a couple of seconds to compile and execute. I could add cout statements to print variables, rearrange the code, and try lots of variations without having to worry about breaking my production code.
After 9 or 10 experiments over the next half hour, I'd figured out how to use the functions to do what I wanted to do. That many recompiles would have taken four or five hours if I'd still been playing with the production code!
Once I knew what I was doing, the real program worked properly on the next compile. I printed out the throw-away code and wrote what I'd learned on the printout, then filed it in case I ever need to do something similar again.
I learned several lessons that day.
- Know how your compiler works! Know how to compile a small test program if you ever need to. Become familiar with a few of the command line options, too, in case you need to link to other libraries or compile with exception handling or something else unusual.
- If you're not sure how a particular command or function works, don't try to use it in your production code. Write a throw-away program, and experiment to see how it works. This is also a good way to debug one of your own functions that's causing errors.
- Recognise when you're falling into the "hacker" mindset. You'll make a quick change to the code, compile, test, make another change, compile, test, over and over. Even if you eventually solve the problem this way, chances are you don't fully understand the solution and any limitations it has. You probably won't go back and remove the hacks that didn't solve the problem, for fear of breaking the code again. Sooner or later, a side effect of one of your hacks will come back and bite you. At the very least you end up with messy code that's hard to maintain.
You need to take a step back, work out exactly what the code is doing, and don't compile until you are confident it will work. Quality software is not built on guesswork.
One way to avoid "hacker mode" is to set yourself a time limit when trying to fix a bug this way. If you can't fix it with 3 or 4 compiles, you're probably barking up the wrong tree and injecting extra errors in the process.
When your time limit is up, go get a coffee. Ask someone else to take a look at the problem code. Read about some of the functions you're using. Look for a similar piece of code elsewhere in the system that works. Write a test program to explore the problem from a different angle.
It'll save you time in the long run.