Enforce Correct Usage By Wrapping Types
Have you ever written an API or a method and expected it to be used a certain way, but then found people doing all kinds of bad things with your precious API that you never intended?
I was working on building an automated testing framework for a welfare system in which cases could be added and benefits run for a given month.
A typical scenario might be something like:
- Create a new case for this month.
- Add some people, do some things.
- Run benefits for this month.
- Run benefits for the next month. Something should happen because the child on the case is now 19.
Seems pretty straightforward until you consider what happens if someone hard codes dates into the test.
I am sure you can imagine plenty of scenarios in a situation like this where hard coded dates would eventually cause all kinds of problems.
The problem is?
If you create an API which takes in various dates, how do you ensure the dates passed in are calculated and not just hard coded?
Why convention doesn?t work
Your first response might be to create some documentation that describes the importance of making dates ?float? forward with time, and not be hard coded.
You might clearly describe how you should not hard code July 7th 1990 to be the birth date of a 20 year old for a test case.
You might give some guidelines of how to properly calculate ages.
But, you have no way of making sure the users of your API will follow those conventions or even read your document.
What if we do the hard work for them?
Why utilities are still not good enough
The next idea you might have is to create utilities that would do date conversions and calculate ages for the user of your API, so that they just have to remember to use them and all will be good.
This solution is better than one relying purely on convention, but still has some major flaws. Part of the problem of utility methods is that they are not self-discoverable. I?ve talked about before how DateUtilities like classes can get overlooked if you don?t know the utility class is there.
If you are relying on your users to go out and find your utility methods to make life easier for them, you are putting a large amount of responsibility in the wrong place. In my experience, it is very unlikely that anyone but yourself will actually use the utility methods.
You really need a way to stop them from being able to hard code dates.
How can you stop someone from hard coding dates?
Don?t accept dates in your API.
It is simple.
What? What do you mean? You can?t just stop accepting dates, you need dates. Not just dates, but integers, or strings. You can?t just not accept the bad data? or can you?
Consider wrapping the data type in another data type which creates your desired data type, but only using the convention or rules you desire.
Let me show you an example with the dates:
In the above example, you can see that I am directly taking a .NET DateTime object. You can also see that I am directly hard coding a date when calling this method.
Let?s see if we can fix that.
Here is what we did:
- We created a custom type called CaseDate.
- We replaced all the external usages of DateTime with CaseDate.
- We provided methods on CaseDate which allow the creation of dates, only through the way we want to allow them to be created.
- Now we can easily prevent users of the API from being able to create hard coded dates. The API only will take CaseDate objects and internally creates DateTime objects from the CaseDate objects.
- We can add other methods to CaseDate to allow the creation of dates from x number of months ago or in the future, or any other valid creation method we want.
By doing this, we are restricting the valid set of inputs to our methods at compile time, not throwing exceptions at run time.
We are achieving the same kind of valuable input constrictions that we have been able to achieve with enumerations, except we are adding more complex restrictions than a simple list of selections.
Where can I use this?
Not just dates. There are many places where restricting the input to your method or API is going to simplify logic, protect against error conditions, and enforce constraints.
Consider using this pattern in some of these scenarios:
- You want to use an enumeration, but your list of possible choices is too large, or repetitive.
- Anywhere you are using a primitive type and that primitive type represents some more complex concept.
- Anywhere you have parameter validation or manipulation repeated in multiple methods.
- Anytime you process some input or repeat a series of steps to transform it before sending it as input.
Every time you are about to make a primitive type a parameter to a method you should ask yourself if it makes sense to wrap the primitive type into an class.
Remember, especially when creating an API, any time your method takes a primitive type, you lose the ability to constrain that input, and are forced to validate it instead.
Should you always wrap all primitive types? No, but it is another tool you can use to provide an extra layer of indirection between the logic of your API and your callers use of it.