Your code is more strongly coupled than you think
In previous articles I introduced connascence—the idea that code coupling can be described and quantified—and discussed five kinds of static connascence. In this article, we’ll wrap up our tour of connascence with a discussion of the deeper kind of connascence, dynamic connascence.
Dynamic connascence is visible only at runtime. Because it’s discovered late and it’s often non-local, dynamic connascence usually represents stronger coupling than static forms.
Connascence of execution
Connascence of execution occurs when code must execute in a certain order for the system to function correctly. It is often referred to as “temporal coupling.” Here is a simple example:
userRecord.FirstName = 'Alicia';
userRecord.LastName = 'Florrick';
userManager.addUser(userRecord);
userRecord.Birthday = new Date(1968, 6, 24);
This code adds the Birthday
value after the user has been added. This clearly won’t work. This mistake can be caught by examining the code, but a more complex scenario could be harder to spot.
Consider this code:
sprocketProcessor.AddSprocket(SomeSprocket);
sprocketProcessor.ValidateSprocket();
Does the order of those two statements matter? Does the sprocket need to be added and then validated, or should it be validated before it is added? What happens if the sprocket doesn’t validate? It is hard to say, and someone not well-versed in the system might make the mistake of putting them in the wrong order. That is connascence of execution.
If you ever see a comment along the lines of, “This line of code MUST BE EXECUTED BEFORE the one below!!!!!”, then the developer before you probably ran into a problem caused by connascence of execution. The exclamation points tell you the error wasn’t easy to find.
The key question: Would reordering lines of code break behavior? If so, then you have connascence of execution.
Connascence of timing
Connascence of timing occurs when the timing of execution makes a difference in the outcome of the application. The most obvious example of this is a threaded race condition, where two threads pursue the same resource, and only one of the threads can win the race.
Connascence of timing is notoriously difficult to find and diagnose, and it can reveal itself in unpredictable ways. Anyone who has delved deeply into debugging thread problems is all too aware of connascence of timing.
The key question: Would changing thread scheduling, network latency, or a timeout alter correctness? Then you have connascence of timing.
Connascence of value
Connascence of value occurs when several values must be properly coordinated between modules. For instance, imagine you have a unit test that looks like this:
[Test]
procedure TestCheckoutValue {
PriceScanner = new TPriceScanner();
PriceScanner.Scan('Frosted Sugar Bombs');
Assert.Equals(50, PriceScanner.CurrentBalance);
}
So we’ve written the test. Now, in the spirit of Test Driven Development, I’ll make the test pass as easily and simply as possible.
void PriceScanner.Scan(aItem: string) {
CurrentBalance = 50;
}
We now have tight coupling between TPriceScanner
and our test. We obviously have connascence of name, because both classes rely on the name CurrentBalance
. But that’s relatively low-level connascence, and perfectly acceptable. We have connascence of type, because both must agree on the type TPriceScanner
, but again, that’s benign. And we have connascence of meaning, because both routines have a hard-coded dependency on the number 50. That should be refactored.
But the real problem is the connascence of value that occurs because both of the classes know the price—that is, the “value”—of Frosted Sugar Bombs. If the price changes, even our very simple test will break.
The solution is to refactor to a lower level of connascence. The first thing you could do is to refactor so that the price (the value) of Frosted Sugar Bombs is maintained in only one place:
void TPriceScanner.Scan(aItem: string; aPrice: integer) {
currentBalance := currentBalance + aPrice;
}
Now our test can read as follows:
[Test]
void TestCheckoutValue {
PriceScanner = new TPriceScanner;
PriceScanner.Scan('Frosted Sugar Bombs', 50);
}
As a result, we no longer have connascence of value between the two modules, and our test still passes. The price is reflected in only one place, and any price will work for our test. Excellent.
The key question: Do multiple places need updating when a constant or a configuration setting changes? If so, then you have connascence of value.
Connascence of identity
Connascence of identity occurs when two components must refer to the same object. If the two components refer to the same object, and one changes that reference, then the other component must change to the same reference. This, too, is a subtle and difficult-to-detect form of connascence. In fact, connascence of identity is the most complex form of connascence.
As an example, consider the following code:
class ReportInfo {
private _reportStuff = "";
get reportStuff(): string {
return this._reportStuff;
}
set reportStuff(value: string) {
this._reportStuff = value;
}
}
class InventoryReport {
constructor(public reportInfo: ReportInfo) {}
}
class SalesReport {
constructor(public reportInfo: ReportInfo) {}
}
function main(): void {
const reportInfo = new ReportInfo();
reportInfo.reportStuff = "Initial shared info";
const inventoryReport = new InventoryReport(reportInfo);
const salesReport = new SalesReport(reportInfo);
// Do stuff with reports...
console.log("Initially shared object?",
inventoryReport.reportInfo === salesReport.reportInfo); // true
// Change one side to point at a new identity
const newReportInfo = new ReportInfo();
newReportInfo.reportStuff = "New info for inventory only";
inventoryReport.reportInfo = newReportInfo;
// Now they refer to different ReportInfo instances (Connascence of Identity risk)
console.log("Still shared after reassignment?",
inventoryReport.reportInfo === salesReport.reportInfo); // false
// Observable divergence
console.log("Inventory.reportInfo.reportStuff:", inventoryReport.reportInfo.reportStuff);
console.log("Sales.reportInfo.reportStuff:", salesReport.reportInfo.reportStuff);
}
main();
Here we have two reports, an inventory report and a sales report. The domain requires that the two reports always refer to the same instance of ReportInfo
. However, as you can see in the code above, in the middle of the reporting process, the inventory report gets a new ReportInfo
instance. This is fine, but the sales report must refer to this new ReportInfo
instance as well.
In other words, if you change the reference in one report, then you must change it in the other report for the system to continue working correctly. This is called connascence of identity, as the two classes depend on the same identity of the reference.
The above example is simple to be sure, but it’s not hard to conceive of a situation where the references change in unknown or obfuscated ways, with unexpected results.
The key question: Must two components always point to the same instance? Then you have connascence of identity.
Okay, so there we have it—the four kinds of dynamic connascence. Now, I’ll be the first to admit that this connascence stuff is quite nerdy and a little hard to understand. But much of it covers coding issues you already intuitively knew but perhaps never considered in a formal way. How many types of connascence are wrapped up in the “Don’t repeat yourself” (DIY) principle?
All I know is that I don’t like heavily coupled code, and anything I can learn that helps me avoid it isn’t too nerdy for me.
Original Link:https://www.infoworld.com/article/4042331/your-code-is-more-strongly-coupled-than-you-think.html
Originally Posted: Wed, 20 Aug 2025 09:00:00 +0000
What do you think?
It is nice to know your opinion. Leave a comment.