Cyclomatic Complexity threshold exceeded (LC0010)

This rule triggers when a procedure’s Cyclomatic Complexity meets or exceeds a configurable threshold. High complexity means more independent execution paths, making the code harder to understand, test exhaustively, and maintain safely.

What triggers this diagnostic?

The analyzer calculates the Cyclomatic Complexity for every procedure and trigger in your AL project. When the calculated score is greater than or equal to the configured threshold, this diagnostic is reported on the procedure.

By default the threshold is 8. This aligns with McCabe’s well-established risk categorization:

Cyclomatic ComplexityRisk level
1 – 10🟢 Low — simple, easy to test
11 – 20🟡 Moderate — review recommended
21 – 50🟠 High — consider refactoring
> 50🔴 Very high — refactoring strongly recommended

A threshold of 8 provides an early warning before a procedure crosses into moderate risk territory. For a full explanation of how the score is calculated and which AL constructs contribute to it, see LC0009 — Cyclomatic Complexity metric .

Why refactor high-CC code?

Procedures with high Cyclomatic Complexity are statistically more likely to:

  • Contain hidden defects — each additional execution path is another place where bugs can hide, and it is easy to overlook edge cases.
  • Require more tests — Cyclomatic Complexity represents the minimum number of test cases needed for full branch coverage. A procedure with a CC of 15 needs at least 15 test paths.
  • Resist safe modification — even small changes in a complex procedure carry a higher risk of unintended side effects, because the developer must reason about all the interacting paths.
  • Slow down development — developers spend more time understanding the code before they can make progress, especially team members who did not write the original code.
  • Complicate upgrades — complex procedures are disproportionately likely to cause issues during Business Central major version upgrades.

Addressing high-CC procedures proactively reduces long-term maintenance cost and keeps your extension healthy across upgrade cycles.

Configuration

Adjusting the threshold

Add or update the CyclomaticComplexityThreshold property in the alcops.json file at your project root (alongside app.json):

{
    "CyclomaticComplexityThreshold": 10
}
PropertyTypeDefaultDescription
CyclomaticComplexityThresholdinteger8Procedures with a Cyclomatic Complexity at or above this value trigger a warning.

Raise the threshold if your team is comfortable with moderately complex procedures (e.g. 10 or 15). Lower it for stricter quality enforcement.

Controlling severity

Use a .ruleset.json file to change how the diagnostic is reported — or to disable it entirely:

{
    "name": "My Project Ruleset",
    "rules": [
        {
            "id": "LC0010",
            "action": "Error"
        }
    ]
}

You can also suppress the diagnostic for individual procedures using a #pragma directive:

#pragma warning disable LC0010
procedure LegacyDataMigration()
begin
    // Complex but stable legacy code — suppressed intentionally
end;
#pragma warning restore LC0010

See Configuration for more details on all configuration options.

Reducing Cyclomatic Complexity

Each decision point — every if, case line, loop, and logical operator — adds one to the complexity count. The strategies below target these constructs directly.

Extract helper procedures

Break large procedures into smaller, focused helpers. Each extracted procedure carries its own separate complexity score, and the calling procedure becomes simpler.

procedure ProcessDocument(var SalesHeader: Record "Sales Header")
begin
    case SalesHeader."Document Type" of
        SalesHeader."Document Type"::Order:
            ProcessOrder(SalesHeader);
        SalesHeader."Document Type"::Quote:
            ProcessQuote(SalesHeader);
    end;
end;

Use early returns

Replace deeply nested if/else chains with guard clauses that exit the procedure early. This eliminates else branches and reduces nesting:

procedure ValidateCustomer(Customer: Record Customer)
begin
    if Customer."No." = '' then
        exit;
    if Customer.Blocked <> Customer.Blocked::" " then
        exit;

    // Main validation logic — no deep nesting
    ValidatePostingSetup(Customer);
end;

Simplify compound conditionals

