In my peculiar line of work, I see a lot of legacy code. In fact, I see a lot of bad legacy code, written (in most cases) by a “lone wolf” programmer who happened to be learning <Perl | PHP | Ruby | etc.> “by the seat of his pants, (making it up...) while going along.” This painful experience has emphasized one thing: “Dominoes = Bad.”
“Bad legacy code” is a chain of dominoes. It’s a disaster waiting to happen. The only reason why any of it works, is because the rest of it works. Magic-value tests (e.g. $state eq 'I') are scattered willy-nilly all over the code base, with no particular rhyme or reason. The application “works” only because (and therefore, only if) all of the planets are in perfect alignment at all times. When any change needs to be made, anywhere in the code, the “ripple effects” can branch out ... not only across “space,” but also (quite literally) across “time.” You might find yourself trying to wrap your head around observations like these, upon which the existing code utterly depends:
$state shifts from 'I' to 'S' only after the user does ..whatever.. after doing whatever-else..., therefore the code that I am looking at right here tests for 'S' ...
“Good code,” whether it is “legacy” or not, contains no temporal assumptions. Or other assumptions, for that matter. Instead, the code that you are looking at is “a statement of present fact,” with no prerequisites or co-requisites. It stands alone. And, if something is nonsensical about whatever it sees, it is the one to raise the alarm.
“Good code,” “bad code,” it’s all really a matter of maintainability. Good code consists of carefully placed, well-defined and well-defended “islands” of functionality with a minimum set of interdependency upon “everything else.” Bad code is precisely the opposite. The only way to understand “bad code” is to understand everything at once. Which is a lost cause.