Connascence of Meaning

Connascence of meaning is the first type of connascence that we might want to do something about, rather than just being a natural part of programming itself. Connascence of meaning refers to when multiple components must agree on the meaning of values being used. Connascence of meaning is also called connascence of convention, as it can be better described as multiple components sharing a convention for the meaning of particular values. We’re going to use both of these terms throughout.

Consider the following classes:

These classes are coupled together by connascence of meaning- they both have to agree on the convention that using the number 1 for a dinosaur’s diet means carnivore, and 2 means herbivore. Presumably when they get some omnivorous dinosaurs, they’re going to have to agree that 3 means omnivore too.

A more common and insidious example of connascence of meaning in Java is agreeing on the meaning of null values.

Consider the following classes:

We’ve got a whole bunch of connascence of meaning over here around the meaning of null.

DinosaurEgg has some connascence over the meaning of timeLaid- all callers must agree with the convention of time laid referring to seconds. We also see our first example of the infamous “meaning of null”, where in this case null refers to the fact that the egg could not be hatched, because it wasn’t laid more than 10 minutes ago.

Our  DinosaurRegistry  uses a null return value when calling get() to indicate that the specified Dinosaur could not be found. Any callers must agree on the convention that null in this case means “dinosaur not found”.

DinosaurLab is the first place we experience this connascence of meaning across different components, as it has to interact with both DinosaurEgg and DinosaurRegistry, and as such has to agree on the convention of null. The DinosaurLab agrees on the meaning of null for the DinosaurRegistry, and does a null check on get() to make sure the specified name is not already registered for a Dinosaur (We don’t want a T-Rex and a Triceratops to both be called Bessie- what if you get confused when someone asks you to go give Bessie her medicine and wander off to the T-Rex enclosure? Not good things). The lab also has to agree on the convention of an egg returning null if it can’t be hatched, and checks whether the dinosaur returned from the egg is null or not.

You’ll notice that the DinosaurLab returns null in two different places, and for two different reasons. In the registry and the egg, null had one meaning and one cause. Not brilliant, but at least the calling classes only had to agree on the convention of null meaning one thing. In the lab, the null convention is used to convey that an egg could not be hatched- both because the egg is not ready to be hatched, and because the name is already registered. When our caller gets a null back from the lab- what do they do? Null means an egg couldn’t be hatched, but we have no idea why. Do we wait before trying again because the egg isn’t ready, or do we change it’s name because there’s already a T-Rex called Princess?

Fortunately, there are things we can do to reduce connascence of meaning into connascence of type and connascence of name.

For our first example, we can easily remove the convention of particular numbers meaning particular diets by introducing our own Diet type, we we can reference the diet of the dinosaur by name.

Consider the following change:

We’ve introduced a new enum Diet to describe the diet of our Dinosaur, and no longer have to rely on knowing that 1 = carnivore and 2 = herbivore, we’ve got it defined right there in the name and type. This is much better than relying on the implicit convention we were using before, as it’s much harder to mess up.

In our second example, the easiest way to remove the different conventions and meanings of null is to just not use null any more. Instead of using null to represent a negative case, we’re going to throw exceptions. We’re also going to introduce some new methods to call- so we don’t have to use the new exceptions we’re throwing to control the flow of our program.

Consider the following changes:

Our DinosaurEgg now throws an EggNotReadyToHatchException if the egg is not ready to hatch, instead of returning null. Now instead of callers relying on the meaning of null being “not ready to hatch”, they can rely on the name of the exception that is thrown, which reduces our connascence of meaning over the convention of null down to connascence of name- of the exception that is thrown. We’ve also made that isReadyToHatch() method public, as it allows us to not have to rely on try-catching an exception to control our program flow. We got rid of the ambiguity around timeLaid, and change it to be an explicit date. We also got rid of the implicit meaning of “600” being 10 minutes, by making it a named constant so we can more easily understand the meaning of the value.

DinosaurRegistry throws a DinosaurNotFoundException when using the get- as it shouldn’t be the usual case that calling get() doesn’t return a dinosaur. Now people won’t have to rely on knowing the meaning of a returned null, they can instead rely on the name of the exception that is thrown. We’ve put another method in place to check whether a name is available, again so we don’t have to rely on try-catch to control our program flow. We probably shouldn’t have been using that method for checking names in the first place, really.

Our DinosaurLab can now use our two modified classes, safe in the knowledge that the only things it now needs to care about is the names of the new methods to call, or alternatively the names of the new exceptions being thrown from the existing methods. We’ve also removed the conflated meaning of null being returned out of the lab, by introducing new exceptions for each individual reason for failure.

Connascence of meaning is the lowest level of connascence that we can realistically do something about- and even though it is such a low level, it doesn’t mean we should just let it be. The examples we used today were small, but if left to grow unchecked could grow to tangle up the whole codebase in collective understanding of the meaning of certain values- the problem becomes much worse when it’s a DinosaurHandler, ShippingContainer, FoodFactory, Dinosaur, DinosaurLab, DinosaurEnclosure, etc. that all rely on the convention of 1 meaning herbivore and 2 meaning carnivore… or was it the other way around?

Author: Sean O'Toole

I like development and I like dinosaurs. Here we are.

Leave a Reply

Your email address will not be published. Required fields are marked *