Clean code principles in JavaScript

Anton Ioffe - August 30th 2023 - 19 minutes read

Introduction to Clean Code

Understanding Clean Code in JavaScript and TypeScript

The journey into JavaScript and TypeScript surrounds developers with the art of crafting structures that are user-friendly, effortlessly navigable, and easy to debug. This art of creating such convenient structures is known as Clean Code. Clean Code is the practice that makes our code easy to read and understand by others and by ourselves when we reference it in future.

Owing to its loosely-typed, agile nature, JavaScript has gained immense popularity. This nature bestows it with flexibility and leniency. However, this inherent nature also imposes a responsibility on developers to write clear, structured, and optimized code, thus reducing the likelihood of future complications arising from ambiguity or errors.

Incorporating JavaScript and TypeScript in websites, given their accessibility by visitors, calls for elevated meticulousness. The practice of clean code simplifies the tasks for any fellow developer who might need to examine or leverage the code you've crafted.

Clean Code also encompasses the role of comments. Amidst the fervent debates among developers about the importance of comments, their role in enhancing code comprehensibility and readability can't be overlooked. Remember, comments should illuminate why a certain operation happens as it does, rather than what the operation is doing. Here's an effective way to use JavaScript comments:

// Adjusts 'totalPrice' to account for changes in 'itemPrice' or 'itemQuantity'
const updateTotalPrice = (itemPrice, itemQuantity) => {
    let total = itemPrice * itemQuantity;
    return total;
}

Clean Code isn't about successfully weaving the shortest or the most adroit lines of code. It's about making the code readable, accessible, and comprehensible, even if it entails a few more lines for precision.

Misconceptions About Clean Code

When forging code in JavaScript and TypeScript, being aware of certain pitfalls is critical. These can include using == instead of ===, or bypassing local variable declaration. Here's an example highlighting a common mistake:

// Returns false as '===' ensures type equality
console.log(0 === false); // false

// The coercion might lead to unexpected results
console.log(0 == false); // true

Adherence to clean code practices when working with JavaScript and TypeScript is fundamental to making your code operational, efficient, and decipherable. The subsequent sections will delve into the profound principles of clean code, beyond just maintaining the aesthetics of the code and managing comments effectively.

The Principles of Clean Code

When it comes to software development, writing clean, efficient, and maintainable code is an art that every developer aspires to master. Here, we shall lay emphasis on JavaScript and TypeScript, apparently because of the significance they hold in the realm of web development.

What is Clean Code?

Clean Code is all about recognizing that your audience isn’t just a computer, but real human beings who will read, refactor, and maintain your code. The philosophy at the core of writing clean code is that it should be easy to understand and modify for developers. The design should be well-thought-out, and each line of code should be written in a way that communicates the intent and the operation.

Primary Traits of Clean Code

  1. Readability: Just like a well-written book, code should be easy to read. In an ideal world, anyone should be able to understand the flow, functionality, and logic just by reading your code.

  2. Maintainability: Clean code should be easy to understand, thus making it easy to extend and maintain. The faster we can understand what the code is doing, the quicker we can make changes.

  3. Minimalistic: Good code is concise. Extra lines or complex structures can make code hard to digest and maintain. The best code is when there's no more code to remove, without affecting functionality.

  4. Predictable: Clean code does what you expect it to. Side effects in a function can lead to hours of debugging and frustration. Avoiding global states and giving each function a single responsibility can make code predictable.

Key Principles for Clean Code

There exist multiple principles for writing clean code. Here, we focus on the most fundamental ones strictly relevant to JavaScript and TypeScript.

  • Single Responsibility Principle(SRP): A function or module should have one well-defined task.
// Good
function calculateArea(radius) {
    return Math.PI * radius * radius;
}

