For any particular development task it is possible to classify it into one of two categories: tasks that you know how to do and just have to actually be done and tasks for which the solution is unknown. The later category is decidedly more prevalent although ideally all tasks eventually decompose to the former.

The implication of this is that you will be developing things that you do not a priori know how to build. This involves design and particularly making design decisions. These decisions will necessarily be made with incomplete knowledge. Unless you are particularly lucky (and lucky consistently) some of these decisions will be wrong. Even if the decisions are not explicitly wrong they may turn out to be sub-optimal and impose unnecessary costs upon development and maintenance.

There are a number of complementary methods for dealing with this including iterative development and refactoring. These are highly valuable techniques I strongly recommend. Additionally, I believe that periodically you need to step back and perform a sanity check upon your system. This is a process where you take a look at your approach and ensure that what you're doing can be justified. I like to look at major design decisions and in essence ask "Why the hell is it doing that?" and evaluating the answer relative to the consequence of the design decision.

As an example I've been experimenting with ASP.NET MVC recently. I've taken a design decision that except for specific excluded content items all string values will be stored HTML encoded in the database. This reduces the scope to forget proper encoding of user data. For this application I'm satisfied that this decision is suitable.

The next design decision relates to how the data will be encoded. Ideally I'd like to automate as much of it as possible. I'm using the UpdateModel infrastructure that ASP.NET provides to bind incoming data so plugging into this seems reasonable. My requirements are to bind some relatively simple classes composed primarily of inbuilt types (String, Int32 etc). As such I started writing a HtmlEncodingModelBinder that implements IModelBinder. This will by default HTML encode string values unless they're marked that they do not require encoding.

After some time building this class I was seeing the required complexity increase rapidly. I had already defined my own IModelUpdater interface plus a related IModelUpdaterFactory interface and was looking at using reflection to examine classes and generate IModelUpdater instances that used collections of delegates to perform the update. It was at about this point that I decided the complexity was getting out of control.

What I was attempting was to replace (in a less functional manner) a significant proportion of the ASP.NET MVC UpdateModel infrastructure. When I asked myself "Why the hell is it doing that?" the answer "so some strings can be automatically HTML encoded" didn't really seem to come close to justifying the complexity involved.

Having decided my approach was essentially untenable I could then use my new knowledge of the framework to come up with an alternate solution. What I've now got is a HtmlEncodedString class that wraps an encoded string. This is coupled with an (enormously simpler) IModelBinder implementation specific to this type, as well as an NHibernate IUserType implementation that allows it to be properly mapped to the database and back. This does have the notable design implication that the domain model now uses HtmlEncodedString which in some applications may be a concern that is not appropriate in this layer. In my application it's a tradeoff that I'm prepared to live with and is significantly better than the previous approach.

In summary:

  • Limited information requires you to make design decisions that may be sub-optimal.
  • You won't find out if your design decisions are sub-optimal until you try them (assuming no other potential information sources)
  • Design decisions need to be reviewed as new information becomes available.
  • When revisiting a design decision the costs of modification must be considered. Not every sub-optimal decision is worth changing.
  • Every decision has tradeoffs. Your job in making design decisions is to make the right tradeoffs not attempting to eliminate them.