A story about design decisions and legacy code

Jon Krogell • Software Engineer

Back in late 2011 - early 2012, my project’s development team was taking the first concrete steps in one of our largest projects so far. We were figuring out the domain model and creating our data model. We already knew that the Customer, a core entity in the system, is conceptually a complex beast (singles, couples, divorces, name changes, decedent estates, companies, and lots of duplicates). Despite this, our chosen (data) model was vastly simplified – basically a natural person with very few restrictions on uniqueness. Creating a complete model incorporating the various customer types was deemed over-engineering. I agreed; it was a sound decision.

Fast-forward to 2013. Decedent estates and companies are added as a type-flag to the Customer model. I regret to say that I wasn't actively involved and could not raise any concerns. I also failed to mention my slight mistrust in the implementation when first seeing it. I acquiesced; it was, after all, a straightforward solution that did nothing extra, no waste, KISS, and all that.

Later, I was involved in a sub-project that required us to tighten the restrictions of the Customer entity, namely, making the email field unique. This caused significant hurdles, as there were duplicate emails in the customer registry. Having overcome the challenges, we stumbled upon a problem: in some cases, decedent estates needed the same contact email address as a natural person.

Consequently, we decided that we should refactor the special cases out of the Customer model and merely link to a natural person Customer-entity with a unique email. This is, in my humble opinion, the proper way to resolve the issue. However, it has since come to our attention that this change will have universal implications on the system and has been deemed too costly, both in money and time required. Instead, a workaround is planned – not a hideous one, but a workaround nonetheless.

I understand the business decision, but frankly, we are adding technical debt where we should be reducing it. That said, my main concern isn't the workaround, it's that we got here in the first place. How did we, within our own project, reach a point where we are afraid or unable to make far-reaching changes? A point where our code can now be considered legacy code.

Obviously changes are cheaper the earlier we do them, but on the flip-side, over-engineering and waste (as in code or features not needed yet or only needed for a short period of time) is considered bad practice. How do we balance between making small evolutionary steps and saying 'no, we need to make a big and somewhat unnecessary change now because it'll be overwhelming to do it later’?