Guiding Principles 101
As a answer-in-part to my own Call To Blog More, I’d like to make an entry. I’d like to talk about some of the guiding principles of design we should all be thinking about and re-enforcing in each other. These principles require discipline and it’s VERY easy to not use them and still achieve decent results (at the cost of looming, expensive problems down the road). You owe it to yourself and your fellow co-worker developer to be familiar with these, at least, and to make an attempt to practice them on a daily basis.
Rather than me repeating documentation on various principles which is already abundantly available on the web, I’m going discuss two things: 1.) The ObjectMentor site which has a collection of well-written documents (in PDF, *ugh*) and 2.) Discuss a somewhat newer principle which I call ‘Framework Ignorance Principle’ and which Ayende recently dubbed ‘Infrastructure Ignorance [Principle].’
Wealth of OO Design Principle Knowledge
http://www.objectmentor.com/omSolutions/oops_what.html
This has a breakdown of each and every principle (plus more) that I was planning on talking about here. That kinda stole my thunder, actually. It’s hard to top ‘Uncle’ Bob Martin when it comes to OO design principles, so I’m not going to attempt it.
It is definitely worth your time to spend a few nights browsing through the PDF’s on that page.
NOTE: If you ever plan on interviewing with me for anything more than a junior level developer position, you better be able to give a sentence or two on at least 5 or 6 of these principles and discuss, at length at least one of them.
I’d like to call out one principle, in particular that I think would probably benefit everyone the most. It’s one of the simplest concepts, though hardest in practice to maintain and requires a good team discipline to maintain:
Single Responsibility Principle
“There should never be more than one reason for a class to change”
– Robert ‘Uncle Bob’ Martin
If you have a communications class that has methods like ‘Open()’ and ‘Close()’ as well as ‘Send()’ and ‘Receive()’, you’re violating SRP. Connection management and data transfer are separate concerns and should be isolated.
Framework Ignorance Principle
This one isn’t really a new concept and is instead really an amalgam of several other principles. It’s worth pointing out and promoting to the level of a ‘virtual’ first-class principle, in my opinion.
When working with various frameworks, there is a propensity for that framework to become pervasive at all levels of the code. Without rigid discipline, the temptations and weakening arguments such as ‘Well, we’re joined at the hip to FrameworkX, might as well make my life easier and give up the pretense that I’m NOT going to use it’ will gain in frequency and intensity until there is a collapse of discipline. Depending on what your framework does, this line of reasoning will trigger a set of events that will lead to a quick or gradual decline in your projects maintainability, testability, and overall agility. It’s a self fulfilling prophecy. If you give up the discipline of keeping the framework segregated by saying that you’re already in for a penny, you will quickly be in for a pound. While it’s probably true that you may not be migrating from NHibernate any time soon, you will most likely be migrating from NHibernate 1.0 to 1.2, or better yet, 1.2 to 2.0 which could be a much larger issue. At that point, you’re very close to adopting a new framework anyhow. Sure, the API is very similar, but the guts could be very different and there is tons of new functionality that may make you rethink certain design decisions — forcing rewrites of part of your app. Maintaining the ignorance to the last possible moment will greatly ease this refactoring or rewrite. If you made the mistake of allowing ICriteria or ISession references bleed up into your UI or Web layer, making the switch from 1.2 to 2.0 will likely be very painful.
Another case study here is log4net. Over the years, I had grown so accustomed and trusting of log4net and relied on it’s snail-like release schedule (or lack thereof), that I allowed log4net to bleed into all parts of my app. “It’s a logging framework, how much could it possibly change?” I found myself saying. Well, then log4net 1.2.9 came out. Sure, the API was exactly the same, but they changed the key signature for the DLL which broke assembly linkage all over the place and was irremediable due to the fact that you cannot do assembly versioning redirection in .NET when the key signature changes. Yet another time that a lack of framework ignorance discipline bit me hard in the rear.
I’m not advocating you go out and write abstraction layers for all your frameworks (*ahem* I’ve tried that, it’s not pretty). You may have to in certain circumstances, but avoid it as much as possible. You will create a potentially brittle change resistance layer that can slow down your refactoring in the future and also mask useful features in the underlying framework (the ‘least common denominator’ problem). I would, however, advocate that you try to leverage the framework to it’s fullest, but do it in a way that works around and among your primary code and not in the middle of it. Attempt to make use of IoC containers for injecting or wrapping functionality. Look
A perfect example of this technique is how NHibernate mapping files are separate from the domain entity objects themselves. The mapping files explain to NHibernate how to persist and retrieve these objects from a relational data store. There are no mapper objects, no adapters, no attributes on the entities themselves, no code-generated partial classes, etc. To stop using NHibernate or to switch to a new version, you simply stop using it or start using the other version. There will be a few places in your code that will have a reference to NHibernate, but these will be minimal and self-contained, allowing for quick refactor.