// Instead of
// Bad
function calculateAreaAndCircumference(radius) {
    var area = Math.PI * radius * radius;
    var circumference = 2 * Math.PI * radius;
    // A function should only do one thing
    return [area, circumference]; 
}
  • Open/Closed Principle(OCP): Code entities should be open for extension but closed for modification. In JavaScript, this is less strict due to the dynamic nature of the language, but it still guides good practices.
// Good
class Circle {
    constructor(radius) {
        this.radius = radius;
    }

    area() {
        return Math.PI * Math.pow(this.radius, 2);
    }
}

// Bad
class Circle {
    constructor(radius) {
        this.radius = radius;
        this.area = Math.PI * Math.pow(this.radius, 2); 
        // This is calculated at construction and can't be changed 
    }
}
  • Yagni Principle: Yagni stands for 'You Aren't Gonna Need It'. This principle encourages developers to write code only when it's needed.

  • KISS Principle: KISS stands for 'Keep It Simple, Stupid'. This principle encourages simplicity in your code.

  • DRY Principle: DRY stands for 'Don't Repeat Yourself'. It's always preferable to reuse a piece of code than to duplicate it.

// Good
function greet(name) {
    return `Hello, ${name}`;
}

greet('Alice');
greet('Bob');

// Bad
console.log('Hello, Alice');
console.log('Hello, Bob');
// Repeating the same code

Common Missteps

Some common mistakes made when applying these principles are:

  • Over-refactoring: While refactoring is an important part of clean code, overdoing it can lead to over-engineered and hard-to-understand solutions.

  • Misinterpreting DRY: Do not abstract away code just for the sake of it. Sometimes repeating slightly different behavior is more readable than an abstract and universal solution.

  • Trying to apply principles rigidly: The principles are meant to guide, but there are exceptions to every rule. Balancing their application is a difficult skill, that comes with time and experience.

In conclusion, writing clean code is a constant journey. The key is to remember that your code will be read by other humans (including future you). These principles provide a solid foundation, but efficient application comes with coding wisdom, practice, and patience. Happy journey towards cleaner code!

Importance of Clean Code

Benefits of Clean Code

Maintaining a clean codebase is akin to running a well-oiled machine. A well-kept codebase improves overall application efficiency, reduces technical debt, and facilitates collaborative development. However, keeping code clean is no easy task, and it carries even more weight when working within a team.

One of the major benefits of clean code is readability. This is crucial when multiple developers are working on the same project. Everyone has a slightly different coding style; thus, a defined standard of clean code can bring uniformity and predictability across a shared codebase. Without it, developers might spend fruitless hours trying to decipher what a piece of code does, essentially wasting productive time.

Impact of Ignoring Clean Code

Ignoring clean code principles results in poor quality code, which is hard to read and understand. This can drastically slow down the future development or maintenance of the project. Even small errors can eventually grow into major obstacles, increasing frustration, stress, and burnout among developer teams.

Example of Clear vs Unclear Code

Let's consider a simple example:

// Unclean code
function d(c){
  let p = 0;
  for (let i = 0; i < c.length; i++) {
    if (c[i].age > 18) {
      p++;
    }
  }
  return p;
}

Rewritten with clean code principles, the function transforms to:

// Clean code
function countAdults(customers){
  let adultCount = 0;
  // Loop through the array of customers
  for (let i = 0; i < customers.length; i++) {
    // Check if the customer's age is above 18
    if (customers[i].age > 18) {
      adultCount++;
    }
  }
  return adultCount;
}

The clean code version is far easier to understand and to work with, for any developer that interacts with it, even if they didn't write or previously work on this part of the codebase.

Conclusion

The deeper we delve into the world of development, the clearer the value of clean coding becomes. Its contribution to individual growth and successful team projects is substantial. Create code that your future self, and others, will appreciate. Embrace the principles of clean code today - your team, your application, and your sanity will thank you later.

Clean Code Writing Techniques

  1. Practical Steps to Clean Code Writing:

First and foremost, let's dig into the practical steps that can guide you in writing clean code. Using JavaScript and TypeScript as our examples:

i. Write Meaningful Names: Give your variables and functions names that describe what they do. This makes your code more readable and easier to understand. For example:

    //Bad
    let d;

    //Good
    let elapsedTimeInDays;

ii. Use single responsibility principle: Each function should do one thing and do it well.

   //Bad
   function parseDataAndWriteToFile(data) { ... }

   //Good
   function parseData(data) { ... }
   function writeToFile(parsedData) { ... }

iii. Avoid Long Functions: Functions that are too long are hard to read and understand. Try to keep your functions as short as possible.

iv. Don't use Flag as a Parameter: If your function needs a flag to control its behavior, it probably should be split into two separate functions.

v. Avoid too much Nesting: Much nesting can make your code hard to read and follow.

  1. Techniques to Identify Whether the Code is Clean:

The beauty of clean code is that anyone can recognize it almost immediately. A clean code adheres to the following principles:

i. Readability: It should be easy to read and understand.

ii. Maintainability: It should be easy to modify without breaking other parts of the software.

iii. Consistency: Everything should follow the same style and pattern.

  1. Conditions to avoid in Clean Code:

As we work towards clean code, certain conditions may not be the best in our journey:

i. Avoiding Duplication: Duplicated code increases the likelihood of bugs. Each change needs to be done in multiple places.

ii. Avoiding Rainbow Code: Overly colorful code with a lot of different concepts can distract and disorient. Aim for a simple, consistent color scheme.

  1. Frequent Mistakes When Applying Writing Techniques:

Here are common pitfalls you might encounter while trying to write clean code:

i. Over-Refactoring: Refactoring is good but overdoing it can lead to overly abstract and complex code.

ii. Obsession with Short Code: Shorter isn't always better. Sometimes, shorter code is harder to understand.

  1. Concepts to Avoid

There are also certain concepts you need to avoid for clean code in JavaScript and TypeScript:

i. Avoid default exports: They make it hard to figure out what’s exported and makes refactoring more tricky.

ii. Immutable Programming: Favor immutability as mutable state is harder to reason about.

Considering these guidelines will set you on the path to writing cleaner, more readable, and more maintainable code. Remember, the ultimate goal is to make everyone's life a bit easier when they have to read and understand your code.

Clean Code in JavaScript and TypeScript

Writing clean, maintainable JavaScript and TypeScript should be every developer's priority. Aiming for this not only achieves better code quality, but also aids in understanding, debugging, and enhancement of the code.

Good Structure for Clean Code

A good structure is vital for clean code. Here are some guidelines to consider:

  • Function Length: Restrict functions to 20 lines max for readability and comprehension.
// Good
function add(a, b) {
    return a + b;
}
  • Function Names: Utilize function names that are descriptive and precise to convey the purpose of the function.
// Good
function getCustomerById(id: number){
    // implementation details
}
  • Argument Number: Limit function arguments to two or less for easier testing and readability.
// Good
function concatenate(firstName, lastName){
    return firstName + ' ' + lastName;
}
  • Variable Names: Ensure variable names are meaningful and pronounceable. Avoid acronyms or abbreviations unless universally recognized.
const cashier: Cashier = new Cashier();
  • Naming Boolean Variables: Begin Boolean variables with is, has, or did.
let isValid = false;
let hasAccess = true;

Common Errors

Errors are bound to occur in coding. Here are some frequent mistakes in JavaScript and TypeScript:

  • Incorrect Scoping: Be mindful of JavaScript and TypeScript's scoping rules. Prefer const or let over var since they are block scoped, unlike var which is function scoped.
// Incorrect Scoping
var x = 1;
if (true) {
    var x = 2; 
    console.log(x);  // yields 2, which is not expected 
}
  • Duplicated Code: Avoid duplicating code as it violates the DRY principle. Instead, use helper functions or modules to write re-usable code.
