The majority of software projects are ongoing efforts - never ending cut-and-release marathons. New screens and functions are released on (or around) specific agreed dates, but the project itself keeps on going. So it’s far more common for developers to join a project mid-flow, with its own dusty corners that no-one dares to disturb, its homegrown O-R mapping frameworks, crusted together with the foibles and wrinkles of a dozen long-since-departed self-styled code gurus.
By contrast, starting a brand new project from scratch is a rare joy: the opportunity to set the standards, consider the architecture, evaluate the available technologies given the business requirements; and to produce clean, maintainable code that’s easily unit-tested.
Ah, that last one. There’s nothing quite like the pain of joining a “mature” project and discovering that unit tests were never even a glimmer in the eyes of the project’s founding coders; the forefathers who laid the treacle-like foundations for their successors to follow.
Like a Spanish explorer in a South American jungle, you’re unlikely to find a lost city of gold; instead, prepare to discover the overgrown remnants of one long-lost programmer’s vain attempt to add unit tests - a strained nod toward provable code quality, quickly abandoned when the poor programmer was beaten back by the tangle of dependencies, the onslaught of complex constructors and static initializer blocks like Aztec ghosts sending the programmer scrambling to the far end of the jungle.
Code that’s difficult to test tends to also be poorly designed code: difficult to maintain, a nightmare to debug, time-consuming and obstinate when all you want to do is add a new field, track down a particular database column reference, or figure out a calculation which should be straightforward, but which snakes from an asynchronous listener object to a UI component and back.
This is one very good reason why (among other practices) writing unit tests as you write code also seems to lead to good, maintainable code with a lower bug count. It's not because of the tests themselves, it's because in order to add tests, you have to design reusability in from the ground up. And that naturally leads to a more flexible, responsibility-oriented design.
(To clarify, though, unit testing by itself doesn't naturally lead to a good design; there are plenty of other considerations, like hiring people with brains and who give a damn about software quality; following an effective design process; and creating something that matches current business requirements, and should cope with future requirements too. But unit tests provide a form of feedback on the design: a sanity check. If the code you just wrote is difficult to test, then it's also going to be difficult to modify or add new stuff - so you're almost certainly storing up problems for later).
So if you're ever lucky enough to start a new project from scratch, be kind to the coders who'll join after you, and add unit tests right from the start. After all, how would you want those future code archaeologists to view you - as the malevolent jungle spirit who sacrificed unit testing virgins on the Altar of the Great Crusty Singleton, or the guru designer who proved that a City of Gold, an El Dorado, really does exist within their code?