I start with a statement without scientific data backing me, so this will be the least mathematical, most subjective and probably one of the most useful clean coding idea:
Most of us understand short, concise and aptly named and labelled code.
If you think about this, you will see that much of Clean Coding is achieving this very idea:
- you want things to be short, so you do not need to juggle a lot of context and details at the same time; you toy with abstractions until you can achieve the most effectives ones
- you want things to be concise and aptly named, so you can trust that a
getUrl
function does not in fact sets up a server if it is not yet running (this oddly specific example is from real life code, so don't argue with me that "Who would do such thing?")
That's why we are grouping and categorizing and recategorizing stuff, that's why you are breaking up that messy folder that has 20 files to smaller ones.
Because you want to lower the cognitive load on your brain, therefore you will be able to do quicker and smarter decisions.
With well isolated modules/functions/components/abstractions you can be quite sure what the consequences of your changes will be.
The opposite is when everything is or can be connected with everything else. That's when you read code for two days to write 3 lines of code.
To best achieve this isolation you are grouping non-obvious and coherent parts of your code into a named
function
/method
/procedure
ormodule
with the name explaining the otherwise obscure inner workings.
The tyranny of templates and tutorials (TTT)
While these two technique (naming and breaking down long passages of code) are very basic and taught very early in clean coding, there are certain conditions when I saw otherwise excellent developers abandoning these two excellent tools.
You see, most of us start writing code based on a template (for instance create-react-app
) or from an existing tutorial. These have to be fairly simple to so people get why things are there and cannot get all encompassing.
But then an aversion to breaking the template kicks in and people start writing 1000s of lines of controllers in app.js
(in case of express.js
) since this was where the template/tutorial instructed them to put their code into.
This is also true for many popular coding strategies like test driven development, functional programming or object oriented programming: you have a problem that does not fit your favourite paradigm and then you force it upon it, regardless how unreadable your code became.
And that is exactly where you can apply these two techniques to force yourself out from the template to a working new categorization.
When reality breaks the template, break the template not the very valid use case at hand.
Templates and paradigms are starting points and not unbreakable contracts or standards.
Examples to open your mind
Using @mixin
of SCSS
to explain CSS
For most developers (myself included) CSS
is scary partly because that not all declarations "do" something unless a bunch of other declarations were also made. These tend to be not intuitive for a lot of cases.
There is a very quiet revolutionary feature in SCSS
(or in LESS
) and that is named @mixin
s. This would allow us to name the obscure and break it down to small chunks.
In this little snippet a couple of statements make sure that the language-input-label
will be the same width
as the input
below it.
.language-input-label {
// neatly sorted alphabetically 😊
flex-grow: 1;
font-size: var(--small-space-1);
padding: var(--spacing-base);
text-transform: uppercase;
width: 100%;
}
Can you spot those? Or could you even guess such feature would exist?
See this example instead:
.language-input-label {
@mixin make-label-equal-width-as-inputs {
width: 100%;
flex-grow: 1;
padding: var(--spacing-base);
}
@include make-label-equal-width-as-inputs;
text-transform: uppercase;
font-size: var(--small-space-1);
}
See how @mixin
is shining, not as a tool to reuse code but to explain:
- your goal with the properties (make label of inputs equal width as the inputs)
- the number of properties that need to work together to achieve the desired effect
So when dev B comes over to refine the component they would understand what needs to change in concerted effort. Neat!
Chaining anonymous functions (e.g. array-iteration or RXjs)
There are certain iteration functions (.map()
, .filter()
, .reduce()
, ...) that we learnt to use with anonymous functions. When those anonymous functions get obscure there is a tendency to leave them as-is and say "well, yes, programming can be hard, functional programming is not for everybody". 🤷🏼
⚠️ You do not need to understand every line here, don't waste your time if something looks magical. It is obscure for the sake of example. ⚠️
// Pseudo code
chatStream
.filter((chat) => {
if (chat.user[0].name !== 'sender') {
return true;
} else {
return false;
}
})
.map((chat) => {
const copiedChat = { ...chat };
// that neat snippet lifted from stackoverflow
let d1 = new Date(new Date().getFullYear(), 3, 0);
d1.setDate(d1.getDate() - d1.getDay());
let d2 = new Date(new Date().getFullYear(), 10, 0);
d2.setDate(d2.getDate() - d2.getDay());
if (chat.timestamp > d1 && chat.timestamp < d2) {
copiedChat.timestamp = new Date();
copiedChat.timestamp.setHours(d.getHours() - 1);
}
// very self-explanatory! 😐
return copiedChat;
})
.reduce((chat) => {/* other things */})
Raise your hand if you would be eager to touch any of this code!
Now let's do the unimaginable abomination and name those anonymous functions (please observe that by defining them outside of the scope those can be exported, reused and/or tested separately!).
function filterSenders(chat: Chat) {
return chat.user[0].name !== 'sender';
}
// now that everyone knows what this is all about
// maybe you can search or a standard solution...
function correctTimeWith1HourDuringSummerTime(chat: Chat) {
const copiedChat = { ...chat };
let d1 = new Date(new Date().getFullYear(), 3, 0);
d1.setDate(d1.getDate() - d1.getDay());
let d2 = new Date(new Date().getFullYear(), 10, 0);
d2.setDate(d2.getDate() - d2.getDay());
if (chat.timestamp > d1 && chat.timestamp < d2) {
copiedChat.timestamp = new Date();
copiedChat.timestamp.setHours(d.getHours() - 1);
}
return copiedChat;
}
// Look how concise and readable it became! ✅
chatStream
.filter(filterSenders)
.map(correctTimeWith1HourDuringSummerTime)
.reduce(createSomeObject)
describe
how it
is the only way in testing
I do not wish to article to be too long, so I will be using quite general terms here.
Another pattern I saw where people merrily copy-paste gigantic amount of code and let a file grow larger than 500+ lines is testing.
The template for testing in jest
for instance looks like this:
// imports
describe('The block under test', () => {
let mock1;
let mock2;
let object1;
let object2;
// ...
beforeEach(() => {
// setup
});
afterEach(() => {
// tear down
});
it('does things', () => {});
it('does other things', () => {});
it('does even more other things', () => {});
// ...
});
When it gets too large why not a) break it down to smaller chunks and b) name them well.
// you can import test helper functions
// you made for your cases!
describe('The block under test', () => {
let mock1;
let mock2;
let object1;
let object2;
// ...
beforeEach(() => {
// why not name these setup steps?
setupCommonMocks();
setupCommonObjects();
});
it('does things', () => {});
it('does other things', () => {});
it('does even more other things', () => {});
// ...
function setupCommonMocks() {
mock1 = jest.spyOn('something');
mock2 = jest.fn();
// ...
}
// Why copy-paste the creation of that
// same complicated initial state?
function createComplicatedInitialState({ flagToDoSomethingDifferently }) {
return {
state1,
state2,
state3: flagToDoSomethingDifferently ? object1 : object2
}
}
});
The takeaway
What I wished to express that templates and tutorials are just scaffolding to start your code with. Remember:
Templates + the way of the tutorial < your good sense to write short, well named code.
Happy cleaning up! 🙂
Top comments (0)