Connascence of Meaning in Java
What is the connascence of meaning between two classes? Check out this example in Java to find out.
Join the DZone community and get the full member experience.
Join For FreeConnascence is a term used to describe the relationship and taxonomy of dependency between two software components. Connascence was used Meilir Page-Jones in the 90's in the book What Every Programmer Should Know About Object Oriented Design (later updated to use UML instead of Page-Jones's own modelling language in Fundamentals of Object-oriented Design in UML (Object Technology Series)) but has not seen widespread popularity in the development community outside of specific areas such as this talk by Jim Weirich and a series of blog posts and a talk by Kevin Rutherford.
Connascence provides a taxonomy for dependency and coupling, allowing developers to reason about the quality of their code. There are two broad categories of connascence, and 9 different types or levels.
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 also be 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:
public class Dinosaur {
private int diet;
public Dinosaur(int diet) {
this.diet = diet;
}
public void feed(Food food) {
// Eat the food
}
public int getDiet() {
return diet;
}
}
public class DinosaurHandler {
private Dinosaur dinosaur;
public DinosaurHandler(Dinosaur dinosaur) {
this.dinosaur = dinosaur;
}
public void feedDinosaur() {
switch (dinosaur.getDiet()) {
case 1: // Dinosaur is a carnivore
dinosaur.feed(new Goat());
break;
case 2: // Dinosaur is a herbivore
dinosaur.feed(new Cabbage());
break;
}
}
}
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:
public class DinosaurEgg {
private long timeLaid;
public long getTimeLaid() {
return timeLaid;
}
private boolean isReadyToHatch() {
return (System.currentTimeMillis() / 1000) - timeLaid > 600;
}
public Dinosaur hatch() {
if (isReadyToHatch()) {
return new Dinosaur(this);
} else {
return null;
}
}
}
public class DinosaurRegistry {
public Dinosaur get(String name) {
// Get dinosaur with this name
// Return null if no dinosaur exists
}
public void register(Dinosaur dinosaur) {
// Register a dinosaur
// Name must be unique
}
}
public class DinosaurLab {
private DinosaurRegistry registry;
public Dinosaur hatch(DinosaurEgg egg) {
Dinosaur dinosaur = egg.hatch();
if (dinosaur == null) {
return null;
}
String name = dinosaur.getName();
if (registry.get(name) == null) {
registry.register(dinosaur);
return dinosaur;
} else {
return null;
}
}
}
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 its 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:
public enum Diet {
HERBIVOROUS, CARNIVOROUS
}
public class Dinosaur {
private Diet diet;
public Dinosaur(Diet diet) {
this.diet = diet;
}
public void feed(Food food) {
// Eat the food
}
public Diet getDiet() {
return diet;
}
}
public class DinosaurHandler {
private Dinosaur dinosaur;
public DinosaurHandler(Dinosaur dinosaur) {
this.dinosaur = dinosaur;
}
public void feedDinosaur() {
switch (dinosaur.getDiet()) {
case CARNIVOROUS:
dinosaur.feed(new Goat());
break;
case HERBIVOROUS:
dinosaur.feed(new Cabbage());
break;
}
}
}
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 make a mistake.
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:
public class DinosaurEgg {
private static final long TEN_MINUTES = 600000;
private Date timeLaid;
public Date getTimeLaid() {
return timeLaid;
}
public boolean isReadyToHatch() {
return System.currentTimeMillis() - timeLaid.getTime() > TEN_MINUTES;
}
public Dinosaur hatch() {
if (isReadyToHatch()) {
return new Dinosaur(this);
} else {
throw new EggNotReadyToHatchException();
}
}
}
public class DinosaurRegistry {
public boolean isNameAvailable(String name) {
// Check the required name is available
}
public Dinosaur get(String name) {
// Get dinosaur with this name
// throw DinosaurNotFoundException if no dinosaur with name exists
}
public void register(Dinosaur dinosaur) {
// Register a dinosaur
// Name must be unique
}
}
public class DinosaurLab {
private DinosaurRegistry registry;
public Dinosaur hatch(DinosaurEgg egg) {
if (!egg.isReadyToHatch()) {
throw new CouldNotHatchEggException("Egg is not ready to hatch");
}
Dinosaur dinosaur = egg.hatch();
String name = dinosaur.getName();
if (!registry.isNameAvailable(name)) {
throw new CouldNotRegisterDinosaurException("Name is not available");
}
registry.register(dinosaur);
return dinosaur;
}
}
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 the 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 the get() 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?
Published at DZone with permission of Sean O'Toole. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments