In software development, writing clean, maintainable, and readable code is crucial for the success of any project. Code that is well-organized, easy to understand, and can be easily modified and extended not only improves developer productivity but also reduces the chances of introducing bugs and errors.
In this article, we will discuss some of the best practices and techniques for writing clean, maintainable, and readable code in JavaScript and TypeScript, but it's important to note that these principles can also apply to other programming languages.
Use meaningful variable and function names
One of the fundamental principles of writing clean code is using meaningful and descriptive variable and function names. Well-chosen names can greatly enhance the readability and understandability of your code. When someone reads your code, they should be able to quickly grasp the purpose and functionality of each variable or function without needing extensive comments or additional documentation.
For example, let's consider the following code snippet that uses poorly named variables and functions:
let a = 10;
let b = 20;
function f(x, y) {
let c = x + y;
if (c > a) {
return c - b;
} else {
return c + b;
}
}
console.log(f(a, b));
In this code, it's not clear what the variables a and b represent. Similarly, the function f has a nondescript name and uses cryptic variable names like x, y, and c. This makes it difficult to understand what the function is doing and how it is related to the rest of the code.
To improve the readability of this code, we can start by using more descriptive variable names that indicate their purpose. For example, we can rename a to threshold and b to offset to better communicate their meaning:
let threshold = 10;
let offset = 20;
function f(x, y) {
let sum = x + y;
if (sum > threshold) {
return sum - offset;
} else {
return sum + offset;
}
}
console.log(f(threshold, offset));
With these changes, it is now much easier to understand what this code is doing. The variables threshold and offset are named after their specific roles, and the function f has been renamed to calculateSumWithThreshold to better communicate its purpose. Additionally, the variable c has been renamed to sum, which more clearly indicates its value.
Using descriptive and meaningful names for variables and functions helps improve the clarity and maintainability of the code. Even in a more complex example, using good naming conventions can make a big difference in how easily the code can be understood and maintained.
Use proper indentation and formatting
Consistent indentation and formatting play a crucial role in code readability. Properly formatted code enhances comprehension and makes it easier to follow the logical structure of the codebase. It also promotes consistency across the project, especially when multiple developers are working on the same code.
Consider the following code without proper indentation:
function calculateAverage(numbers){
let sum = 0;
for(let i=0;i<numbers.length;i++){
sum += numbers[i];
}
let average = sum/numbers.length;
return average;
}
Without indentation, it becomes challenging to visually identify the code's structure and nesting. This can lead to confusion and introduce errors while understanding or modifying the code.
By applying consistent indentation, the code becomes much more readable and structured:
function calculateAverage(numbers) {
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}
let average = sum / numbers.length;
return average;
}
In the formatted code, each level of indentation represents a higher level of nesting or scope. It clarifies the relationship between different blocks of code, such as loops and conditional statements. The code is now easier to scan and understand at a glance.
In addition to indentation, consistent formatting practices should be applied, such as proper spacing around operators, consistent placement of braces, and alignment of related code elements. Adhering to a style guide or adopting a code formatter tool like Prettier can help maintain consistent formatting across the codebase.
Keep functions, methods, and classes short
"When picking a name for something, use the most common shortest name". This principle extends beyond domain names and applies to various elements in software development. It encompasses variables, functions, classes, and more. By choosing shorter and more common names, code readability and understandability can be significantly improved.
For instance, suppose you have a function named relinquishSomething. In that case, it might be beneficial to opt for a shorter and more commonly used alternative. You could rename the function to releaseSomething, for example. The word "release" is shorter and more familiar than "relinquish." Use Google or ChatGPT to search for word synonyms, e.g., "relinquish synonyms", to find the shortest and most common similar term.
Comment and Document
Commenting and documenting your code is important for ensuring its clarity, maintainability, and understandability. Well-placed comments and documentation provide valuable insights into the code's purpose, behavior, and any considerations or limitations. They serve as a communication tool for developers, including your future self and team members, helping them navigate and work with the code more effectively.
In the past, I have made the mistake of neglecting proper commenting and documentation. I used to believe that my code would never be read by someone else and that investing time in commenting was a waste. However, I soon realized that this approach was flawed.
Code is rarely developed in isolation. Projects involve collaboration, maintenance, and often, the passing of code from one developer to another. Even if you are the sole developer, you may revisit your code months or years later, and without proper documentation, understanding your own code becomes a daunting task.
Here are some best practices for commenting and documenting your code:
- Use inline comments: Include comments within your code to explain complex or non-obvious sections. Inline comments should provide additional context and clarification without stating the obvious.
// Calculate the sum of the array
let sum = 0;
for (let i = 0; i < array.length; i++) {
sum += array[i];
}
- Write descriptive function and method headers: Begin each function or method with a descriptive comment that summarizes its purpose, input parameters, return values, and any side effects. This helps readers quickly understand the function's behavior without diving into the implementation details.
/**
* Calculate the average of an array of numbers.
* @param {number[]} numbers - The input array of numbers.
* @returns {number} The average of the numbers.
*/
function calculateAverage(numbers) {
// Implementation
}
Document external APIs: When working with external APIs or libraries, document how to use them effectively. Provide examples, explain input parameters and their expected values, and detail the structure of the response or expected output.
Update comments regularly: Keep your comments up-to-date with the code changes. Outdated or misleading comments can be worse than having no comments at all, as they can misguide developers and introduce confusion.
Write README files: Include a README file in your project's root directory to provide an overview of the project, installation instructions, usage examples, and any relevant information for contributors or users.
Avoid code duplication (DRY Principle)
Code duplication, also known as "Don't Repeat Yourself" (DRY) principle, refers to the practice of having redundant or repetitive code in your codebase. Duplicated code can lead to several issues, including increased maintenance overhead, decreased readability, and the risk of introducing bugs.
You can avoid code duplication and adhere to the DRY principle by considering the following techniques:
Extract reusable code: Identify common functionalities and create separate functions, methods, or classes
Use inheritance and composition: Leverage inheritance and composition in object-oriented programming for code reuse
Use libraries and frameworks: Use existing tools instead of reinventing functionalities to reduce duplication.
Refactor duplicated code: Regularly review code and extract common logic into reusable components.
Avoid magic numbers and strings
Magic numbers and strings are hard-coded values that are used throughout your codebase, often without any clear explanation or documentation. These values can be difficult to understand and maintain, leading to bugs and errors. Always declare constants for any magic numbers or strings and use them throughout your codebase. This makes it easier to modify these values in the future and provides context for their usage.
Avoid deep nesting
Deep nesting refers to excessive levels of indentation and nesting within your code. It occurs when there are multiple levels of conditional statements, loops, or nested functions within each other. While nesting is sometimes necessary, excessive levels of nesting can make your code harder to read, understand, and maintain. It also increases the chances of introducing bugs and decreases code performance.
You can avoid deep nesting in your code by following these tips. First, simplify your logic by breaking down complex operations into smaller, more manageable functions or methods. This helps improve code organization and readability. Second, regularly review your code and look for opportunities to refactor or extract nested blocks into separate functions or methods, reducing the levels of nesting. Lastly, consider using early returns to handle exceptional cases or exit conditions early in your code, which can help flatten the code structure and make it more readable.
Continuous refactoring
Continuous refactoring is another important aspect of writing clean code. It involves making changes to the existing codebase to improve its design, structure, and readability without changing its functionality. It helps in eliminating code smells and reduces technical debt.
Refactoring is an ongoing process that should be integrated into your development workflow. It should not be considered a separate task to be performed after the code has been written. It should be done regularly, in small incremental steps, to ensure that the code is always in a good state.
Remove unused code
How many times have you forgotten to remove unused code from your projects? For me, it's happened more often than I'd like to admit. However, it's crucial to regularly review your code and eliminate any unused functions, classes, or variables that you no longer need. By doing so, you can greatly enhance your code's readability and reduce complexity. Additionally, removing unused code improves the performance of your application by eliminating unnecessary processing. Make it a habit to identify and remove unused code, either through manual code reviews or with the help of tools like Eslint.
Summary
Writing maintainable and readable code is a fundamental skill for any developer. By adhering to best practices and techniques like following a consistent coding style, properly commenting on your code, and avoiding code duplication, you can greatly improve the quality of your codebase.
Remember to also prioritize simplicity and readability over complex, convoluted code that may be difficult to understand and maintain. Take advantage of automated tools to help identify and remove unused code and reduce the complexity of your codebase.
Connect with me on various platforms
Top comments (6)
Great list!
While not necessarily a clean code tip, I'd suggest also using
const
where appropriate. One of the examples above has the linesaverage
is never reassigned, so it's possible to useconst
. By doing so, it can make bugs easier to spot: an error is thrown if aconst
is accidentally reassigned. That doesn't happen in the example, but is a real possibility in more complex code.Thanks for the suggestion!
No problem, Thomas.
You can take that a step further and skip the variable altogether if the function name clearly expresses the expected return value:
Hi Graham.
This is a valid point and I've seen many developers do this.
Personally, I tend to declare the variable because it makes debugging easier. When the calculation is stored in a variable, you can put a breakpoint on the next line and hover the mouse/use a 'watch' to see its value (though I know in a browser it's possible to highlight the code and the tooltip will show the result too).
I used to work at a place where where the team heavily insisted on skipping the variable declaration 'because it takes additional memory'. I'm not fully convinced it will make any meaningful difference once compiled, but that was (approximately) the reason given to me. However, whenever there was a problem, the first thing they always did was to rewrite the code to declare a variable so they could breakpoint and inspect it.
However, I realise it's a stylistic thing. I may well be in the minority by declaring a variable and then immediately returning it.
Looks like you forgot to rename the function
x
in the refactor of the first example.