Extract compound boolean expressions into well-named helper procedures. Each and/or operator adds to CC — moving the logic out keeps the calling procedure simple and makes the condition self-documenting:

procedure IsEligibleForDiscount(Customer: Record Customer): Boolean
begin
    exit(
        Customer."Customer Posting Group" = 'PREMIUM' and
        (Customer."Balance (LCY)" > 10000)
    );
end;

Break down large case statements

Each case line adds to Cyclomatic Complexity. If a case statement has many branches, move the body of each branch into its own procedure or consider rethinking the design with enum extensibility:

procedure HandleRecordType(ImportBuffer: Record "Import Buffer")
begin
    case ImportBuffer."Record Type" of
        'CUSTOMER':
            ImportCustomer(ImportBuffer);
        'HEADER':
            ImportSalesHeader(ImportBuffer);
        'LINE':
            ImportSalesLine(ImportBuffer);
    end;
end;

Example

The following procedure triggers LC0010 because its Cyclomatic Complexity exceeds the default threshold of 8:

procedure ImportData(var ImportBuffer: Record "Import Buffer") // LC0010: Cyclomatic Complexity exceeds threshold
var
    Customer: Record Customer;
    SalesHeader: Record "Sales Header";
    SalesLine: Record "Sales Line";
begin
    if ImportBuffer.FindSet() then
        repeat
            if ImportBuffer."Record Type" = 'CUSTOMER' then begin
                if not Customer.Get(ImportBuffer."No.") then begin
                    Customer.Init();
                    Customer."No." := ImportBuffer."No.";
                    Customer.Name := ImportBuffer.Description;
                    if ImportBuffer."Country Code" <> '' then
                        Customer."Country/Region Code" := ImportBuffer."Country Code";
                    Customer.Insert(true);
                end else begin
                    Customer.Name := ImportBuffer.Description;
                    if ImportBuffer."Country Code" <> '' then
                        Customer."Country/Region Code" := ImportBuffer."Country Code";
                    Customer.Modify(true);
                end;
            end else
                if ImportBuffer."Record Type" = 'HEADER' then begin
                    SalesHeader.Init();
                    SalesHeader."No." := ImportBuffer."No.";
                    SalesHeader."Sell-to Customer No." := ImportBuffer."Customer No.";
                    SalesHeader.Insert(true);
                end else
                    if ImportBuffer."Record Type" = 'LINE' then begin
                        SalesLine.Init();
                        SalesLine."Document No." := ImportBuffer."No.";
                        SalesLine."Line No." := ImportBuffer."Line No.";
                        SalesLine.Description := ImportBuffer.Description;
                        SalesLine.Quantity := ImportBuffer.Quantity;
                        SalesLine.Insert(true);
                    end;
        until ImportBuffer.Next() = 0;
end;

After refactoring into focused helper procedures, each procedure has fewer branches and a lower Cyclomatic Complexity:

procedure ImportData(var ImportBuffer: Record "Import Buffer")
begin
    if not ImportBuffer.FindSet() then
        exit;

    repeat
        case ImportBuffer."Record Type" of
            'CUSTOMER':
                ImportCustomer(ImportBuffer);
            'HEADER':
                ImportSalesHeader(ImportBuffer);
            'LINE':
                ImportSalesLine(ImportBuffer);
        end;
    until ImportBuffer.Next() = 0;
end;

local procedure ImportCustomer(ImportBuffer: Record "Import Buffer")
var
    Customer: Record Customer;
begin
    if not Customer.Get(ImportBuffer."No.") then begin
        Customer.Init();
        Customer."No." := ImportBuffer."No.";
    end;

    Customer.Name := ImportBuffer.Description;
    if ImportBuffer."Country Code" <> '' then
        Customer."Country/Region Code" := ImportBuffer."Country Code";

    if Customer.Insert(true) then
        exit;
    Customer.Modify(true);
end;

See also

  • LC0009 — Cyclomatic Complexity metric
  • LC0007 — Maintainability Index metric
  • LC0008 — Maintainability Index threshold exceeded