The "Simple, elegant type system is all you need" bias
András Tóth
Posted on February 19, 2022
This Spaghetti code article will be on the soft side as in soft-skills as I am going to talk about a very frequent and very typically engineering bias that leads to spaghetti code. This article is about a human bias, it is not a JS article nor React
though all my examples will be in this language or in a pseudo-web framework.
"Do not disturb my circles!"
Originally from Latin ("Nōlī turbāre circulōs meōs!") it is said to be a quote from Archimedes, almost his final words, shouting at an invading Roman soldier while he was minding his geometrical studies in the sand.
The "Do not disturb my circles!" bias is when we assume we can provide an elegant type system of which can cover all use cases. When the real use cases show up, instead of adopting or changing the system completely, we push back, question or hack these new requirements into the no-longer beautiful type system.
Maybe this bias has a better name, however I am not literate enough in the psychology of biases to know it, so hereby I will name it like this.
Examples
Let's say the engineering team is presented a handful of design slides. Every slide is covering some problems the users face: there is a title
, a description
and an action
to take in form of a button.
Engineer A will now propose that "Uh um, very easy! All I need is this simple structure:"
const userSolution = {
title: 'Some title',
description: 'This is where the description will be.',
action() {
// TODO
},
actionText: 'Press the button'
};
They then proceed and create an entire system based on the assumption that in fact this is the type at heart. Multiple layers of tests are written. The layout is set in stone:
<slide-title>{title}</slide-title>
<description>{description}</description>
<button on-click={action}>{actionText}</button>
Weeks later the UX report comes back:
"We tested the prototypes and a couple of changes will be needed:"
- One slide will have an alternative
action
- One slide will not have a
description
ortitle
but an entirevideo
instead and a link below - One slide will have a clickable icon instead of the button, no text
- An there will be a "rich slide", where an interactive custom built "widget" will be placed between the
title
and thedescription
The reaction
I have seen many reactions to this scenario, most of which is toxic to the codebase or to the product:
- Denial: "Is this really what people need? They will get confused because the system is so varied now!" The more confrontative devs will do it, with the occasional deep sighs, that they are forced to ruin their code with this mess.
-
Condition-mania: in which every possible property is added as an
optional
value. The entire codebase is now a mess ofif-else
anddescription && <description>...</description>
blocks, it is hard to see what the end result will look like -
Chicken typing 🐥: it's like 🦆 duck typing just worse: the duck typing is based on flimsy guesses, existing properties are reused with totally different meaning, say if the title has the word
video
in it, then it must be thevideo
slide:if (title.contains('video') { slide.description = <embed video={slide.decription} />
- Math cowboy: finds the greatest common divisor of all types and actions and runs with it. Looks smart at first but totally obfuscates any system. (See below).
Sometimes all 3 will appear, so there is a product compromise like a "Description" header remaining on the page even though there is now clearly a video being embedded there. The code is littered with ugly conditional-fixes and superfluous guesses what to do based on a moving target.
The math-cowboys
Let's see an example:
// OK, so now my structure can look like anything
// then use Map
const textSlide = new Map();
textSlide.set('title', 'This is smart slide');
textSlide.set('description', 'This is smart description');
textSlide.set('action', () => {});
textSlide.set('actionText', 'Press the button');
Looks smart, but it is extremely hard to use: for every property
now you have to test whether it exists. You will never be sure how many different slides exist since the real world handful cases are now replaced by infinite possibilities. The system now must be carefully analysed before anything is changed.
And why? The math-cowboy
did not want to get bothered adjusting their system later.
Fun fact: I knew a guy who ignored the class
system of Java
and used Map<String, Object> map = new HashMap<String, Object>();
to cover every case.
Polymorphism? Ha! That is so constraining. Let lesser people work instead.
One possible solution
Generally I think it is a good stance to write simple and easy to refactor code when the user needs are not properly understood yet. No need to write future-proof, but instead something straightforward and easy to change.
That way when change comes you will be OK with the idea to redo your system grounds up maybe.
In the concretion with the case above my battle tested solution is to anticipate early the polymorphic nature if real world cases with a trustable duck typing system in place.
I have written about this in my Toxic optionals article, but here's a very short refresher if you don't want to click.
First iteration
enum SlideTypes {
Text,
}
type TextSlide = {
type: SlideTypes.Text;
title: string;
description: string;
action: {
text: string;
effect: () => {};
}
};
Second iteration
enum SlideTypes {
Text,
Video,
AlternativeAction,
RichSlide,
}
type TextSlide = {
type: SlideTypes.Text;
title: string;
description: string;
action: {
text: string;
effect: () => {};
}
};
type VideoSlide = {
type: SlideTypes.Video;
videoUrl: string;
action: {
text: string;
effect: () => {};
}
};
type AlternativeAction = {
type: SlideTypes.Text;
title: string;
description: string;
mainAction: {
text: string;
effect: () => {};
};
alternativeAction: {
text: string;
effect: () => {};
};
}
// ...
All these slides can now get a sub-component where there is no conditional magic
and are short and super easy to read.
And later on the page when you need to output the specific slides you just do a good ol' switch-case
(I know it's very old-school):
switch (slide.type) {
case SlidesType.Text:
return <text-slide data={slide} />;
case SlidesType.Video:
return <video-slide url={slide.videoUrl} action={slide.action} />;
// ...
}
Posted on February 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.