CO[R]RECT: [R]ange
Learn the importance of range and the use of constraints in writing tests.
When we use Java’s built-in variables, we often get far more capacity than we need. If we represent a person’s age using an int
, we’d be safe for at least a couple of million centuries. Inevitably, things will go wrong, and we’ll end up with a person a few times older than Methuselah, or a backward time traveler with a negative age.
Excessive use of primitive datatypes is a code smell known as a primitive obsession. A benefit of an object-oriented language like Java is that it lets us define our own custom abstractions in the form of classes.
In order to understand the importance of range, we will look into the examples of circles and rectangles.
Bearing
A circle has only 360 degrees. Rather than store the direction of travel as a native type, we can create a class named Bearing
that encapsulates the direction along with logic to constrain its range. Tests show how it works. Below is the code for testing the Bearing
class:
package scratch; import iloveyouboss.*; import org.junit.*; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; public class BearingTest { @Test(expected=BearingOutOfRangeException.class) public void throwsOnNegativeNumber() { new Bearing(-1); } @Test(expected=BearingOutOfRangeException.class) public void throwsWhenBearingTooLarge() { new Bearing(Bearing.MAX + 1); } @Test public void answersValidBearing() { assertThat(new Bearing(Bearing.MAX).value(), equalTo(Bearing.MAX)); } @Test public void answersAngleBetweenItAndAnotherBearing() { assertThat(new Bearing(15).angleBetween(new Bearing(12)), equalTo(3)); } @Test public void angleBetweenIsNegativeWhenThisBearingSmaller() { assertThat(new Bearing(12).angleBetween(new Bearing(15)), equalTo(-3)); } }
The constraint is implemented in the constructor of the Bearing
class:
public class Bearing {public static final int MAX = 359;private int value;public Bearing(int value) {if (value < 0 || value > MAX) throw new BearingOutOfRangeException();this.value = value;}public int value() { return value; }public int angleBetween(Bearing bearing) { return value - bearing.value; }}
Note that in the above code, angleBetween()
(line 12) returns an int
. We’re not placing any range restrictions (like disallowing negative returns) on the result.
The
Bearing
abstraction makes it impossible for client code to create out-of-range bearings. As long as the rest of the system accepts and works withBearing
...