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 ofIncoming
,Search
,MissingPages
,Orphans
,RecentChanges
and a few others.Incoming
is 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 Hypercard
:
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 PageMetadata
:
The problem
We can see that Hypercard
and PageMetadata
share a responsibility and at least one collaborator.
Here’s the current source code of the PageMetadata
component:
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 Hypercard
.
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 Hypercard
and 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.
Solution options
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 Hypercard
and 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
PageMetadata
andHypercard
with the tools to be used for displaying incoming links.Have one thing use the other — in this case,
PageMetadata
could useHypercard
.
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?
Root causes
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 Hypercard
.
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.
Prevention
So 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
Hypercard
a class component and have the helpers be private members of that class. ButHypercard
would then be huge. And private methods aren’t (easily) testable.Or I could create a folder for
Hypercard
and its helpers, but export onlyHypercard
from the folder’sindex.ts
. That doesn’t enforce anything, but as a convention it has merits. The folder represents the encapsulation unit, and theindex.ts
represents 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.
Recapping
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.