// Duplicated Code
function calculate(input){
    // some complex calculation
    return result;
}

function calculateResult(input){
    return calculate(input);
}
  • Ignoring TypeScript Compiler Errors/Warnings: TypeScript compiler errors and warnings should not be ignored. They are designed to uphold code quality and correctness.
// Ignoring compiler error
const data: number = 'not a number'; // Will throw a compile-time error

Writing error-free code not only enhances your software application but also makes it more comprehensible to you and your colleagues. Start writing well-structured code today and see the positive impact it will have on your software engineering journey.

Testing and Maintaining Clean Code

First and foremost, clean code is not just about crafting code without indentation errors or semantic errors; it's, in fact, a higher skill level. The essence of clean code is about making it straightforward to understand, modify, and test, regardless of the complexity of the application. Clean code advocates for minimalism and simplicity. In turn, this healthy code ecosystem can greatly improve productivity and significantly impact the overall development timeline.

To maintain such quality code, several principles are emphasized:

1. Comprehensibility: Write code in such a way that any developer, irrespective of their project knowledge level, can interpret and interact with the codebase without much difficulty.

2. Modularity: Organize code neatly and divide it into manageable modules or components for better understandability, easy maintenance, and scalability.

3. Robustness: Ensure the written code avoids any unanticipated behavior. Codes should handle errors gracefully in case of undesired inputs or actions.

Ensuring adherence to these principles requires the employment of a number of testing techniques:

Unit Testing: This involves testing the smallest part of an application to verify its correctness. For JavaScript or TypeScript, frameworks such as Jest, Mocha, or Jasmine can be used. For instance:

// Here we define a simple function
function add(a, b){
    return a + b;
}

// And then we write the test for it
test('adds 1 + 2 to equal 3', () => {
    expect(add(1, 2)).toBe(3);
});

Integration Testing: This testing approach ensures that different modules or services within an application can successfully interact and operate.

Functional Testing: This method verifies that the software can accomplish the desired tasks and functions as expected.

One of the common mistakes during testing and code maintenance is insufficient testing. No matter the size or seemingly insignificance, every function should be tested.

Lack of Comments: Another common mistake is neglecting to use comments. Comments provide context and explanation for complex parts of the code, hence they are especially crucial in these areas. For instance:

// Here we iterate through all active users in the last hour
for(let i = 0; i < activeUsers.length; i++) {
  checkLastActivity(activeUsers[i]);
}

While the Clean Code Architecture or The Ports and Adapters pattern doesn't fall under the purview of this section, it's worth a brief mention. This technique involves organizing the software into layers with distinct responsibilities and will be covered in detail in Section 7.

In summary, writing and maintaining clean code requires time, patience, and substantial practice. Despite demanding an initial investment, the returns can prove rewarding as it dramatically reduces complexity and improves code quality. Avoid the need for haste, and focus on organizing and maintaining your code to ensure it is clean, readable, and of top quality.

One important thing to keep in mind is the role and acceptable length of functions. Keeping functions short and focused on a single task helps keep code clean and maintainable. A good rule of thumb is that if you can't describe what the function does without using the word "and," it probably does too much and should be broken down further.

Clean Code Architecture

Understanding the concept of 'Clean Code Architecture' and its relevance in modern web development is significant for any seasoned developer intent on improving code comprehensibility and maintainability. The Clean Code Architecture, also known as The Onion Architecture or Ports and Adapters pattern, was introduced by Robert C. Martin, widely recognized in the software community as Uncle Bob. This pattern provides a robust structure that helps create software that is both flexible and scalable.

Clean Code as a Design Pattern

In essence, Clean Code Architecture (CCA) is a design pattern aiming to compose software in such a way that it remains decoupled from the frameworks on which it rests, its database, and even the UI. One of the main advantages of applying this design pattern is that the business logic, which represents the rules for how data can be created, stored, and modified, does not depend on anything else.

