When to refactor
A lot of developers, project managers and technical leaders wonder when to refactor and when is a good time for doing so. It's not always an easy question to answer. Generally as soon as you notice a problem, it's a good time to refactor. Refactors don't have to be giant, time-consuming tasks. For little problems a refactor might be as simple as renaming a variable in a few places. If a problem is left in code for too long, it tends to grow exponentially until your team spends more time fixing bugs than working on features. And nobody likes just fixing bugs. I think the following 5 times to refactor break down the variety of situations fairly well.
Refactor as you add features
There is a popular refactoring strategy in test driven development(TDD) referred to as "Red, Green, Refactor". The idea being that nobody writes perfect code the first time around so when you write code you should refactor it a bit before moving on to a new feature. In this system, you should write a test for a new feature and then run the test. The test should fail immediately(red) because you haven't written any code to make it pass. Once you see it fail, you can then write the code to make it pass(green). Once you have a proper test and code that makes it pass, it's time to refactor.
Again, the idea here is that you probably didn't write perfect code to begin with but that's okay. Take a little time to organize things better, rename things that are unclear and add notes so that other developers can understand what's going on.
Refactor as you work on old code
As you add new features or fix bugs in sections of code that have been sitting around for a while, you're bound to notice problems and irregularities in the code. You'll want to fix these problems and irregularities right away to prevent dealing with more bugs and unmaintainable code later.
Fix naming and namespacing issues as you go along. If something doesn't have a name that makes it painfully clear what it is: it needs to be renamed. Don't use abbreviations or eliminate vowels in variable names as you're only making the code harder to read. And namespace things appropriately as well. This tends to keep variable scopes limited and leads to better performance(smaller variable scopes means the variables can be garbage-collected sooner).
Any time you can reduce cyclomatic complexity, you probably should. If you follow the "Rails way" of doing things, you want to break down everything you can into the smallest methods that are maintainable. That's not always the best way to do things as it can lead to having to sort through dozens of methods to figure out what's going on but at least in that way you don't wind up with 12 nested callbacks of unreadable mess. Decide what level of complexity is allowable on your team and stick to it. Personally, I put an upper limit at 5. Anything higher than that has to be refactored into simpler methods.
Add comments to everything. Even when your variable names are perfectly clear and your methods are small, it can still be a bit of a burden to have to read the code and figure out what it does. Adding a comment above a method with a plain-language description of what it does can save other people(and your future self) plenty of time debugging. In the world of javascript, I like to use the jsdoc syntax for documentation in order to auto-generate a documentation website for the code.
If there isn't a test for something, there probably should be. Good software is well-tested. We don't always manage to write a test(or a few) for every line of code but it'd be nice if we had time for it. Try to write a test for any untested sections of code and definitely write a test for every bug. You don't want to have it pop up again because there wasn't a test for it.
Small refactors
Small refactors are ones that are significant enough to require their own story(if you follow the Agile methodology) but not so bad that they're going to be a major headache. These should take half a day to a full day and involve fixing sections of code that are on the verge of becoming unmaintainable.
When a method becomes too complex it may need refactoring. In bad cases you might have a method with several levels of nested callbacks or it might just be extremely long(I would say extreme equals greater than 30 lines of code). These are methods that need to be broken down into smaller parts and documented so it's clear how they get reassembled in the end.
Large methods or just lots of methods can lead to very large files. Large files are categorically unmaintainable. With large files, you spend too much time scrolling around looking for the bit of code you want and hoping that you didn't just rename something that appears in other areas of the file. With proper inline documentation, a source file might reasonably reach 120 or so lines. Anything longer than that definitely needs to be broken down. If your team doesn't use inline documentation, a source file should never be longer than 50 lines. Break the file out into chunks that can be include
d or require
d by other files so as to maintain readability.
Major refactors
Major refactors are for the case when large areas of code have become unmaintainable or when you need to make large changes in the structure of your code.
Things can get unmaintainable when your team needs to work quickly and makes small sacrifices in code quality in order to meet business deadlines. When this happens, you'll need to schedule regular refactoring cycles. Every couple months or so, set aside a week to fix up your code. The code quality is good enough when you know that somebody who's never touched your project before could get up and running quickly.
I think there are two events that can lead to major refactors even when the code quality is good. One is when you want to move to a new language/framework and the other is when you introduce a new linting/quality-checking tool. If your team is working on plain CSS and decides to move to Sass, that can be a huge refactor and involve lots of time and coding. It will save you time and headaches later but getting onto that system is not so simple. Another example is if you decide to start using something like coffeelint to clean up your coffeescript files. When you first introduce a tool like this, there is bound to be hundreds if not thousands of mistakes that get flagged. Take the time to go through and clean them all up. Make sure to do this with granular commits so your git history is easier to maintain. You'll thank yourself later.
Starting over from the beginning
Sometimes it's just not worth saving the project. When your team's velocity has dropped significantly, when you discover a new tool/framework that will dramatically reduce the amount of code you need to maintain or when there are more bugs being worked on than new features it's probably time to retire the old code and start over.
When this happens, hopefully you have well-written tests for your code. If you do, it's relatively straightforward(although still time-consuming) to create a new project, copy over your tests and then keep writing code until all your tests pass. Once all the tests are passing, you should have a brand new, more easily maintainable project that does exactly what the old one did. If you don't have a comprehensive test suite, I would recommend going through your existing project and documenting everything it does. Build specs off of your documentation and then start coding.
Final thoughts
I hope this helps you write your software better and in a more maintainable fashion. Let me know if you've got a list of other tools/tricks you use to keep code maintainable, I'd love to learn about more of them. If you don't think you understand code quality well enough or you just don't want to be bothered with checking it yourself, there are many tools out there like Code Climate(free for open source projects!) and SCSS-Lint that you can integrate with that will tell you when things get messy.