Skip to main content

Maintain legacy code

This topic describes the concept and classifications of legacy code, and provides strategies for different types of legacy code. This topic also describes the support of Qoder CN for maintaining legacy code.

Legacy code

  • Code related to outdated technologies:
    • Code related to operating systems or software libraries that are no longer supported.
    • Code that relies on obsolete technology stacks or programming languages.
  • Code that is reserved for compatibility with legacy features:
    • Code segments that are reserved for compatibility with older versions of features in modern software.
    • Code that has to be reserved for backward compatibility.
  • Code that has no corresponding documentation and is not maintained:
    • Old code that has no corresponding documentation.
    • Code that has no modern development practices such as unit tests and code reviews.

Solutions of legacy code

You can use one of the following strategies to address legacy code issues:

Solution

Pros and cons

Rewrite code

Costs are high and the existing system can continue to run as expected, which results in code risks.

Refactor code

Supplement unit tests

Unit tests are used to identify issues in code and provide quality assurance for possible code changes.

The preceding table shows that supplementing unit tests is an effective method to solve legacy code issues. However, the following issues still come with this method:
  • A large amount of legacy code lacks unit tests, and test costs are very high due to the complex dependencies between code.
  • Specific metrics are not clear to define an adequate unit test.
  • The code that requires unit tests is not clear.

Common mistakes in unit tests

  • False unit tests that lack assertions: Developers may call functions without assertions to improve the code coverage, which results in many invalid unit tests.
  • Unit tests that are considered as white-box tests: Some viewpoints classify unit tests as white-box tests. However, unit tests are actually black-box tests specific to function signatures.
  • Unit tests that rely on actual environments: The main factors that hamper unit tests include laziness and environment-dependent configurations. If you do not use Stub or Mock to remove the dependence on the external environment such as IP addresses or databases, unit tests cannot meet the principle of FIRST (fast, independent, repeatable, self-validating, and timely).

Selectively perform unit tests

In addition to benefits, unit tests generate costs. If you analyze legacy code from a benefit and cost perspective, you can supplement unit tests for legacy code. This means that you can selectively perform unit tests. How do you distinguish costs and benefits?

Cost-benefit quadrant classification of unit tests for legacy code

Unit tests specific to legacy code can be categorized in quadrants based on their cost and benefits. The following figure shows the classification criteria and quadrants of unit tests.

Classification criteria

  • X-axis (Cost): A higher code dependency causes the greater test cost.
  • Y-axis (Benefit): A higher code complexity causes the greater quality gain.

Four quadrants

Code classificationFeatureDescriptionBenefitCost
Algorithms codeHigh cyclomatic complexity and fan-InIt contains many conditional judgment and loop statements and depends on a small number of code, but is relied on by a large number of code.HighLow
Trivial codeLow cyclomatic complexity and high fan-InIt uses simple logic and contains only one or two lines of code.LowLow
Coordinators codeLow cyclomatic complexity and high fan-InIt is at the upper layer of the calling relationship and reflects specific business scenarios by calling other code.LowHigh
Overcomplicated codeHigh cyclomatic complexity and fan-InIt uses complex logic, has multiple dependencies, contains a large number of functions and parameters. It is typical code smell.HighHigh

Cyclomatic complexity and its dependencies

  • Cyclomatic complexity: the number of logical branches in the code.
  • Fan-in: the number of parent modules that directly call the module. A large fan-in indicates a high degree of module reuse.
  • Fan-out: the number of modules that a module directly calls. A large fan-out indicates that the module depends on a large number of modules.

Strategies for different types of code

The following strategies apply to legacy code based on the preceding analysis:
  • For algorithms code, you can generate unit testing.
  • For coordinators code, you can perform API testing.
  • For overcomplicated code, you can seek the right opportunity to refactor code.
  • For trivial code, you do not need to process code.

Use Qoder CN to process legacy code

1. Understand a project

To maintain the legacy code of a project, you can use @workspace to first understand the purpose of the entire project and its modules.

2. Process different types of code

  • Generate unit testing for algorithms code
  • Perform API testing on coordinators code
  • Seek an opportunity to refactor overcomplicated code
Select the sections for which you want to generate code based on production code. During the generation, you can focus on dependency information such as the required framework and Mock. You can append relevant information after a unit test command is generated. Example: /generate unit testingCppUTest.In general, the number of unit test cases generated from the existing code is usually limited. If you have specific requirements for the number of test scenarios and use cases of unit tests, you can generate more unit tests by continuing test functions in a unit test file that is newly generated. During the continuation, Qoder CN follows the existing use cases as much as possible and use the cases as a context for reference.

Summary

To sum up, you can keep the best practices in mind when processing legacy code. To effectively process legacy code, you must go deep into the complex structure of the code and trace every possible branch node in detail. In this process, in addition to identifying and fixing potential defects, you must complete all tasks within a limited time. To minimize the time required for processing legacy code, we recommend that developers prevent code corruption, make good use of tools, and follow good programming principles in the early writing stages.