Well-constructed != Over-architected
Let’s imagine for a moment that we’re building a dog house for our beloved family pet. We want it to protect Rover from the elements, be a comfortable place for him to escape the sun and relax, and in general, have the structure hold up for quite some time.
We are probably going to select decent wood, rather than scraps. We’ll probably frame it out, make sure it’s sturdy, make sure the joints are secure, that it has a solid base, and that the roof is well put together. We’re probably going to plan it out, draw it up, and do the measurements to make sure Rover fits inside. If we aren’t that good at carpentry, we may grab a book or two and follow the recommendations of people more practiced than us. Sure, we can hammer nails, but we don’t necessarily know whether a miter joint is more appropriate than a dovetail joint in this case. We’re not just going to slap it together with glue and tacks, leaving gaps between boards, and not being too concerned if it skews in a strong wind. In short, we’re going to build a shelter that we’re happy to put a loved one in.
We aren’t going to support the walls with flying buttresses to future-proof the structure. We aren’t going to build it with steel girders. We aren’t going to turn it into the Winchester House.
There’s a difference between well-constructed and over-architected.
There’s an epidemic of misinformed opinion going around that adhering to commonly accepted good coding practices is synonymous with being an “architecture astronaut”. That somehow someone who insists on applying SOLID and other principles to their code is more likely to inappropriately “solve” simple problems with too much complexity. This is simply not the case, and in my experience, the opposite is usually true.
If I have a simple one-off forms over data application to deliver in a short time it’s very unlikely that I’m going to spend a lot of time applying everything I ever read about DDD to the problem, and put a message bus behind it, put NHibernate under it, and implement some extensibility framework ahead of time just in case.
I will, however, write the code to adhere to the Single Responsibility Principle, and I will employ Dependency Inversion, and I probably won’t have many Law of Demeter violations. And I’ll probably, but maybe not always, design it with TDD. Why? Because my experience leads me to understand that these are good things. They help ensure that this simple app is going to be understandable and readable in 6 months when I have to add a feature or fix a bug. And they’re internalized. It’s the way I code now. It’s no faster or slower than not employing SOLID principles because that’s a useless comparison at this point. I’m unlikely to not employ these techniques because it’s how I write code.
I believe that IoC containers are powerful tools. Every application I’ve written in the last couple of years does not, however, employ an IoC tool. Each of them does, however, make use of dependency inversion internally because that’s a good way to construct your classes.
I believe ORMs are powerful and useful tools. I do not, however, have a single application in production today that uses NHibernate (ok, it’s out, I’m the only alt.net guy not using NHibernate in production). I do, however, typically abstract data access away from the rest of the application as a natural result of applying SRP.
I believe that DDD is a very powerful design philosophy, and that DDD as a concept is composed of many useful parts, not just the patterns. Every application I have in production does not, however, have a complex domain model, and a layer of application and domain services, and a slew of repositories, and explicit aggregates with roots and so on.
I do, however, always think about my domain even if it’s an ActiveRecord type implementation, because it helps make sure I’m modeling the appropriate concepts. And I do always think about and build a lexicon for ubiquitous language for a project because it enhances communication and clarity. And I do think about what my bounded contexts are because it helps me understand the nature of the system.
Irrespective of the complexity of code to be written or the time allotted to write it, I always strive to make sure my code is readable, and that my APIs are intention-revealing. This isn’t about geeking out on code structure, this is about putting something together that someone else on your team can understand. There’s no time tradeoff to be made here, nor is there a complexity one. It’s just part of constructing your software well.
Nobody ever said that building software well meant employing every tool and technique in the alt.net toolbelt every time, so I don’t know why people insist on acting like that’s being said. These arguments about not having enough time to “do it right” or not wanting to overcomplicate “simple” solutions usually come from a position of either ignorance or fear.
Constructing something well means simply constructing something well. Have the professionalism to do so.