Sorry, this is late. Excuses, excuses.
I feel like I’ve seen this somewhere else once before in my life, but it the idea doesn’t seem to have spread much. That’s probably due to the lack of traction for pre- and postconditions in general. So, we’ll start with those.
Preconditions and Postconditions
Preconditions and postconditions are a little old-fashioned, having been largely replaced by the proliferation of TDD, but I don’t think they should be completely ruled out.
Preconditions are a set of assumptions that your function has about the parameters being passed into it.
This is especially useful when you accept “primitive” types, but what you actually want is considerably more restrictive than what those types provide. For example, you’re accepting a string that is meant to be the path to a file. Many would argue that you should restrict it to a
FilePath type of some sort that makes sure the path string is of the correct format when the object is created. I would argue that that is the ideal, but not always the pragmatic solution. Another example along those lines is taking in a
FilePath, but the precondition is that the file pointed to already exists and it has the correct file extension. Creating specialty types just to ensure those preconditions will almost definitely be a pain.
Postconditions are similar to preconditions, but are a promise back to the caller that either a certain command was carried out or that the return value will be of a certain shape or format (assuming the preconditions were met).
Both of these, if they are ever actually tested in the code (when I first heard of them, it was simply something you put into the function’s documentation), are typically done with
Assertions are statements that take a boolean expression and optionally (usually) a string message. If the boolean expression evaluates as false, the statement throws/raises a specialized exception, using your message if it was provided. They also have a super power: they can disappear. At a compile or runtime stage, users can shut off assertions in code. The idea behind this is to test run the code with the assertions on so that it can do early detections of potential problems. But once you’ve tested the code enough, you can be reasonably sure of its safety and shut off the assertions for production and not waste precious cpu cycles (you still need user input guards, though, so don’t use assertions for those).
This is where matchers come in. By matchers, I mean any test library that uses a fluent api to make assertions. The best part about using them? They’re almost definitely already set up to do assertions. At the very least, they’re throwing assertion exceptions (which isn’t enough; more on this in a little bit).
Anyway, the biggest problem with most pre- and postconditions in functions is that they’re ugly and take up a lot of space. So, using a library that lets you express and test ideas about objects is incredibly helpful in fixing that.
So, pick out your favorite assertion library (mine is immatcher, which was made by me; my very incomplete youtube video series is about converting it to kotlin) and dig into the code behind it to see if it uses
assert statements. If not, you can probably just write a little helper function that can use the matchers via an
assert statement. And, if your language allows for inlining functions (like Kotlin does), you could even inline that function call so as to not waste the call when assertions are shut off.
I was going to give a simple example of what some of the improvements could look like, but this post is already late, plus anyone who has used matchers has already seen their benefits over regular assertions, so I feel like it’s an easy sell.
The harder part might be to convince people to use preconditions and postconditions, and that’s a whole different topic. In short, if you want to use them but feel like they may be a waste, then I recommend using them about one layer in from user interaction to make sure your user interaction layer is doing its job well.
Until next time…