This is part 2 of the series of Clean Code By Robert C. Martin. If you haven't gone through the part 1, read here.
Smaller Functions
The first rule for functions is that they should be smaller and easily readable by anyone. It should have the least number of lines, each being transparently obvious. Also, avoid complex nested structures and many indent levels.
Functions should do one thing. They should do it well. They should do it only.
To ensure that the functions are doing one thing only, it is essential that all the lines inside the function belong to one level of abstraction.
Abstraction is a fundamental concept in OOPS. In brief and layman’s term and it talks about hiding the “how” part and only expose “what” to the outer world. Level of abstraction is being able to mentally grouping inside the function. In general, different “blocks” of code inside a method is a classic indicator of different level of abstractions. This means, the reader of the code now has to create a “branch” in their mental grouping to read that condition or loop block and merge back to the same level where the block ended.
Let me explain with an example.
const checkForValidOrder = (order, customer) => {
let tax=15;
return
order.status==='PLACED' &&
order.payment==='SUCCESSFUL' &&
order.price(1 + (tax/100)) <= customer.walletBalance
}
For the above function, there are two levels of abstraction since mathematical computation is being carried put in the last statement. This can be avoided using a small tweak.
const checkForValidOrder = (order, customer) => {
return
order.status==='PLACED' &&
order.payment==='SUCCESSFUL' &&
generateTotalAmount(order.price, tax) <= customer.walletBalance
}
Hence, using abstraction, we are essentially hiding the “how” part and preserving the part indicating what the function does. This was a simple example with only one level of abstraction, when function tends to become bigger with many levels of abstraction, the Single Level of Abstraction (SLA) principle should be followed.
Follow the Stepdown Rule
The rule suggests that the code should be readable in a top-down narrative, descending one level of abstraction per function. It implies that the function ordering is no longer random. A caller function should always reside above the callee function.
Taking the previous example, the ordering of the functions is best as given below and not the other way around.
const checkForValidOrder = (order, customer) => {
return
order.status==='PLACED' &&
order.payment==='SUCCESSFUL' &&
generateTotalAmount(order.price) <= customer.walletBalance
}
const generateTotalAmount = (price) => {
let tax=15;
return price(1 + (tax/100));
}
Functional Arguments
Functions can be broadly classified into the following types based on the number of arguments:
- Niladic(zero)
- Monadic(one)
- Dyadic(two)
- Triadic(three)
- Polyadic(more than three)
As the number of arguments for a function increases, complexity increases and it even becomes tougher from a testing point of view.
When functions need three or more arguments, most of the times, it is easier to wrap some of these arguments into a class/object of its own. This will ensure better readability.
const describeFruit = (color, name, size, price, numSeeds, type) => {
return `${fruitName} is ${fruitColor}. It's ${fruitSize}.
It costs ${price}. It has ${numSeeds}. The type if
${type}`;
}
The above function is too confusing to read with so many arguments. A better alternative and cleaner versionis given below.
const describeFruit = (fruit) => {
return `${fruit.name} is ${fruit.color}. It's
${fruit.size}. It costs ${fruit.price}. It has
${fruit.numSeeds}. The type if ${fruit.type}`;
}
No Side Effects
Side effects are code in a function that makes changes to things that are outside the function. This can lead to unexpected changes to code outside and cause damaging mistruths.
For example, the code snippet given below should be avoid.
let numberOfBoxes = 1;
const addBoxes = () => {
numberOfBoxes++;
}
const removeBoxes = () => {
numberOfBoxes--;
}
Instead, do this:
const addBoxes = (numberOfBoxes) => numberOfBoxes + 1;
const removeBoxes = (numberOfBoxes) => numberOfBoxes - 1;
Ending Notes
Don't repeat yourself. They are functions for a reason.
Functions are the verbs of the code and do task to avoid any duplication. When structured correctly, functions will be shorter, well named and much readable. Hence, cleaner code.
Head over to Part 3 of the series.
Top comments (2)
Got it! Nice catch.