Incoming: The coupling that wasn't there
Expressing intentions via explicit coupling.
As you may know from earlier articles, I have a side project to build a wiki. There is an API, which won’t concern us today, and a single page application (SPA), which will. The SPA is written in Typescript and React. Wiki page contents are written in Markdown, to which I’ve added two bespoke extensions:
Every occurrence of text such as
[[Habitable Code]]is replaced in the rendered page by a hyperlink; when the link is clicked, it opens the wiki page with that title. Fairly standard for a wiki.
A wiki page can also contain content that is calculated dynamically, at the time the page is rendered. This occurs whenever the page’s Markdown content includes text of the form
[macro: f], where f is one of
RecentChangesand a few others.
Incomingis particularly useful for Category pages, for example.
This is the top part of a rendered page:
Everything below the title is rendered by a
Hypercard React component. Here’s a CRC card for
And if the reader clicks the page’s title, that
Hypercard is replaced by a
PageMetadata component, which among other things displays a list of the pages that link to this page:
Here’s a CRC card for
We can see that
PageMetadata share a responsibility and at least one collaborator.
Here’s the current source code of the
But reference to that
<Incoming /> component represents knowledge of how a
Hypercard renders incoming links. So
PageMetadata knows how to insert a list of incoming wiki links into the display, and so does
Hypercard; they both use the
Incoming React component.
PageMetadata has “borrowed” part of the internal implementation of
I find these problems quite difficult to think about, so I made a diagram to help me:
We can see why it’s a problem if we consider what might change and potentially break this code.
Hypercard could replace its use of
Incoming with something different, having a different display or using a different API endpoint.
PageMetadata could do that too. And yet that’s not what my Customer wants. I checked with him (me), and he wants both
PageMetadata to calculate and display the incoming links in the same way.
This is a very important observation, and probably the key takeaway from this article: The intended coupling in this code comes directly from the Customer. We have a requirement that these two components display the same set of incoming links, calculated the same way and displayed the same way; and we have a requirement that they continue to evolve together (at least for the foreseeable future). And so there must be a single place in the code that reflects that requirement. The code should say “here’s the one decision that dictates the behaviour of A and B”; whereas the code we have says “currently A and B are independently choosing to use the same helper”.
I get a huge amount of benefit when I can relate coupling directly back to something from the Customer. I wonder:
Is it generally true that things should be coupled in the code if, and only if, the Customer wants them coupled in the application’s behaviour?
Another way to say the same thing: We have explicit coupling, but it is of the implementations, not the intent. The Customer’s intended coupling is missing (or is implicit at best). There is no representation in the code of the coupling we wish we had.
We can now see that the problem here is not the coupling per se, but the fact that the coupling we currently have is “accidental”. So how can I change that? How can I guarantee that both
PageMetadata use the same
Incoming component? And could I write a test for that? I think there is a choice between two general approaches to problems such as this:
Arrange for some other code to supply both
Hypercardwith the tools to be used for displaying incoming links.
Have one thing use the other — in this case,
In our case here, option 1 won’t scale. Based on the history of this codebase, it seems quite likely that my Customer will want
PageMetadata to evolve and display other stuff. Injecting one common algorithm now feels like a short-term solution; soon I will probably need to inject more and more.
So instead I’m going to adopt the second approach. I rewrite
PageMetadata to be this:
By making this change I have clearly and explicitly stated that
PageMetadata delegates to
Hypercard, and thereby makes use of all of its current and future capabilities.
In connascence terms, I’ve replaced Connascence of Algorithm by Connascence of Name and Type. I don’t find that to be as helpful as saying this:
I’ve represented the Customer’s intention explicitly, once and only once, in a single line of code.
And now that the code looks like this, it seems to me that I don’t need a test. Unless I want to capture the Customer’s requirement, of course. Would you write a test here?
Let’s step back for a moment and look at how this problem came to be in the code.
Obviously the first step would have been for me to ask the Customer how closely the
PageMetadata display should be coupled to that of the
Hypercard. This is something that software developers rarely do, and yet establishing the intended coupling in the requirements is an important part of the analysis of any user story, and getting it right can avoid huge amounts of future rework. It can be as simple as checking “Should these two domain concepts evolve together?” Of course, such questions weren’t necessary in the days when we perfected our requirements specification at the start of the project, and so I suspect we’ve simply forgotten to add it to our habits now that our software evolves iteratively and incrementally.
But there’s also another contributory factor in this case, and again it’s something I see in a lot of codebases:
Hypercard and its helpers were all just sitting around, mixed in with all of the other React components in the same source folder. Even though the helpers are — conceptually and practically — part of the implementation of
Hypercard, this was not reflected in the way these components were organised in the codebase. This problem is also evident in the CRC card shown above for
Hypercard: those helper components should not be listed as collaborators, because the intention is that they are implementation details.
Again, the intended coupling — this time it’s the developer’s intention — was not represented explicitly in the codebase in any way. And this made it easy for
PageMetadata to “reuse” the
Incoming component, despite the intention that, logically,
Incoming is “part of” the implementation of
In fact, this is how I noticed this problem. I was tired of having to sift through the huge, flat mess of components; I wanted to find groupings and patterns among them. As part of this effort I had decided that
Hypercard and its helpers should form a single encapsulation unit and I was moving them all into a new source folder. And that meant I had to change the import statement in
PageMetadata, which struck me as odd. And so here we are.
PageMetadata made use of
Incoming, but should that even have been possible? The code is obviously not clear enough about the fact that
Incoming is “part of”
Hypercard, so should
Incoming be invisible to any and all other components? And if so, how could I express that in Typescript?
Again, I can think of two options (I’m not a Typescript expert, so please let me know in the comments if I missed something):
I could make
Hypercarda class component and have the helpers be private members of that class. But
Hypercardwould then be huge. And private methods aren’t (easily) testable.
Or I could create a folder for
Hypercardand its helpers, but export only
Hypercardfrom the folder’s
index.ts. That doesn’t enforce anything, but as a convention it has merits. The folder represents the encapsulation unit, and the
index.tsrepresents its public interface. I could see at a glance if any source file imported from something like
‘../Hypercard/Incoming’so that would be a red flag.
In fact I’ve chosen option 2 and it seems to be working so far. It’s an Explicit solution, but only by convention.
So to recap, my code had two instances of Implicit Coupling: Firstly, the intention that two rendered components should in future evolve together. And secondly, that certain components were internal to the implementation of another and should not be used outside of that context. (How would you express those in the language of connascence? Or in terms of code smells?)
In both cases I’ve tried to find a way for the code to make that intended coupling Explicit, so that it can be read locally, once and only once in the codebase.
Things to try
In your next user story conversation be sure to discuss how the various domain concepts are likely to evolve in relation to each other. Then try to express that intention in your code.
If you have a module like
Hypercard, that consists of more than one encapsulation unit, find a way to encapsulate the whole lot very obviously in your codebase.
Try fixing similar problems in your code and practices today; and please leave a comment sharing your results.