At this point I’m going to go ahead and claim that finding correct abstractions and coming up with interfaces is the most difficult thing in application design. Note that I’m talking about design, not implementation.
Implementation might carry a load of technical difficulties and workarounds and digging through the web. But that’s what they call “mechanical” problems. Often this is what is assumed programmers are meant to do, but I’d say that in mainstream software development most things have already been figured out, articles have been written and examples are available for download. That’s the kind of work that said to take about 10% of overall development time.
On the other hand, abstractions are always domain-specific and while writing them down or typing up or diagramming on the board is easy, it requires deep understanding of the problem domain and applicable use-cases. Usually the process involves more than one person. Depending on the scope it might require coordination of multiple designers/architects to ensure consistency throughout the system being produced.
Leaving these difficulties aside, you have to come up with abstractions that are easy to use correctly, but more importantly are very hard to misuse. Comments in the code are a lie and the code itself is the ultimate documentation and first and foremost it should be written for other people to read and make use of.
I’ll admit I’ve done my share of mistakes, but I was fortunate enough to be able to start from clean slate and try something different (and hopefully better) every time. After having designed half a dozen decent-sized applications from ground up I came up with these observations:
– Separation of concerns is easy to accomplish with layered architecture;
– Role-based interfaces with very few verbs over relatively large number of nouns allows you to build Chain of Responsibility with ease (HTTP with its 4 verbs is the ultimate example this), cut down on the lines of code and are easy to understand at glance – w/o looking at the details of implementation;
– Practicing ubiquitous language and keeping domain model rigid with Services, Entities and immutable Value Objects (Domain-Driven Design concept, not DTO) simplifies understanding, use and change;
– Thinking about external dependencies (like persistence, communications with other systems, etc) in terms of services for the Business layer to consume establishes solid ground for defining interfaces with Data Source layer;
– Having separate unit test projects for every layer gives you sense of accomplishment, confidence and freedom to change things;
– Once all that is done, wiring it all together with Dependency Injection framework of your choosing becomes trivial;
– Adding integration test project to ensure that various parts of the application talk to each other frees you from having to run UI to test a change.
Each one of those practices helps, but taken together they end up amplifying the effect of each other. Let me tell you, going through a month of intensive development following these practices feels like a vacation. It keeps the management happy too because there’s always solid and verifiable progress and thanks to extensive testing the definition of “done” is really inclusive.
I think I’ll conclude on this happy note.