It seems like after I've written a significant amount of code I get this anxious feeling like I haven't done it in the best possible way, and I end up continuously refactoring and spending way too much time on the project, or never getting it done sometimes. Does this ever happen to anyone else, and how do you deal with this?
I enjoy these moments when they happen to me.
The reason: if I did not look back at my work and think there is something I could have done better than I would not be advancing as a developer. Therefore, when these moments of enlightenment occur, embrace them and take note of what you learned. Consider your timetable for current project and if possible refactor the code, if not possible take the lesson and use it in future implementations for projects.
Either way, learning from your own mistakes is a great thing!
It does seem like you can always refactor, doesn't it? I try to limit refactoring only when trying to solve other issues. For example, refactor when you have a performance problem and refactoring with help you solve it - refactor when you need to add new functionality and refactoring will help you solve it
To be honest, I'd worry more if you were cranking out huge reams of code and thinking it's all perfect and doesn't need any refactoring...
When I was younger and inexperienced, I was very arrogant about my programming ability, and always tended to imagine that it's possible to design and plan really well - and that once I get to the implementation stage I'll just bang it out and it'll all be perfect.
Reality is almost the opposite. Some even say that as soon as you start coding you should be in Maintenance mode. The idea here is that the "Implementation" stage of the SDLC doesn't really exist as such, because you should never put bug fixing or refactoring aside and pretend that the code you're producing is "fresh" and perfect.
All that said, I suppose it IS possible to get too obsessive about refactoring. I just haven't seen it yet. And the more experience I have, the more I think it would be a good thing if more software teams patently refused to work to tight deadlines and go into technical debt. After all, this is the most common reason why refactoring gets put aside in the real world.
I would suggest to not depend purely on feeling or code smell.
Enumerate clearly what is wrong with the code and what the solutions are. Seriously, write it down, because you're going to want to review how effective your refactor was against this.
Identify ways to break down the refactor into achievable chunks, and prioritize them. Have the discipline to focus only on the scope of each chunk, avoiding tangents that can undermine your task.
Also, identify what unit tests you can write against the existing code before you proceed. If there are already extensive tests, that's a great thing. Lack of unit tests represents a great opportunity to make some.
There is no doubt I fall into this trap but some refactoring is important for the future support/debugging.
When writing a major piece of code it becomes very easy to keep through lines of code into the method that's currently being written. When I've completed a major piece of code I place a todo: code review comment. Then a few days later I'll do the code review and refactor accordingly. If I have trouble reading it a few days later what's going to happen in a few months or even years.
I fall into this trap when I'm first learning a language or technology. For example, when you first learn java, imagine you write a web-app with a servlet, thinking that's the right way. Then you realize there's jsp and you think oh that's newer that's probably right. Then once you get half way through that you find Struts and maybe some EJB stuff, after which you find spring (xml based) after which you find the cool @MVC annotations, after which you find it all too verbose and you're spoiled for choice between groovy/grails and scala/lift! This is perfectly fine for personal projects since the point is usually to learn and not necessarily to hit some deadline.
I used to be uber-refactorer at work also. But as I've gained more experience I've become more selective about what I'll refactor. It turns out that when you leave a company you don't take the code with you, and usually other engineers can wreck code almost faster than you can fix it.
The rule of thumb I follow is this: I refactor until it's as optimized as it can be at that time and then I don't refactor it again unless situations change, or more rarely if I have an inspiration about some new and better way to do things (for instance using a new language feature).
For instance, I once had to write a new code module for an existing application. I wrote it in a certain way, and the next day I gave it more thought and figured I could refactor it out and make it more abstract since we were fairly certain it would need to be extended down the road for other uses. So I refactored the code out. Then, I decided it was a little too generic and I consolidated some classes and interfaces that were basically doing the same thing, using Generics (C#) to get the same functionality. At this point I probably could further refactor to make it even better, but it's "good enough" - the code is well-written, follows proper design patterns and engineering concepts, and is extensible. I would only refactor it again if the requirements forced me to reevaluate the code, or if there was a language feature that could make the code clearer and/or more readable.