DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,864 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for Clean Code Part 2: Functions
Tanisha Sabherwal
Tanisha Sabherwal

Posted on • Updated on

Clean Code Part 2: Functions

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

Functional Arguments

Functions can be broadly classified into the following types based on the number of arguments:

  1. Niladic(zero)
  2. Monadic(one)
  3. Dyadic(two)
  4. Triadic(three)
  5. 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}`;
}
Enter fullscreen mode Exit fullscreen mode

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}`;
}
Enter fullscreen mode Exit fullscreen mode

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--;
}
Enter fullscreen mode Exit fullscreen mode

Instead, do this:

const addBoxes = (numberOfBoxes) => numberOfBoxes + 1;
const removeBoxes = (numberOfBoxes) => numberOfBoxes - 1;
Enter fullscreen mode Exit fullscreen mode

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 (4)

Collapse
 
tanisha03 profile image
Tanisha Sabherwal Author • Edited on
function getFormattedDate(){
  var today = new Date();
  var dd = String(today.getDate()).padStart(2, '0');
  var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
  var yyyy = today.getFullYear();

  today = mm + '/' + dd + '/' + yyyy;
  return today;
}
Enter fullscreen mode Exit fullscreen mode
 
tanisha03 profile image
Tanisha Sabherwal Author

Got it! Nice catch.

🌚 Life is too short to browse without dark mode