Inappropriate coding practices - Cyclomatic complexity - Dart

Inappropriate coding practices - Cyclomatic complexity - Dart

Need

Improvement of coding practices to reduce cyclomatic complexity

Context

  • Usage of Dart for building cross-platform mobile, web, and desktop applications
  • Usage of Shelf for managing and organizing data in a Node.js application

Description

Non compliant code

        import 'package:shelf/shelf.dart' as shelf;

shelf.Response handleRequest(shelf.Request request) {
  var method = request.method;
  var url = request.requestedUri.path;

  if (method == 'GET') {
    if (url == '/home') {
      return shelf.Response.ok('Home Page');
    } else if (url == '/about') {
      return shelf.Response.ok('About Page');
    } else if (url == '/contact') {
      return shelf.Response.ok('Contact Page');
    } else if (url == '/help') {
      return shelf.Response.ok('Help Page');
    } else if (url == '/privacy') {
      return shelf.Response.ok('Privacy Page');
    } else if (url == '/terms') {
      return shelf.Response.ok('Terms Page');
    } else {
      return shelf.Response.notFound('Page Not Found');
    }
  } else if (method == 'POST') {
    if (url == '/login') {
      return shelf.Response.ok('Login Page');
    } else if (url == '/signup') {
      return shelf.Response.ok('Signup Page');
    } else if (url == '/logout') {
      return shelf.Response.ok('Logout Page');
    } else {
      return shelf.Response.notFound('Page Not Found');
    }
  } else {
    return shelf.Response.forbidden('Method Not Allowed');
  }
}
        
        

This code has a high cyclomatic complexity due to the nested if and else if statements. Each if or else if statement adds a path through the code, increasing the cyclomatic complexity. This makes the code harder to understand, maintain, and test.

In addition, the code's complexity can lead to the introduction of new security vulnerabilities. For example, if a new endpoint is added, it might be easy to forget to add it to the list of allowed endpoints. This could lead to unauthorized access to the endpoint.

Moreover, the high complexity of the code makes it harder to detect and fix existing vulnerabilities. For example, if a vulnerability is found in the way the application handles requests, it will be harder to locate the vulnerable code due to the high number of paths through the code.

To mitigate this, the code can be refactored to reduce the number of conditional statements, thus reducing the cyclomatic complexity. This can be achieved by using a routing library or creating a map of routes and their corresponding handlers.

Steps

  • Identify the parts of the code that are causing high cyclomatic complexity. In this case, it's the nested if-else statements.
  • Refactor the code to reduce the number of decision points. This can be achieved by using a switch-case statement or a map/dictionary to handle different cases.
  • For the 'GET' and 'POST' methods, create a map/dictionary where the keys are the URLs and the values are the corresponding responses. This will eliminate the need for multiple if-else statements.
  • For the method check, use a switch-case statement instead of if-else. The cases would be 'GET', 'POST', and a default case to handle other methods.
  • By doing these changes, the cyclomatic complexity will be reduced, making the code easier to understand and maintain.

Compliant code

        import 'package:shelf/shelf.dart' as shelf;

// Define a map for GET requests
final Map getResponses = {
  '/home': 'Home Page',
  '/about': 'About Page',
  '/contact': 'Contact Page',
  '/help': 'Help Page',
  '/privacy': 'Privacy Page',
  '/terms': 'Terms Page',
};

// Define a map for POST requests
final Map postResponses = {
  '/login': 'Login Page',
  '/signup': 'Signup Page',
  '/logout': 'Logout Page',
};

shelf.Response handleRequest(shelf.Request request) {
  var method = request.method;
  var url = request.requestedUri.path;

  switch (method) {
    case 'GET':
      return getResponses.containsKey(url)
          ? shelf.Response.ok(getResponses[url])
          : shelf.Response.notFound('Page Not Found');
    case 'POST':
      return postResponses.containsKey(url)
          ? shelf.Response.ok(postResponses[url])
          : shelf.Response.notFound('Page Not Found');
    default:
      return shelf.Response.forbidden('Method Not Allowed');
  }
}
        
        

The refactored code reduces the cyclomatic complexity by eliminating the nested if-else statements and replacing them with a switch-case statement and map lookups.

The handleRequest function now uses a switch-case statement to handle different HTTP methods. For 'GET' and 'POST' methods, it looks up the requested URL in the corresponding map ( getResponses or postResponses). If the URL is found in the map, it returns a response with the corresponding value from the map. If the URL is not found, it returns a 'Page Not Found' response.

For any other HTTP methods, the function returns a 'Method Not Allowed' response.

This refactoring makes the code easier to understand and maintain, and reduces the risk of introducing new security vulnerabilities.

References