Original post can be found in the 10Pines blog.
Write less error-prone code.
First things first: What is the problem space? Simply put, it is the entire spectrum of inputs that exists in the process of finding a solution to a problem. Say you want to buy a soda from a vending machine. You now have a problem to solve: “Decide what beverage to purchase”. Every button is part of the problem space; yet you can narrow it down to just buttons that get you a can.
Down to business
In order to present my idea, let’s take a somewhat simple example. a TennisMatch object, that represents just that: a tennis match.
One thing that a
TennisMatch should keep track of is its score. Let’s assume the match is responsible of doing this with a
score method. We have several design options:
Most of the times, we’d go for alternative 1 or 2. This makes our code look something like this:
Please bear with me for the sake of this example, because we are going to be using a dictionary with the player (whichever their representation:
Player) as the key, and their score as the value. This decision in and of itself is not important, but rather a way to scaffold the point I’m trying to get across.
Which would lead to several unwanted ramifications: What if…
playerparameter is not in our
- the player is null?
- the player key is in the
Map, but the value of the map is
Before talking about how can we can improve our code, let’s take a minute to think how the latter in particular might be easily fixed.
We could avoid it altogether (and maybe the other ones) by forcing the
TennisMatch creation to have two players and making those players have a valid starting score.
Let’s jump briefly to another situation.
Suppose that we wanted to solve the following problem:
If we have 3 minutes for a presentation, and we need to equally divide them between n students, how many minutes would each student get?
A method that would solve this problem might look something like this:
Of course, it doesn’t make much sense to accept an
Object in the method signature, since we already know we are going to be dealing with numbers. And, mind you, because downcasting stinks.
So, based on what we already know, we could avoid downcasting, limiting the possible inputs from
I’d ponder this is not much better.
With a keen eye for finding problems, one might realize that if the parameter were
0, the program would explode.
Say this is not what we want, because we know that we are dealing with an amount of students,
n should never be zero. Nor should it be less than zero.
This suggests to me that our method is accepting a problem space much much larger than what we actually can and should be able to handle.
We could take two paths to fix this:
- Preparing for the worse and checking for this restrictions to be met inside our method
- Deal with the problem head on, and limit the problem space!
Math has a name for this kind of numbers: Natural numbers. Java does not. At least not out-of-the-box. But we can create the
This way, we wouldn’t need to be prepared for those edge cases, since we’d have limited the input of our method, and deal only with valid numbers:
Java does not have an object to handle the filter for unauthorized users on a rest API call either, yet we are eager to create
UnauthorizedUserFilter implements RESTFilter. I don’t usually see simple problem limiting classes like
NaturalNumber. It baffles me.
williamsVsPetrova.score. Let’s think about this problem as a math function. Its domain should be only two values. One for each player.
Now, lets analyze the parameter types we mentioned above:
public void score(
Stringtype is infinitely (disregard limited memory problem) large. So it might not be the best type. If we do go for this option, we will be forced to do some assertions if the name is one of the player participating in the
public void score(
player): Same as 1., but with added obfuscation. Please don’t.
public void scorePlayer1(): No parameter means the domain size is only one. We are half way there! We re now forced to implement public void scorePlayer2(). The code for these two methods might be conspicuously similar, so we might refactor them like so:
Although we are using a
String with its infinitely large domain space, the method is private and we are sure that the only possible calls are with one of two valid values.
- public void score( Bool player): In spite of the fact that the the Boolean type might fit our domain-length restrictions; think about how it will look like when used:
A side note on the language
I used Java throughout the post, and although I took advantage of the fact that Java is strongly and explicitly typed with static type checking, this principles are not language-specific.
Bonus: Make the problem space explicit!
Since a method can have its argument either
null or an actual value; rather than have a comment, use a
type that represents both cases (
Optionals in the case of Java).
- Constructors are important. If you can’t build invalid objects, then you can write code assuming the objects you interact with are valid. This can save you many headaches.
- Don’t be afraid of creating classes with the sole purpose of delimiting your problem space.
- Thinking about the domain space of a method, albeit a good technique, is not the only one. Think of it as an extra tool in your tool belt.