Consider a scenario where we are building an e-commerce application. Our application's core business logic, shopping cart management, would rest in the center of our Onion Architecture. This core code wouldn't be directly dependent on whether our application uses Angular or React for the UI, MongoDB or MySQL for the database, or even if it's being run on a smartphone or a web browser. Our various UIs would simply adapt or plug into our core business logic.

// CartItem interface
interface CartItem {
    id: string;
    price: number;
    quantity: number;
}

// Core business logic
class ShoppingCart {
    private cartItems: CartItem[];

    constructor() {
        this.cartItems = [];
    }

    addItem(item: CartItem) {
        this.cartItems.push(item);
    }

    removeItem(itemId: string) {
        this.cartItems = this.cartItems.filter(item => item.id !== itemId);
    }

    // ... more business logic here
}

Layers in CCA

CCA involves several layers centered around Entities, which are the most central part of the architecture. These Entities encapsulate critical business rules. Depending on the complexity of the software, there might be more areas in your structure, but generally, it includes Entities, Use Cases, Interface Adapters, and Frameworks & Drivers.

A user in a software application can be an example of an Entity. The use case might be creating or deleting a user. The Interface Adapters layer would keep track of how these operations interact with the rest of the system - such as the database layer. Frameworks & Drivers are the tools and libraries that are necessary to run the application, such as a database management system like MongoDB or a user interface library like React.

The outer layers only communicate with the adjacent inner layer and depend on the inner layers but not vice versa. For example, a change in the UI logic should not affect the database operations and vice versa. This results in a loosely coupled, high cohesion software design which is easier to maintain and evolves over time.

Examples of CCA

Here is a simple example of how the dependency rule works using JavaScript:

// Entity layer
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
}

// Use Case layer
class UserManager {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  addUser(name, email) {
    const user = new User(name, email);
    this.userRepository.save(user);
  }
}

In the above example, userManager (from Use Case layer) depends on User (from Entity layer), but User doesn't depend on userManager.

Frequent Mistakes in Understanding and Applying CCA

Despite the benefits of CCA, developers often make common mistakes when learning and applying it. Some tend to overuse it without considering whether or not it's necessary for the project. Not all projects require such extensive decoupling - if your project is a simple website with a few pages, but it involves complex operations or may require future expansion, using CCA could be beneficial. On the other hand, it could add unnecessary complexity if the application does very basic operations and isn't expected to scale.

Another common mistake is not fully understanding the dependency rule such as reaching through the layers, forgetting that the dependency should only be towards the inward layers. Here is an example of what it could look like:

// Incorrect: reaching through the layers
class UserManager {
    addUser(name, email, userRepository) {
        const user = new User(name, email);
        userRepository.save(user); // Incorrect
    }
}

// Correct: Only depend on the inward layers
class UserManager {
    constructor(userRepository) {
        this.userRepository = userRepository;
    }
    addUser(name, email) {
        const user = new User(name, email);
        this.userRepository.save(user); // Correct
    }
}

Avoid Basic Coding Standards

It is critical to differentiate the concept of CCA from basic coding standards. The latter is just about naming conventions, code structure, styles, and other aspects related to the writing of code. CCA, on the other hand, is a design principle that defines how different parts of your application should interact. It focuses on the 'big picture' and leads to cleaner and loosely coupled software.

In conclusion, CCA is an essential practice for developers aiming to write easy-to-understand code and maintainable software. It has some learning curve attached to it, and in some cases, it might be a bit of overkill, but when applied wisely, it can result in software that withstands the passage of time and the shifts in technological trends.

Clean Code for Beginners

Clean Code Basics

When you start your journey in the world of software development, writing 'clean code' proves pivotal. 'Clean code' is easily understandable, modifiable, and maintainable, thereby being the key to sustainable software and efficient debugging.

In this section, we will embark on the basic coding standards contributing to clean code. We will also delve into the common problems beginners face and illustrate the standards with practical examples.

