Refactoring is not a dirty word. Time will always apply pressure on a project. When this occurs, there is a tendency to want to cut corners. If you are a “doer” coder, then I would expect the first working version of your source code is not particularly neat. Your task is not over yet! Make the effort to refine variable, function and class names. Make sure you identify what is wrong with your code and fix it there and then.
Unit tests are your friends here. Well constructed unit tests help you to re-factor fearlessly. You will know if you break your code when you re-factor, as your unit tests will fail. It is important that you have sufficient coverage with these tests. If you miss boundary cases, or not test each code-path, you are leaving yourself exposed to introducing bugs. Sometimes a few unit tests are worse than having no unit tests as they can lead to a false sense of security.
Having unit tests can also help you to produce less tightly coupled code. It is generally code that is better abstracted and hence more compliant for code reuse. In fact, the code you write is already used in two places: firstly in your application, and secondly in your unit tests.
Test-driven development is the technique of writing test-cases and then writing the code that passes them. (There is more to it than that – but the fact that people write whole books on the subject probably tipped you off to that) There is the concept that you write just enough code to pass the unit tests and no more. It is a great way of ensuring code brevity. Less code means less chance for bugs to exist. It also helps to remind you of what you are trying to achieve in the code that you are writing.
Unlike some cases I have seen, refactoring is not “throwing away” a code base and starting again. That is rewriting! I have not seen much public endorsement of the rewriting technique for improving a code base. Refactoring is merely the art of neatening the code. A compiler will “understand” what it has to do, no matter how nasty (and bug ridden) the code may be. The purpose of refactoring is to allow another human to understand what the code does. As humans are (relatively) good at pattern matching, looking for repeated code blocks is a good first step when refactoring. This code is not necessarily going to be multiple statements, or indeed even one complete statement. Sometimes this repeated block is worthy of its own function, sometimes it may just be good to assign the result of the code block to a local variable.
It is worth spending some time on tidying the code. I have yet to come across any firm metric to help you determine how much time this will be. You don’t want to be accused of “playing” with the code, the way a child “plays” with food they don’t want to eat! When refactoring existing code, it can be worth multiple check-ins. If your project uses a continuous integration product and sufficient test case coverage, then this helps prevent you breaking code. If you don’t use these tools, it still helps provide a level of transparency to your work. It will be easier for someone to understand how the code evolved into the state it is. This may be important in case bugs are introduced.
Remember that the result of this work is not to produce bug-free code, but code where bugs cannot hide! You are aiming to make it readable for the next person by making the learning curve as gentle as possible. Why bother making it easy for the next person? Well, it may just be you!