Most software developers don't have an exact definition of legacy code, but to paraphrase Potter Stewart, they "know it when they see it." Unfortunately, legacy code has something else in common with Stewart's famous subject--it is embarrassingly ubiquitous.
Legacy is the degree to which code
The modern interpretations of legacy code are all implications or symptoms of the above. For example, consider the idea of legacy code as "written by someone else and impossible to maintain." This indicates a failure to capture and communicate essence, usually on multiple levels:
Organizations often jettison legacy code when they jump to the Next Big Thing. In the circles I run in, Java Web Development was the Last Big Thing. Ruby and Rails Web Development is the Current Big Thing. The Next Big Thing is ... well, a subject for a different post.
For an IT buyer, these Big Things are Sisyphean moments. They get to start over at the bottom of the hill, rolling the same damn rock again. This isn't all bad: Platform shifts provide a rare and brief opportunity to jettison a codebase that is legacy crap. Unfortunately, there is little reason to believe that the next codebase will be much better.
It doesn't have to be this way. As software developers, our goal should be to build this generation's code so that the Next Big Thing won't force a complete "do over." That's pretty ambitious, but if we try hard and miss, the consolation prize would still be pretty good: improved maintainability and reuse within this technology cycle.
To get there, we have to start by rejecting ceremony.
Good code is the opposite of legacy code: it captures and communicates essence, while omitting ceremony (irrelevant detail). Capturing and communicating essence is hard; most of the code I have ever read fails to do this at even a basic level. But some code does a pretty good job with essence. Surprisingly, this decent code still is not very reusable. It can be reused, but only in painfully narrow contexts, and certainly not across a platform switch.
The reason for this is ceremony: code that is unrelated to the task at hand. This code is immediate deadweight, and often vastly outweighs the code that is actually getting work done. Many forms of ceremony come from unnecessary special cases or limitations at the language level, e.g.
High-ceremony code damns you twice: it is harder to maintain, and it needs more maintenance. When you are writing high-ceremony code, you are forced to commit to implementation approaches too early in the process, e.g. "Should I call new, go through a factory, or use dependency injection here?" Since you committed early, you are likely to be wrong and have to change your approach later. This is harder than it needs to be, since your code is bloated, and on it goes.
I have found the following techniques helpful in writing low-ceremony code.
What has worked for you?