Understandability

Your code should be comprehensible for other developers, not merely yourself. One way to ensure this is through self-descriptive variable and function names.

Consider the example below:

let x = 3.14; // Unclear
let piValue = 3.14; // Clear

Here, piValue clearly signifies its purpose, unlike x making it a better choice.

Simplicity

Simple codes are convenient to debug and comprehend. Achieve such simplicity by breaking daunting operations into smaller, digestible functions.

Look at an example below:

// Initial complex function
function calculate_area_and_circumference(diameter) {
  let radius = diameter / 2;
  let area = Math.PI * Math.pow(radius, 2);
  let circumference = 2 * Math.PI * radius;
  return [area, circumference];
}

// Simplified alternative
function getRadius(diameter) {
  return diameter / 2;
}

function getArea(radius) {
  return Math.PI * Math.pow(radius, 2);
}

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

// Using simplified functions
function calculateAreaAndCircumferenceSimplified(diameter) {
  let radius = getRadius(diameter);
  let area = getArea(radius);
  let circumference = getCircumference(radius);

  return [area, circumference];
}

Consistency

Prominent parts of consistency in coding style involve case style for variables, semicolon usage, and uniform spacing around operators.

Variable Casing

In JavaScript, preferring camelCase proves common, as shown below:

let firstName = 'John';
const lastName = 'Doe';
let age = 23;

Semicolon Usage

Using semicolons at the end of each statement averts potential errors and enhances the clarity of your code.

let firstName = 'John';
const lastName = 'Doe';
let age = 23;

Operator Spacing

Ensuring consistent spacing around operators can improve the readability of your code significantly.

let sum = 1 + 2;

Effective Commenting

While comments are integral to your code, their overuse or misuse can disrupt the ease of readability. Use comments to clarify intricate parts of your code or mark sections but avoid stating the obvious.

Consider the examples below:

// Bad commenting
let x = 10; // assigning 10 to x

// Good commenting
// Calculate area of circle with given radius
function calculateArea(radius) {
  return Math.PI * Math.pow(radius, 2);
}

Common Barriers and Solutions

As a novice, you may face hardships when attempting to write clean code. Let’s discuss solutions alongside examples to ease you into the process:

Lack of Problem Understanding

Make sure that you have a deep understanding of the code itself before you start developing it. On failing this, you may encounter issues with coding itself. Thus, always try to grasp the problem completely before diving into coding.

Bad Example:

// Unclear about what to calculate and return
function calculate(numbers) {
  // coding
}

Good Example:

// Clear about what to calculate and return the sum of an array of numbers
function calculateSum(numbers) {
  let total = 0;
  for (let i = 0; i < numbers.length; i++) {
    total += numbers[i];
  }
  return total;
}

Long, Complex Functions

Long and convoluted functions can be difficult to understand and debug. Aim to create short and simple functions, each with a clear responsibility.

Bad Example:

// Function with too many responsibilities
function processUser(user){
  // Validate user
  // Save user
  // Send notification
};

Good Example:

// Splitting responsibilities into multiple functions
function validateUser(user) {...}
function saveUser(user) {...}
function notifyUser(user) {...}

Inconsistent Coding Style

Inconsistency in your coding style can be confusing for other developers. Make sure to consistently use the same variables case, semicolon usage, and space within operators as explained in the Consistency section above.

Misuse of Comments

Sometimes comments that state the obvious, or are inaccurate, can disrupt the readability of a codebase. Use comments to help future developers (or yourself) understand why the code exists rather than what it is doing.

Bad Example:

// Set x to 10
let x = 10;

Good Example:

// Set x to 10 to account for offset in rendering.
let x = 10;

Clean coding should not solely be a task but a habit. Implement these practices and you will train yourself to code cleanly by default. Recollect, consistent practice holds the key to mastery. Happy coding!

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers