The Ultimate Code Review Checklist For Developers

In this article:
Subscribe to our blog:

Deploying code without reviewing it first is like drinking from a dirty stream and hoping to stay healthy. Sooner or later, you'll have to deal with the consequences of not verifying what's entering your body, or in the context of software, your production environment. 

Code reviews form the bedrock of the “clean code” philosophy. Every healthy codebase is a product of a stringent code review process. By conducting code reviews, software developers and teams can keep an eye on the main branch and ensure consistency among everyone involved in the code creation process.  

One way to improve the effectiveness of code reviews is by using a checklist to guide reviewers on what to look for during the process. If your team wants to optimize its code review processes, then understanding some best practices and how to implement them with an application security testing (AST) solution like Codacy is pivotal. 

Why Code Reviews Are Essential

No software developer is immune to mistakes — not even developers with years or decades of experience. Anyone can mistakenly access the wrong variable or fail to validate the user input, which can inadvertently lead to costly software defects in production.

Code reviews allow software teams to find defects the original author might have missed without exposing the code to customers. Here are some reasons why code reviews are essential:

  1. Identifies issues on time: Code reviews are often blocking in nature, meaning the new code must be reviewed and approved before being released to customers (i.e., the pull request approach). This allows code reviewers to detect and fix issues in code before they snowball out of control and wreak havoc in production.

  2. Allows teams to create and maintain consistency in coding style: In a team of developers, each developer might follow a different set of conventions and practices when writing code. Code reviews allow teams to define and enforce coding standards, which ensures consistency in coding style and a smoother collaboration process. This is even more important in large teams and projects.

  3. Enables teams to optimize code for performance: Sometimes the code accomplishes its intended task, but does so inefficiently. In such cases, code reviews allow the more experienced developers to optimize the code while offering the less experienced ones a chance to learn and improve their skills.

  4. Supports knowledge transfer: The collaborative nature of code reviews facilitates knowledge transfer by allowing less experienced developers to learn more reliable coding techniques and best practices from their more experienced counterparts. 

Code reviews help teams maintain consistency in the codebase by enforcing policies and norms. This makes it easier to share code and allows anyone to work on any part of the code without much friction.

The Ultimate Code Review Checklist

A comprehensive code review involves examining various aspects of your software, not just the correctness of the code. Here’s a checklist of things to check for when reviewing source code.

1. Check The Code’s Logic

One of the main objectives of code reviews is to verify that the code does what it was written to do. Check that the code behaves as expected when given different parameters and that all edge cases are covered.

For example, consider this JavaScript function that calculates the sum of all even numbers in an array:

 

function sumEvenNumbers(arr) {
    return arr.filter(num => num % 2 === 0).reduce((sum, num) => sum + num, 0);
}

To check if this function works correctly, we test it with different inputs, including edge cases like empty arrays, arrays with only odd or even numbers, and arrays with negative numbers.

 

// Test case 1: Normal case with mixed even and odd numbers
console.log(sumEvenNumbers([1, 2, 3, 4, 5, 6])); // Expected output: 12 (2 + 4 + 6)

// Test case 2: Empty array
console.log(sumEvenNumbers([])); // Expected output: 0 (no numbers to sum)

// Test case 3: Array with all even numbers
console.log(sumEvenNumbers([2, 4, 6, 8, 10])); // Expected output: 30 (2 + 4 + 6 + 8 + 10)

// Test case 4: Array with all odd numbers
console.log(sumEvenNumbers([1, 3, 5, 7])); // Expected output: 0 (no even numbers)

// Test case 5: Array with negative numbers
console.log(sumEvenNumbers([-2, -4, -6, 1])); // Expected output: -12 (-2 + -4 + -6)

// Test case 6: Array with mixed positive and negative numbers
console.log(sumEvenNumbers([1, -2, 3, -4, 5, -6])); // Expected output: -12 (-2 + -4 + -6)

The results are consistent with our expectations, which you should aim for in tests like these. Incorporating log statements and error handling will help troubleshoot issues if they arise. 

2. Error Handling And Logging

Anyone writing code must be prepared for potential errors, whether they anticipate them or not. During review, check that the code includes proper error handling and logging mechanisms, as these help when debugging and troubleshooting.

Going back to our first example, let’s modify the sumEvenNumber function to include error logging with error messages that are clear, descriptive, and actionable:

 

function sumEvenNumbers(arr) {
    // Check if the input is an array
    if (!Array.isArray(arr)) {
        throw new TypeError('Input must be an array');
    }

    // Ensure all elements in the array are numbers
    if (!arr.every(num => typeof num === 'number')) {
        throw new TypeError('All elements in the array must be numbers');
    }

    // Filter out even numbers and return the sum
    return arr.filter(num => num % 2 === 0).reduce((sum, num) => sum + num, 0);
}

Based on the code above, here’s what’s going to happen in different scenarios: 

  • Non-array input: The function throws an error if the input is not an array.

  • Non-numeric elements: If any element in the array is not a number, the function throws an error. 

  • Empty array: Although not an error, it's important to ensure that the function handles empty arrays gracefully (it will return 0 in this case, which is correct).

3. Readability And Maintainability

Clean code is easier to understand and maintain. During reviews, check that the code is well-formatted and follows “clean code” principles like proper indentation, consistent naming style, and appropriate use of comments to explain parts of the code that may not be obvious.

For instance, take a look at the code below:

 

function sumEvenNumbers(arr) {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] % 2 === 0) {
            sum += arr[i];
        }
    }
    return sum;
}

While this function does its job of summing all even numbers, it’s verbose and hard to read. Here’s a better version:

 

function sumEvenNumbers(arr) {
    return arr.filter(num => num % 2 === 0).reduce((sum, num) => sum + num, 0);
}

We reduced the code from nine to just four lines by incorporating modern language constructs like filter() and reduce(), making the code easier to read and less verbose. 

4. Code Structure and Design

Check that the new code aligns with the project’s overall architecture and design. This ensures that it integrates smoothly into the codebase and that the project’s structure remains consistent.

Here are some best practices for organizing software code:

  • Dependency Inversion (DIP): This principle states that a given class should not depend directly on another class but on an abstraction (interface) of this class.

  • Separation of Concerns (SoC): Organize the application into distinct layers (e.g., UI, business logic, data) so logic and presentation stay separate. 

  • Single Responsibility Principle (SRP): Each module/class should do one thing and do it well.

  • Modularity: Organize code into small, self-contained modules to increase reusability; group related files logically (e.g., by feature, not file type).

  • Open/Closed Principle (OCP): Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

5. Review For Security Vulnerabilities

Vulnerabilities often result from poor coding practices, insufficient security measures, human error, and insecure third-party libraries. For instance, this Node.js server code has several exploitable vulnerabilities:

 

const express = require('express');
const mysql = require('mysql');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));

// Database connection (SQL Injection vulnerability)
const db = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'password',
  database: 'test_db'
});

db.connect((err) => {
  if (err) {
    console.error('Error connecting to database:', err);
    return;
  }
  console.log('Connected to the database');
});

// Vulnerable route with SQL Injection
app.get('/user', (req, res) => {
  const username = req.query.username; // User input
  const query = `SELECT * FROM users WHERE username = '${username}'`; // SQL Injection vulnerability
  db.query(query, (err, result) => {
    if (err) throw err;
    res.send(result);
  });
});

// Vulnerable form for XSS (Cross-Site Scripting)
app.get('/feedback', (req, res) => {
  res.send('<form action="/submit-feedback" method="POST"><input type="text" name="feedback"><button type="submit">Submit</button></form>');
});

app.post('/submit-feedback', (req, res) => {
  const feedback = req.body.feedback; // User input
  res.send(`<h1>Thank you for your feedback:</h1><p>${feedback}</p>`); // XSS vulnerability (unsafe output)
});

// Insecure password storage (no hashing)
app.post('/register', (req, res) => {
  const { username, password } = req.body; // User input
  const query = `INSERT INTO users (username, password) VALUES ('${username}', '${password}')`; // Insecure password storage
  db.query(query, (err, result) => {
    if (err) throw err;
    res.send('User registered');
  });
});

app.listen(3000, () => {
  console.log('App is running on http://localhost:3000');
});

Manually checking for vulnerabilities in your code can be tedious, except if your application is small and comprises a few lines of code. Security testing is best done automatically using a code review tool like Codacy, which looks for vulnerabilities in your code repositories, pull requests, and even IDE as you write code (via IDE extensions). 

6. Performance

Code reviews should check not just if the code works, but also how well it performs. A piece of code might complete its task, but if the code is inefficient, it can cause unnecessary complexity, waste resources, and run slowly.

For instance, the code below is poorly optimized because it reads files synchronously inside a loop using fs.readFileSync(), which blocks the pro gram execution until it completes reading a file:

 

const fs = require('fs');

// Reading files synchronously in a loop
function readFiles() {
  const filenames = ['file1.txt', 'file2.txt', 'file3.txt'];
  let fileContents = [];

  for (let i = 0; i < filenames.length; i++) {
    const content = fs.readFileSync(filenames[i], 'utf8');
    fileContents.push(content);
  }

  return fileContents;
}

console.log(readFiles());

