Saturday 30 June 2012

Type Safe Enum Pattern in C#

The Context

Sometimes, a feature of the C# language just isn't quite enough to do the job you'd like it to. An alternative view is that it wasn't designed to do that; I hear what you're saying, but let's ignore that for the time being. The built in structure to restrict a list of items to a known set is the enumeration. This is designed to provide a programmer with a choice of strongly named constants, that each map to a numeric value.

The Example

Sometimes the list of items that is needed is not numeric, but a string; take for example the following data structure, which maps a list of features available on to a list of cars.

Given feature.Id is calculated by the database on entry (Sql Server 'IDENTITY', MySql 'AUTO_INCREMENT'), the Id of the feature can't be relied on to be constant across different installations, so the Name column needs to be used as the identifying property.
When searching for whether a particular feature is available on a car, some SQL needs to be constructed, to query the table, e.g.

In order to constrain the list of features available to a developer, the usual approach would be to create an enum containing the features available

enum.ToString()

The enum would then be used to build the Sql by using the ToString() method of the enum:
This method will rely on the enum being named identically to the database value, and will not allow spaces in the name, so "Air Conditioning" and "Heated Seats" are not valid names.


Attributes

An alternative method is to create a custom attribute for the enum to define the database name:
This allows the "name" of the feature to be specified, with the added advantage that the attribute name does not need to match the enum name. However, there is extra plumbing required to reflect the attribute value at runtime:




The Type Safe Enum Pattern

There is an alternative to the above solutions, and it is to effectively roll your own enum. I'm not going to pretend any of this was my idea, I believe there are examples of it in the hieroglyphics from Tutankhamen's tomb.
The best way to describe the pattern is to show an example:



















This gives you a class that has static properties with a type of that class... ouch. But it means that the usage of the class is almost exactly the same as the usage of an enum, but without the confusion of an enum being intrinsically a number:







Thursday 28 June 2012

How I should have noticed I broke the code

In yesterday's exciting episode, I showed how some prefactored code duped me into some "innocuous" refactoring.

Having read the article, my friend and ex-colleague @NathanGloyn (check his blog here) posed the question as to why I hadn't gone all the way and refactored the code to a switch statement instead of a series of if...else if... blocks.

I really wish I had; as Nathan confirmed, the C# compiler would have refused to compile the code because of the duplicated values, and I could have invoked the keystroke of the gods, Ctrl-Z:


















I've been trying to think why I didn't take that step and refactor further. Ironically, I think I was scared of breaking things if I refactored too hard.

Wednesday 27 June 2012

How I broke the code

One of my favourite coding analogies is Uncle Bob's Boy Scout Rule - "Always leave the campground cleaner than you found it". I try to apply this rule when I have the drains up on a piece of code, if it looks like it needs some love.

I got bitten today, when a piece of boy scouting I'd done hit production. With hindsight it was obvious that I should have taken more care, but the trapdoor I fell down was a humdinger.
The code I thought I was refactoring was similar to this:
So I added some basic optimisation:
All good...
Except, the code I actually refactored to ended up looking like this:
Can you spot the difference? I didn't! It's subtle, but the effect was a huge change in behaviour.
I've learnt lots from this:
  • tiny refactors can have a huge effect;
  • code will sometimes come back and bite you;
  • a peer review is not a safety net;
  • you can't guarantee a refactor unless you have solid unit tests;
  • don't assume that code you're about to refactor is in any way optimal;
and, most important
  • when a ticket comes in around some code you've changed, grab it

Monday 11 June 2012

The Design of Everyday Things III

or

Why did you change it? What was wrong with it as it was? Where's the feedback now?

Imagine the scene... you're travelling along the motorway at 69.7mph, 315ft behind the car in front. The windscreen is getting slightly misty so you decide to use your car's blower to direct a blast of air on the windscreen.  You risk a glance at the digital climate control console:


That's blowing on the windscreen and your feet.
You look for *the button*

You press it, and risk another glance at the console:

That's now blowing at the windscreen, your feet and your face; that's less effective than the last one, so you press the button again. And another glance:

Hmm, now it's not blowing on the windscreen at all. Just your feet and face. The windscreen's getting more misty now. Let's see what the button does this time:

I don't even know what this symbol means. I get feet and face, but "up"? Symbolised by an empty triangle instead of filled... still, it's not blowing on the windscreen, so let's hit the button. Again. And look. Again.

Ok, it's not pointing at your feet any more. Just your face, and "up". You sigh. The windscreen mists up even more. You press the button. Surely...

No. It's just blowing directly at your face. You wish hard, and press the button again.

At last, you now have air blowing at your windscreen.

But at what potential cost?  Assuming you followed the instructions above, you will have taken your eyes off the road 8 times. There's no link between a push of the button and its result. Compare this to the more traditional analogue heater/blower console:

One glance and you can see that to get air on the windscreen, you need to twist the right-hand dial 1/5 clockwise. You'll feel the 1/5 turn, you'll get instant feedback. Simple. Great UX.