Wednesday, 11 April 2007

Whole Values

I read a post the other day that said one of the commonest mistakes made by newbie OOP programmers was to do this:
PostCode postCode = "SW1 1EQ";
Then a spurt of experienced programmers hailed down their fury on this common misconception amongst newbies and how they had to bash out of them that they shouldn't waste their lives abstracting strings.

I also heard a MS qualified trainer tell all he knew and respected him that you should avoid the implicit operator overload at all costs because "if your type were meant to be a string MS would have made it one".

Unfortunately this is one of those places where the MS fan boys have been bought up badly by nasty VB. The concepts that are being so frowned upon by leagues of MVPs is not only a corner stone of OOP (to mix data and behaviour) but a wonderful concept called Whole Value and implicit overloading is C#'s gift to you to make it happen.

Thom Lawrence has a lovely short post on how to do whole values with implicit operators here
so I won't repeat his good work (though I will add that Martin Fowler recommends using structs) but what I will do is try and explain why whole values are a wonderful thing.

Because a PostCode isn't a Surname
One of the first and most basic things a Whole Value will give you is a level of type safety that you may never have realised existed. Have you ever had that annoying bug pop up in an application because someone accidently did this:
FindPerson(form.PostCode, form.Surname)
// somewhere else far, far away:
public ReadOnlyCollection FindPerson(string surname, string postCode);
In this simple example it's pretty obvious you've got it round the wrong way but when you've got a few extra variables to play with it's really easy to get it wrong. Well what if I said there's a way to prevent this ever happening? Use a Whole Value like so:

FindPerson(form.PostCode, form.Surname)

interface Form
{
Surname Surname {get{}};
PostCode PostCode {get{}};
}
// somewhere else far, far away:
public ReadOnlyCollection FindPerson(Surname surname, PostCode postCode);
Now when you go to compile you will get an error because tpye PostCode cannot be assigned to type Surname. You'll also find it helps when you do overloading. You can turn nasty code like this:
FindByPostCodeAndSurname(string postCode, string surname);
FindByPostCode(string postCode);
FindBySurname(string surname);
Into this:
Find(PostCode postCode, Surname surname);
Find(PostCode postCode);
Find(Surname surname);
How much cleaner is that? Of course there are other ways to skin that cat but you will still find that those ugly method names dissapear (especially in factories etc.).

Because a PostCode was born of string
The other thing is PostCode will start his life out as a string of some form. Either from a web form or a database but somewhere he was made out of a string. This is where the implicit overloading comes in: we can allow PostCode to easily start out as a string and handle like a string when he needs to (because he's gonna need to):
PostCode postCode = Form["PostCode"];
Then somewhere far away:

Parameter["PostCode"] = Address.PostCode;
Because a PostCode isn't a string
The other thing is PostCode isn't a string. Sure somewhere he starts life as a string and somewhere you've got to have a string with the real post code in it but somewhere even further down that ain't a string at all it's a char array and somewhere further down... The point of OOP is to abstract real world things and encapsulate them and if you let PostCode wander around your system as a string he's never gonna reach his full potential (and he might just wander where he shouldn't). All the other bigger grown up objects are going to have to do everything for him: deciding whether he's valid, chop him up to find out what his area code is, compare him to other postcodes to see if they're in the same area. The poor old postcode will never reach his potential and instead will be pushed and shoved around by all the bigger boys.

You are a cruel, cruel programmer to let this happen: you are just as bad as those parents who never give their children any responsibility and then moan at them for being incapable of doing anything for themselves. But there is still hope: give your PostCode some responsibility and start by making him a Whole Value:
struct PostCode
{
Area {get;}
District {get;}
Sector {get;}
InwardCode {get;}
OutwardCode {get;}
}
Doesn't that look better? Now instead of this:
string postCode = "SQ1 1EQ";
if(LondonPostCodes.Contains(postCode.Substring... yuk I can't go on!
You can do something beautiful like this:
postCode = "SQ1 1EQ";
if(LondonPostCodes.Contains(postCode.Area)) ...
This of course goes even further because Area is a whole value too and you may decide that it should know what city it is. So the code now becomes:
if(postCode.Area.City.Equals(City.London))
Now PostCode can take all of that nasty horrible code that all the bigger boys had (and probably duplicated) and deal with it himself.

Because a PostCode should be a legitimate PostCode
Validation is also a good responsibility of a whole value so you can make the thing blow up if you try and put something bad in it (just the same as a DateTime will). For extra safety you can add Parse and TryParse methods to your Whole Value (I have them as standard).

So not only does your code become more type safe, more powerful and flexible but it also becomes more stable. No longer does every other object have to keep checking whether the postcode is in good shape or reference some nasty function library to find out the area; our little PostCode string has grown up into a real object at last and can now go out into the big wide world knowing he can shoulder the responsibility of keeping himself valid and answer all the questions people want to know of him.

So now whenever you see a simple type like a string or an int sittting on the outside of one your classes take a close look at it and ask youself what could have been if you'd only let it become a whole value.

No comments:

About Me

My photo
West Malling, Kent, United Kingdom
I am a ThoughtWorker and general Memeologist living in the UK. I have worked in IT since 2000 on many projects from public facing websites in media and e-commerce to rich-client banking applications and corporate intranets. I am passionate and committed to making IT a better world.