A more efficient approach would be to read the files asynchronously, so it doesn't block the execution of the program:

 

const fs = require('fs').promises;

// Reading files asynchronously using promises
async function readFiles() {
  const filenames = ['file1.txt', 'file2.txt', 'file3.txt'];
  const fileContents = await Promise.all(filenames.map(filename => fs.readFile(filename, 'utf8')));
  return fileContents;
}

readFiles().then(contents => console.log(contents));

You can resolve quality issues like this by incorporating an application security testing (AST) tool that also checks for code quality, like Codacy.

7. Test Coverage

Test coverage shows if our test cases cover the application code and measures the extent to which the code is exercised during test execution. This technique is essential for uncovering potential bugs and vulnerabilities.

During code reviews, ensure that the code has undergone rigorous testing, including unit tests, integration tests, code coverage, cross-browser tests, and responsiveness tests. The tests should be passing and up-to-date.

8. Proper Use Of Dependencies

When you integrate insecure third-party code into your software, its vulnerabilities become a part of your application and can serve as an attack vector for cybercriminals. For this reason, you must review all libraries, frameworks, or components and ensure that any dependencies are managed correctly and up-to-date.

Software composition analysis (SCA) is an important security measure that scans the entire software supply chain to find and fix vulnerabilities. You can automate SCA testing using a code review tool like Codacy.

9. Adherence To Coding Standards

Coding standards are collections of coding rules, guidelines, and best practices to ensure consistent code quality, compliance with language standards, and software security. 

Always check that the new code follows the standards for the organization, project, or programming language. Use linters and other static analysis tools to identify issues with code style, including formatting and syntax problems, such as variable names, use of brackets and quotation marks, tabs versus spaces, etc.

10. Documentation

Check that the code is well-documented, and that comments are included where necessary (to explain parts of the code that may not be obvious). Typically, your comments should be brief. However, thorough comments can be occasionally used to explain complex functionality.

For example:

 

/**
* Filters and processes a list of products, applying discounts and tax,
* and returns only those products that meet the price criteria and are in stock.
*
* @param {Array<Object>} products - The list of product objects to process. Each product
* should have the following properties:
*   - `price` {number} - The original price of the product.
*   - `inStock` {boolean} - Whether the product is available in stock.
*
* @param {number} discountRate - The discount rate to apply to the product price, expressed
* as a decimal. For example, 0.1 for a 10% discount.
*
* @param {number} taxRate - The tax rate to apply, expressed as a decimal. For example,
* 0.08 for an 8% tax.
*
* @param {number} minPrice - The minimum final price (after discount and tax) a product
* must meet in order to be included in the returned list.
*
* @returns {Array<Object>} A filtered list of products where each product includes
* a `finalPrice` property, representing the final price after discount and tax.
*
* @example
* // Example usage:
* const products = [
*   { price: 100, inStock: true },
*   { price: 150, inStock: false },
*   { price: 200, inStock: true }
* ];
*
* processProducts(products, 0.1, 0.08, 120);
* // Returns: [
* //   { price: 100, inStock: true, finalPrice: 97.2 },
* //   { price: 200, inStock: true, finalPrice: 216 }
* // ]
*
* @throws {TypeError} If any of the input parameters are not of the expected type,
* such as if the `products` array contains invalid objects or the rates are not numbers.
*/
function processProducts(products, discountRate, taxRate, minPrice) {
    return products.filter(product =>
        product.inStock &&
        (product.price - product.price * discountRate) * (1 + taxRate) >= minPrice
    ).map(product => {
        product.finalPrice = (product.price - product.price * discountRate) * (1 + taxRate);
        return product;
    });
}

Conclusion

The primary goal of using a code review checklist is to ensure consistency among team members who are involved in the code creation and review process. A good checklist provides newcomers and experienced developers with clear guidance on the factors to consider during the process.

Codacy specializes in automating and standardizing code reviews to assist developers in handling vast amounts of code. It provides technological tools for measuring, managing, and improving software quality and security measures, alongside integrations that streamline the code review process.

Get started with Codacy today for free and take your first step toward writing clean and secure code.







RELATED
BLOG POSTS

Top mistakes your dev team makes when performing code reviews
Code reviews are an essential part of any software development process and are crucial for improving code quality. However, despite their importance,...
Code Reviews in Large-Scale Projects: Best Practices for Managers
Code review in large teams and projects is daunting. Almost everything is against you: you have many developers and a lot of code spread over a large...
Code review process: how to improve developer productivity
According to our survey of 680 developers, the code review process had an overall positive impact on developer productivity which we discuss below. We...

Automate code
reviews on your commits and pull request

Group 13