KiguriCodes

← Back to blog

Published on 2024-04-07 11:12 by Kiguri

Why does clean code matter?

Surely there are several times when you look at a piece of code and think, “What the heck is this?” or “Who wrote this?” or “I can’t understand this.” This is a common scenario in software development. The code is not just for the computer to understand; it is also for humans to read and understand. Clean code is a software development principle that emphasizes writing code that is easy to read, understand, and maintain. It is a key aspect of producing high-quality software that is efficient and bug-free.

Applying clean code principles to your codebase can have several benefits:

How can I access whether my code is clean or not?

Determining the cleanliness of your codebase can be achieved through several measures. Key signs include thorough documentation, uniform formatting, and an orderly structured code repository.

Peer code evaluations serve as a vital checkpoint, spotlighting areas for improvement while ensuring adherence to established coding standards and norms.

Furthermore, the role of testing in maintaining code purity cannot be overstated. It verifies the operational integrity of the code and aids in the early detection of discrepancies.

Adopting a range of tools, methodologies, and standard practices can significantly aid in cultivating a code environment that prioritizes clarity, simplicity, and ease of upkeep.

However, it’s crucial to acknowledge the subjective nature of code cleanliness. Perspectives on what constitutes clean code can vary widely among individuals and projects. What may be perceived as clean and efficient in one context may not hold the same regard in another.

Despite this subjectivity, there exists a core set of principles that, if followed, can lead to the development of cleaner, more manageable code. Let’s explore these principles further.

Meaningful Names

This is the first thing you should consider when writing clean code. Names should reveal intent. If a name requires a comment, then the name does not reveal its intent. For example, consider the following code:

const d = 5; // elapsed time in days

The variable d does not reveal its intent. A better name would be:

const elapsedTimeInDays = 5;

Avoid naming variables with affixes that are already present in the variable’s nature.

const nameString = "John Doe"; // Bad
const name = "John Doe"; // Good

const isFlag = true; // Bad
const flag = true; // Good

const accountList = []; // Bad
const accounts = []; // Good

Also, you should avoid using “Noise Words” in your variable names. Noise words are words that do not add any value to the variable name. For example:

const theAccounts = []; // Bad
const accounts = []; // Good

const userData = {}; // Bad
const userInfo = {}; // Bad
const user = {}; // user is enough because it is a variable, and it is obvious that it is data. // Good

There are several noise words that you should avoid, such as the, data, info, object, value, etc.

Magic Numbers

A “magic number” in programming refers to a hard-coded value that appears in the source code without explanation of its meaning. These numbers are often used to control program behavior, specify sizes, limits, or special values, but they can make the code difficult to understand and maintain. The term “magic” implies a lack of clarity about how or why the number works in the context in which it is used.

Using magic numbers is generally considered poor practice because it decreases the readability and maintainability of code.

Bad

def validate_image_size(image):
  if image.width * image.height > 1000000: # 1000000 is a magic number
    return False
  return True

Good

MAX_IMAGE_SIZE = 1000000
def validate_image_size(image):
  if image.width * image.height > MAX_IMAGE_SIZE:
    return False
  return True

By defining a constant with a meaningful name, you can make the code more readable and easier to maintain. If the value of the constant needs to change, you only need to update it in one place.

Functions

Naming Functions

Naming function is as important as naming variables. Functions is always a verb or a verb phrase. For example, getUser, validateUser, saveUser, etc.

Be consistent with your function names. If you are using getUser for getting a user, then use saveUser for saving a user, not saveUserData or saveUserDetails. Following "one word for each concept" is a good way to name your functions. For example, the action of “getting” a user information can be described by some words like fetch, retrieve, get, find, search, etc. So, you should choose one of these words and use it consistently throughout your codebase.

Bad

function getUser() {
  // ...
}

function retrieveOrder() {
  // ...
}

Good

function getUser() {
  // ...
}

function getOrder() {
  // ...
}

Below is the naming of CRUD actions that I often use in projects:

// Create
function createUser() {
  // ...
}

// Read one
function getUser() {
  // ...
}

// Read many
function listUsers(params) {
  // I don't like to use getAllUsers because the function could come with a filter or pagination
  // So, listUsers is more appropriate
  // ...
}

// Update
function updateUser() {
  // ...
}

// Delete
function deleteUser() {
  // ...
}

Functions Should Do One Thing

In real-world projects, there are cases where functions span several hundred lines, or even thousands of lines. For those new to the project, this can truly be a nightmare as it becomes very difficult to read, understand, and maintain.

So while trying to create a function, you should ensure:

Bad

function fulfillOrder(customer, product) {
  createOrderAndSendEmail(customer, product);
}

function createOrderAndSendEmail(customer, product) {
  // ...
}

Good

function fulfillOrder(customer, product) {
  const order = createOrder(customer, product);
  sendEmail(order);
}

function createOrder(customer, product) {
  // ...
}

function sendEmail(order) {
  // ...
}

Bad

function createOrder(
  customerId,
  customerName,
  customerAddress,
  productId,
  quantity,
  price,
  discount,
  tax,
  shippingCost
) {
  // ...
}

Good

function createOrder(customer, product) {
  const {  id, name  address } = customer;
  const { id, quantity, price, discount, tax, shippingCost } = product;
  // ...
}

Don’t Repeat Yourself (DRY)

Reusability

You shouldn’t write the same code with the same logic or doing the same thing in multiple places. If you need to change the logic, you will have to change it in multiple places, which is error-prone and time-consuming.

To avoid this, you should extract the common code into a function and call that function wherever you need it so that you only need to change the logic in one place.

Bad

function calculateRectangleArea(length, width) {
  return length * width;
}

function calculateCircleArea(radius) {
  return Math.PI * radius * radius;
}

function calculateTriangleArea(base, height) {
  return 0.5 * base * height;
}

let rectArea = calculateRectangleArea(5, 10);
let circleArea = calculateCircleArea(5);
let triangleArea = calculateTriangleArea(5, 10);

Good

function calculateArea(shape, ...args) {
  switch (shape) {
    case "rectangle":
      return calculateRectangleArea(...args);
    case "circle":
      return calculateCircleArea(...args);
    case "triangle":
      return calculateTriangleArea(...args);
    default:
      throw new Error("Invalid shape");
  }
}

let rectArea = calculateArea("rectangle", 5, 10);
let circleArea = calculateArea("circle", 5);
let triangleArea = calculateArea("triangle", 5, 10);

Single Source of Truth

The Single Source of Truth (SSOT) principle is a software development practice that promotes the idea of having a single, definitive source of data or information within a system. This principle is particularly relevant in the context of clean code, as it helps to reduce redundancy, inconsistencies, and errors in your codebase.

By following the SSOT principle, you can ensure that your code is more maintainable, scalable, and reliable. When there is a single source of truth for a particular piece of data or logic, you can be confident that any changes made to that source will be reflected accurately throughout your application.

For example, consider a scenario where you have multiple functions that need to access the same configuration settings. Instead of hardcoding these settings in each function, you can define them in a central configuration file and import them wherever they are needed. This way, if the configuration settings change, you only need to update them in one place, ensuring consistency across your codebase.

Bad

// get.js
let API_URL = "https://api.example.com";
let API_KEY;

function fetchData() {
  return fetch(`${API_URL}?key=${API_KEY}`);
}
// create.js
let API_URL = "https://api.example.com";
let API_KEY;

function createData(data) {
  return fetch(API_URL, {
    method: "POST",
    body: JSON.stringify(data),
    headers: {
      "Content-Type": "application/json",
    },
  });
}

Good

// config.js
export const API_URL = "https://api.example.com";
export const API_KEY = "";
// get.js
import { API_URL, API_KEY } from "./config";

function fetchData() {
  return fetch(`${API_URL}?key=${API_KEY}`);
}
// create.js
import { API_URL } from "./config";

function createData(data) {
  return fetch(API_URL, {
    method: "POST",
    body: JSON.stringify(data),
    headers: {
      "Content-Type": "application/json",
    },
  });
}

Comments

Avoid annotating your code with comments that merely describe what it’s doing. Ideally, the code should stand on its own, clear and comprehensible without additional explanation. Reserve comments for clarifying the reasons behind the code’s operations. If you find the need to use comments to explain what your code does, it might be time to rethink and refactor your code to enhance its readability.

Bad

// This function calculates the area of a rectangle
function calc(a, b) {
  // a is the length of the rectangle
  // b is the width of the rectangle
  return a * b;
}

let area = calc(5, 10); // calculate the area of a rectangle with length 5 and width 10

Good

function calculateRectangleArea(length, width) {
  return length * width;
}

let area = calculateRectangleArea(5, 10);

Commented-out code often clutters project repositories. It can lead to confusion for others debugging or reviewing your code, prompting questions about its purpose. Unnecessary code should be removed to maintain cleanliness. For code you might need in the future, rely on your version control system such as GIT to retrieve it. In cases where the code is needed under specific circumstances, such as development environments, consider implementing a feature flag or configuration file to toggle its availability.

Bad

// Don't need in production
// function debug() {
//   // ...
// }

function main() {
  // debug();
  // ...
}

Good

function debug() {
  // ...
}

function main() {
  if (env === "development") {
    debug();
  }
}

Code Organization and Formatting

Code Organization

Organizing your code is essential for readability and maintainability. A well-organized codebase makes it easier to navigate, understand, and modify the code. On the other hand, a disorganized folder structure can lead to confusion, inefficiency, and difficulty in managing the project. It can result in wasted time searching for files, difficulty understanding the project’s architecture, and increased chances of introducing bugs.

Here are some best practices for organizing your code:

The following is an example of a well-organized folder structure for a React project, which is organized around features. Each feature has its own folder containing components, API calls, and routes. This structure makes it easy to locate and work on specific parts of the project.

├── public
├── src
│ ├── assets
│ │ ├── **/\*.css
│ │ ├── **/\*.png
│ ├── components
│ │ ├── Button
│ │ ├── Card
│ │ ├── Header
│ │ ├── index.ts
│ ├── constants
│ ├── features
│ │ ├── auth
│ │ │ ├── components
│ │ │ ├── api
│ │ │ ├── routes
│ │ │ ├── index.ts
│ │ ├── home
│ │ │ ├── components
│ │ │ ├── api
│ │ │ ├── routes
│ │ │ ├── index.ts
│ ├── lib
│ ├── routes
│ │ │ ├── index.ts
│ │ │ ├── PublicRoutes.tsx
│ │ │ ├── PrivateRoutes.tsx
│ ├── utilities
│ ├── App.tsx
│ ├── index.css
│ ├── main.tsx

Code Formatting

Code Linting and Formatting Tools

Leveraging tools can help automate and enforce consistency in code organization and formatting:

Documentation

Effective documentation is as vital as the code itself. It serves as a guide for future maintainers, including your future self, and facilitates a deeper understanding of the code’s purpose and functionality. Different types of documentation serve various purposes:

Conclusion

Wrapping up our clean code journey in a more cheerful note: Clean code isn’t just a bunch of rules that make our lives as developers harder; it’s the secret sauce that makes our code sparkle and shine! It’s what transforms a chaotic jumble of characters into a masterpiece of efficiency, readability, and sheer brilliance. By hugging the best practices and principles of clean code close, we’re not just coding—we’re crafting art that’s a joy to behold (and debug)!

So, if you’ve stuck with me this far, hats off to you! You’re on your way to becoming a maestro of the clean code symphony. Remember, like any good skill, mastering the art of clean code takes a bit of patience, a dash of practice, and a whole lot of passion. So keep on coding, keep on refining, and let’s make our codebases places of beauty and joy. Here’s to less head-scratching and more high-fiving. Happy coding, you brilliant coding wizards! 🎉👩‍💻🧙‍♂️✨

Written by Kiguri

← Back to blog
  • Why forwardRef needs to be removed from React

    Why forwardRef needs to be removed from React

    ForwardRef is a feature in React that allows you to access the ref of a child component from the parent component. It is a useful feature, but it has some downsides that make it a bad practice to use.

  • Clean Code

    Clean Code

    Clean code is a software development principle that emphasizes writing code that is easy to read, understand, and maintain. It is a key aspect of producing high-quality software that is efficient and bug-free.