Clean Code: Functions

Functions play a vital role in making our code more clean, easier to understand, and easier to maintain. In this blog post, we will review the function chapter of Clean Code by Robert C. Martin.

Function should be small

The first rule to make a function clean is that it should be small. Functions should not be 100 lines long.

Blocks and Intending

The blocks within if statements, else statements, while statements and so on should be one line long. Probably that line should be a function call.

if() {
  doSomething();
}

Functions should do one thing

This is a golden rule of clean code that a function should do one thing. Consider this code,

function tranformAndSaveToDatabase(user) {}

As name suggests the function tranformAndSaveToDatabase() is doing more than one task. To maintain function should do one thing lets refactor the function,

function makeTransformation() {}

function saveToDatabase() {}

We split the tranformAndSaveToDatabase() function into makeTransformation() and saveToDatabase(), as names suggest they both are doing one task.

Single Level of Abstraction

All statements of a function should belong to the same level of abstraction. The purpose of SLA is to avoid mental grouping within a function. If there is a statement inside a function that belongs to a lower level of abstraction, it should go to a new function that performs a statement inside its level. Consider this,

const updateCartWithPayment = (update_type, cart_index) => {
  carts[cart_index].quantity = update_type === 'sub' ? 
    carts[cart_index].quantity - 1 : 
    carts[cart_index].quantity + 1;

  let total_payment = 0;

  carts.forEach(cart => {
    total_payment += cart.quantity * cart.payment;
  });

  return {
    carts,
    total_payment
  }
}

Inside the updateCartWithPayment() function we have multiple abstractions. We can refactor the function like this,

const updateCart = (update_type, cart_index) => {
  const updated_carts = [...carts];

  updated_carts[cart_index].quantity = update_type === 'sub' ? 
    updated_carts[cart_index].quantity - 1 : 
    updated_carts[cart_index].quantity + 1;

  return updated_carts;
}

const totalPayment = () => {
  let total = 0;

  carts.forEach(cart => {
    total += cart.quantity * cart.payment;
  })

  return total;
}

const handlePayment = (update_type, cart_index) => {
  const updated_carts = updateCart(update_type, cart_index);

  const total_payment = totalPayment();

  return {
    carts: updated_carts,
    total_Payment
  }
}

No each function has one level of abstraction.

Function Arguments

The ideal number of function arguments is zero. One or two arguments are fine but more than that should be avoided.

createUser(fullName, email, password)

We are passing three arguments which should be avoided. If you are using javascript you can use object,

const user = {
  fullName,
  email,
  password
}

createUser(user);

Flag Arguments

Flag arguments or boolean arguments indicate that a function does more than one thing. Consider this following example,

function generateReport(type) {
  if(type === 'customer') {
    // generate customer report
  } else {
    // generate product report
  }
}

Instead of using flag arguments,

function generateCustomerReport() {
  // generate customer report
}

function generateProductReport() {
  // generate product report
}

Split the function into two functions and call them where you need them.

Side Effects

It is advisable to avoid side effects. A side effect refers to any observable change that happens outside of the function that is currently being executed. Keep in mind that sometimes we can’t avoid side effects, so that time it is necessary to use it.

const carts = []
const product = {
  name: "Mac Laptop"
}

const addProductToCart = (carts, product) => {
  carts.push({ product, date: Date.now() });
};

addItemToCart(carts, product);

The addItemToCart() should not change the global variable, in our case carts. It is better to write like this,

const carts = []
const product = {
  name: "Mac Laptop"
}

const addProductToCart = (carts, product) => {
  return [...cart, { product, date: Date.now() }];
};

const updatedCarts = addItemToCart(carts, product);

Now addProductToCart() function is not changing value outside scope.

Don’t repeat yourself

It is a principle of software development aimed at reducing repetition of code. For example,

function generateUserSalary() {
  const salaryWithBonus = user.baseSalary + bonus;
  const totalInYear = 2;
  const totalSalaryInYear = salaryWithBonus * totalInYear;
}

function generateEmployeeSalary() {
  const salaryWithBonus = employee.baseSalary + bonus;
  const totalInYear = 2;
  const totalSalaryInYear = salaryWithBonus * totalInYear;
}

Their is code repetition in both functions, in order to maintain dry principle,

function generateSalary(baseSalary) {
  const salaryWithBonus = baseSalary + bonus;
  const totalInYear = 2;

  return salaryWithBonus * totalInYear;
}

function generateUserSalary() {
  const totalSalaryInYear = generateSalary(user.baseSalary);
}

function generateEmployeeSalary() {
  const totalSalaryInYear = generateSalary(employee.baseSalary);
}

Sometime you do need to re-use code, so you can re-use based on your requirements.

Leave a Comment

Your email address will not be published. Required fields are marked *