Unlike my wife who loves to go shopping with no real purpose in mind :), I avoid putting myself in situations where I could separate myself from my money without due cause. I just end up desiring or thinking about items that I have never had and am living quite comfortably and happily without. Where once my inner-pond was clear and calm, seeing new and improved toys causes the lines of need vs. want to blur and I start trying to justify the need for a new toy.
The same idea is true in software development. The more I learn about new tools, techniques, patterns, and principles of software development, the more I try to use them even though I have been living "fine" thus far without them.
As an example, I have never been overly concerned with low coupling in the past, and in 99% of my applications it has never been a problem. In the other 1%, I was able to refactor with little effort to a design pattern that supported pluggable functionality. In the past year, however, I have started to see various classes and interfaces being added to my code to support looser coupling. I remember the effort of creating the classes, but have not realized any benefit from their existence because their creation wasn't based on actual requirements, but my desire to use the ever evolving best practices and patterns to make my software "more maintainable and flexible."
Low Coupling Principle
I have learned to see the low coupling principle as an "evil" in many cases. It is a con artist making you think your custom application or code needs to be pluggable or future-proof in many cases when 1) it doesn't, or 2) it is just as easy if not easier to refactor to a pluggable architecture later using known design patterns ( Refactoring to Patterns by Joshua Kerievsky ). Obviously if you are building frameworks and reusable components the idea of low coupling and coding to interfaces is important, but I have seen and done this myself in the past with custom / opinionated software as a "just-in-case." In more times than not, this just-in-case type of development leads to over-architected and hard to maintain software.
Once your skills have improved in OOP and refactoring and you are very much aware of design patterns to solve common problems, you can avoid doing things just-in-case because refactoring code to support new requirements is simpler. In fact, I conciously avoid adding code to support functionality unless it is "an absolute." This may sound obvious with people shouting YAGNI and KISS, but your code is a reflection of you as a developer. When later you move on to greener pastures, requirements change in the application, and another developer is maintaining your code, he might be saying you should have forseen this changing requirement and used the XXX Design Pattern to make this evolution easier.
Applying UML and Patterns - Pure Fabrication and Indirection
Getting back to this low coupling evil :), Applying UML and Patterns by Craig Larman has a couple of GRASP Patterns ( principles ) that are all about low coupling:
- Pure Fabrication - Who is responsible when you are desperate, and do not want to violate high cohesion and low coupling? Assign a highly cohesive set of responsibilities to an artificial or convenience "behavior" class that does not represent a problem domain concept - something made up, in order to support high cohesion, low coupling, and reuse.
- Indirection - How to assign responsibilities to avoid direct coupling? Assign the responsibility to an intermediate object to mediate between other components or services, so that they are not directly coupled.
I hadn't caught onto this when I originally read the book, but now that I have a better sensitivity to the overhead associated with low coupling, I start to see the words that imply only do this if absolute necessary as creating these artificial constructs leads to less pure and bloated code that blur the descriptive and self-documenting nature of a well crafted domain layer.
Only if absolutely necessary and based on in-your-face requirements do you add classes and behaviors to loosely couple your layers and partitions. Do you really need a factory, service locator, registry, etc. that returns an interface or can you couple directly to the class? Is there a requirement sitting in front of you now that justifies it? How long would it take you in ReSharper to extract the Interface, find usages, and replace the class with an interface if the need actually arises in the future? What is the chance of that need actually arising?
Domain-Driven Design and Repositories
I just finished reading Applying Domain-Driven Design and Patterns by Jimmy Nilsson. A really good book in my opinion and one I recommend. However, it still doesn't clear up for me one of the artificial constructs in Domain-Driven Design used to loosely couple the domain from the datastore - the Repository.
In the DDD community, there are many disagreements about what the repository can and should do and if it can be referenced by a domain object. Here we have a class that provides indirection for low coupling purposes, appears to be a first-class object in DDD, and yet people often still treat it as a "crazy-cousin" or only use it as an intermediate pass-thru object to NHibernate or a DAO.
Because I, personally, haven't got much value from the repository, I have tossed it out altogether in favor of a thick application layer above the domain layer handling use cases, application-specific rules, and interaction with the data access layer. I am sure others have used Repositories wisely, but until I see the light, such artificial constructs ( Repositories ) have been kicked to the curb as they only over-complicate my applications.
Principles Are Cagey Ideas - Expert C# 2005 Business Objects Example
Principles and best practices are cagey. For every principle suggesting you do "a", you will find at least one telling you not to do "a", instead do "b".
As an example, another book I recently read is Expert C# 2005 Business Objects, which is a followup to Expert C# Business Objects that discusses the CSLA .NET Framework. I will post a review of the new book in the near future.
Anyway, in the book, Rocky uses the principle of Encapsulation as well as other good reasons to suggest that the data access code for common CRUD methods be in the business object itself. The business object itself contains the code for fetching, deleting, etc. itself and any of its children. One could suggest this is good use of the Information Expert Grasp Pattern as well.
He briefly discusses why this is better than a competing principle, High Cohesion, that suggests using a separate persistent object in another layer. Some people would argue for high cohesion, suggesting it better to create a DAO in a data access layer that is responsible for all the data access needs of the object.
To each his own, but you can see how wishy-washy these principles can be taken.
Conclusion
Obviously when you have a need for loose coupling you need your artificial constructs. However, in general, objects and behavior based on pure fabrication and indirection only muddy-up your code. By sticking with various object-oriented principles, like single-responsibility principle and open-closed principle, you will be able to refactor youself to an architecture that supports looser-coupling where needed later while keeping your code as lean as possible today.
I am probably kicking in closed doors, but I needed to set myself straight once again and thought others might find it valuable :)
Posted
08-26-2006 1:37 PM
by
David Hayden