I'm interested in what strategies people have come up with for separating all the crufty logic that's necessary to maintain backwards compatibility from the main code of an application. In other words, strategies that let you come closer to having your code look as if there were no backwards compatibility concerns except for separate isolated source files distinctly for that task.
For example, if your application reads a particular file format, instead of one giant honking file parsing function, you could have your code first iterate a list of "quirks" entries/objects, where each quirk checks the file to see if it's a file it would apply to, and if so invokes its own parsing logic instead of the normal case logic.
Quirks is an OK strategy but you have to do work to put hooks in for quirks checks at all the appropriate places in your app, and what the checks will look like will vary for different quirk types, etc. It almost seems like there should be libraries dedicated to the boilerplate for this task. Another issue is how to enforce that quirks aren't abused as general purpose hooks into arbitrary chunks of the app.
My usual strategy is to have something separate that will translate the backward compatibility input into the new implementation input, and then use the new implementation code with this translated data.
This would depend on the time-frame until the retirement of said backwards compatibility features. It you're fairly sure that in a couple of months you're going to release another version of your software that will no longer have to have those quirks, you can just keep the old code around if you're disciplined enough to actually remove all the cruft in the next development cycle. I'm maintaining two separate backend server components where I work and while they can't be upgraded at the same time, they usually can be within a couple of weeks of each other. This means the communication between them needs to be backwards compatible, but only a single version back, and in each version I can remove the old code that I left for backwards compatibility reasons in the previous version.
If however, the compatibility layer is there to stay for a long time or even indefinitely (think Word's binary file formats) I would try to refactor the code in such a way that the new functionality and the old functionality are on equal terms in it. I think both the old format (or behavior) and the new format are part of the system's requirements and there's no reason for the old format to be a second class citizen in it (other than being old that is, pun intended).