Coding: Connascence - Some examples
I’ve been reading Meilir Page Jones' 'Fundamentals of Object Oriented Design in UML' recently and one of the chapters that I found the most interesting is the one where he talks about 'connascence'.
Connascence describes the relation between two different bits of code and two bits of code are said to be connascent if a change to one bit of code would require a change to the other bit of the code or if some change to another piece of code would require both bits of code to change for our program to still be correct.
I think this principal is quite similar to the idea of coupling which we seem to use more frequently these days but I found it quite compelling that as I was reading through the different types of connascence that Page-Jones describes I was easily able to identify mistakes I’ve made and seen made in code.
There are many different types of connascence and Jim Weirich goes through many of them in his presentation from MountainWest RubyConf 2009 titled 'The building blocks of modularity'.
I’ll just cover a couple of the ones that seem to cause the most pain from my experience.
Connascence of execution
This describes the situation where two different lines of code have to be executed in a certain order for the program to be executed correctly.
A typical example of this type of connascence occurs when we make use of setter methods to construct objects:
public void SomeMethod()
{
var someObject = new SomeObject();
someObject.Property1 = new Property1();
// some where else far far away
SomeOtherMethod(someObject);
}
private void SomeOtherMethod(SomeObject someObject)
{
someObject.Property1.AnotherProperty = new AnotherProperty();
}
In this example we need line 2 to be executed before line 13 otherwise we’ll get a null pointer exception. These two lines therefore have connascence of execution.
Quite often line 13 of this example would be hidden away in a chain of method calls and it wouldn’t be completely obvious that it relies on a line further up being executed first.
We eventually end up making what we think is an innocuous reordering of the method calls and suddenly our code doesn’t work any more.
In this case to reduce this type of connascence we might look at using a constructor to create our objects, use one of the builder/factory patterns or at least try and capture related code in the same method so that the potential for confusion is reduced.
Connascence of algorithm
This typically describes the situation where we are making use of a data structure in a specific way such that all the pieces of code which interact with that data structure need to know exactly how that data structure works in order to make use of it.
This problem seems to happen particularly when we’re over using lists when perhaps another level of abstraction is required.
One example of this might be where a piece of code assumes that another piece of code inserts a certain value into a list.
public class AClassWhichPutsASpecificItemInAList
{
public List<SomeObject> CreateTheEvil()
{
var myEvilList = new List<SomeObject>();
myEvilList.Add(new SomeObject("SomeSpecialName"));
// and so on
return myEvilList;
}
}
public class SomeOtherClassFarFarAway
{
public void SomeMethod(List<SomeObject> someObjects)
{
var speciallyNamedObject = someObjects.Where(s.Name == "SomeSpecialName").First();
}
}
Despite the fact that these two classes never refer to each other they have connascence of algorithm because if 'AClassWhichPutsASpecificItemInAList' decides not to put that value into the list then 'SomeOtherClassFarFarAway' may stop working so we will need to change that as well.
This type of connascence is much more implicit than some of the other types and it may not be immediately obvious that two pieces of code are related.
We could get around this problem by encapsulating this type of logic in its own type so that at least we only have to deal with it once.
The goal is to try and make an implicit piece of code more explicit.
Connascence of convention
This describes the situation where there is an implicit convention for the meaning behind the value of a certain piece of code such that every other bit of code that touches it needs to know this convention. This is quite similar to connascence of algorithm.
An example that I came across recently was around how we passed around the value of an option selected on a form by the user.
The user could either select 'Yes', 'No' or they could choose not to answer the question. If they entered 'Yes' then they would need to answer another question immediately after this.
Later on we needed to pass this data to the service layer. A value of 'No' if they selected that, the answer to the next question if they answered 'Yes' and 'None' if they didn’t enter anything.
We ended up having code similar to this in the service call and then again in the binder:
public class TheServiceThatUsesThatValue
{
public void CallTheService(string theAnswer)
{
var theRequest = new ServiceRequest();
if(theAnswer == "None")
{
theRequest.TheAnswer = "None";
}
else if(theAnswer == "No")
{
theRequest.TheAnswer = "No";
}
else
{
theRequest.TheAnswer = LookUpTheCodeFor(theAnswer);
}
}
}
We eventually gave up on the idea of passing 'None' around because that was the bit causing the most confusion. Instead we stored the answer in a nullable data type and then did the conversion to 'None' when necessary in the Service class.
In summary
There are just a couple of the examples that Page-Jones outlines but the general idea is that we want to try and minimise the connascence in our system by creating well encapsulated objects.
Within those objects it makes sense to keep connascence high and in fact if it’s not then it might suggest that we have another object waiting to get out.
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.