Having waffled for a couple of articles I think it’s time for a code example. This one cropped up in my personal side coding project only a few days ago…
I’m currently building a wiki. The key feature of wikis is the links between pages, so in the wiki browser client I have this type:
type ResolvableLink = {
title: string,
url: string,
}
(The code in this example will all be in Typescript. If you don’t know Typescript, think C# and you won’t be far adrift.)
But, as in any wiki, there can also be links to pages that don’t yet exist:
type UnresolvableLink = {
title: string,
}
Now, throughout the client I occasionally need to create lists of links (for example the links visited in this client session), and each list can contain a mix of ResolvableLink
s and UnresolvableLink
s. What should the type of such a list be? Clearly something that is based on an Array:
type ListOfLinks = Array<T>
But what should T be, given that I want to include a mix of two object types?
One common starting point is to observe that the only difference between a ResolvableLink
and an UnresolvableLink
is the presence or absence of that URL. So I could write this:
type ListOfLinks = Array<{
title: string,
url?: string,
}>
By copying the internals of the link types, I have created coupling between them and the list that I can’t see just by reading the definition of ListOfLinks
. Nor can my IDE help me, because there’s no reference to either of the link types.
The type ListOfLinks
will always be coupled to the types ResolvableLink
and UnresolvableLink
, because those are the kinds of data I’ll be putting into — and getting out of — the array. But the type definition above doesn’t give me many clues about what that relationship actually is.
I’m calling this Implicit Coupling: if any of these three types changes in certain ways, only run-time checks will tell me that something has broken. And that means that this code contains surprises that could slow me down when I try to work on it, and could render any developer estimates useless. (I’ve represented it by dashed connections in the diagram.)
Instead of this, I want to have Explicit Coupling between these types. I want to be able to see their dependencies locally, without having to execute the code. So for this example, I want to couple the ListOfLinks
type explicitly to the types it could contain, ie. ResolvableLink
and UnresolvableLink
.
So I want to do this:
type ListOfLinks = Array<ResolvableLink | UnresolvableLink>
Or this:
type Link = Either<UnresolvableLink, ResolvableLink>
type ListOfLinks = Array<Link>
(using the wonderful fp-ts library by Giulio Canti). Both of these alternatives allow me to specify the types of the things I can put into the array. (When it comes to get them out again, well that’s a story of different coupling, for a future article.)
In both of these cases I can see the coupling locally, simply by reading the code here and now. I don’t need to run it, or go spelunking around — I can just see it here. This is what I mean by the term Explicit Coupling.
In diagrams I’ll represent Explicit Coupling with a solid connection:
In this example I will always have some form of coupling (although we’ll probably see examples later in which coupling can be removed). And given that the coupling is necessary, I want to choose Explicit over Implicit: I want to be able to see it locally — and maybe even have my IDE offer help. If I can do that I will have fewer surprises when I make changes. And surprises are bad, because surprises slow me down and add stress to my day.
There’s a lot more to say about the coupling in this little example — in later articles we’ll look at the code that uses these types too. But for now I simply wanted to introduce some vocabulary (as I intend to use it). I believe that Explicit vs Implicit Coupling is a simple idea that can transform the habitability of a codebase. Stay tuned and we’ll explore these ideas together.
I would love to hear about similar examples in your codebase, so if you can share, please:
such union types is also something I really liked when learning elm (https://guide.elm-lang.org/types/custom_types.html). in java, we would model this with an interface and both ResolvableLink and UnresolvableLink implementing it?
Hello Kevin.
Do you think you could elaborate, e.g. with example(s), on what you mean by 'only run-time checks will tell me that something has broken' and 'I want to be able to see their dependencies locally, without having to execute the code.'
I am asking so that I can better understand what the problem is and also whether the reason why 'run-time' is being mentioned has anything to do with weak/strong typing and/or static/dynamic typing.