Declare what you need, not what you get
Editor's note: This post was originally published in July 2019. Since then, Atomist has evolved and updated its platform and product offerings. For up-to-date information, check out the Atomist product page.
TL;DR: the Interface Segregation is particularly smooth in TypeScript. Use it.
When I write a function, I want to declare its parameter types to be the minimum of what the function needs. It may be easier to use the same type as what gets passed in, because then I don't need to declare a new type. But sometimes it's worth the extra work.
When I make a UI component, I like it to declare exactly what it is going to use in the display. So I create a custom, local type (for its React props, in this case) that declares exactly the fields it uses, and nothing else. For example:
export interface AspectForDisplay {
name: string;
displayName: string;
documentationUrl?: string;
}
Now, it happens that the type that gets passed in is a ManagedAspect
, which extends BaseAspect
, which has these and other props. It would be easier (less typing) to declare that as the prop passed in. But it would be less specific. Fortunately I'm in TypeScript
TypeScript does duck typing, so as long as whatever's passed in has the properties that I declare (and they have the correct types), it's fine. So I pass a ManagedAspect
into a function that requests an AspectForDisplay
. That helps a lot; in a language like Java or Scala I'd have to go back and make ManagedAspect
implement AspectForDisplay
explicitly, and no way am I letting the UI intrude into the analysis code like that.
Even better (as Rod points out), TypeScript offers another solution. Instead of declaring another interface, I can express this type in terms of the one passed in.
type AspectForDisplay = Pick<BaseAspect, "documentationUrl" | "name" | "displayName">;
This says, "Take the properties out of type BaseAspect
. Find the ones called either documentationUrl
or name
or displayName
. Put them in this new type." This gives AspectForDisplay
the three properties I want, with the same types, optional-ness, and readonly-ness they have in BaseAspect
.
I love this syntax because: it tells me that these properties have the same meanings they have in BaseAspect
. Even though they're all string
s, the documentationUrl
is a specific kind of string (a URL, yeah); the displayName
is an ordinary string, since it's for display; and the name
has technical meaning (it is a foreign key, effectively).
Being specific about what my function needs has some advantages:
- it's informative. By looking at the parameters, people can understand what information the function accesses.
- it's way more testable. I can call it without declaring all the irrelevant fields in
BaseAspect
just to pacify the compiler. - when someone adds a field to
BaseAspect
, it won't change the API to this function. They won't have to change tests of my function.
It has some negatives:
- more work, initially. I have to type push more buttons on the keyboard.
- when I want to use an additional field, it takes an extra change. That can be a breaking change if this function is a public API and that field is required in
BaseAspect
.
There are places where these negatives are sufficient to deter me. Local functions that I extracted in order to give them a name, it is rarely worth making a new type.
But along conceptual boundaries, like between the result of data analysis and the UI, this is particularly valuable. In any code I want to re-use, this is valuable. In any code I want to unit test, yes please.
For interviewing purposes, this is called the Interface Segregation Principle, and it's the I in "SOLID," from back in the day when Object Oriented programming was king. This principle works for all statically-typed programming, especially functional programming. It's goes with Postel's Law: be liberal in what you accept.
Be specific about what you need, not what you get.
Editor's note: This post was originally published in July 2019. Since then, Atomist has evolved and updated its platform and product offerings. For up-to-date information, check out the Atomist product page.