Maintainability Index threshold exceeded (LC0008)

This rule triggers when a procedure’s Maintainability Index falls at or below a configurable threshold. A low Maintainability Index indicates code that is likely difficult to understand, test, and maintain — and disproportionately expensive to change safely.

What triggers this diagnostic?

The analyzer calculates the Maintainability Index for every procedure and trigger in your AL project. When the calculated score is at or below the configured threshold, this diagnostic is reported on the procedure.

By default the threshold is 20, which corresponds to the boundary between the 🟢 green (good) and 🟡 yellow (moderate) rating bands in the Microsoft scoring scale:

ScoreRatingMeaning
20 – 100🟢 GreenGood maintainability
10 – 19🟡 YellowModerate maintainability — consider reviewing
0 – 9🔴 RedLow maintainability — refactoring recommended

For a full explanation of how the score is calculated, see LC0007 — Maintainability Index metric .

Why refactor low-MI code?

Procedures with a low Maintainability Index are statistically more likely to:

  • Contain hidden defects — complex, lengthy code is harder to reason about and easier to break.
  • Resist safe modification — even small changes carry a higher risk of unintended side effects.
  • Slow down development — developers spend more time understanding the code before they can make progress.
  • Complicate upgrades — low-MI procedures are often the source of issues during Business Central major version upgrades.

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

Configuration

Adjusting the threshold

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

{
    "MaintainabilityIndexThreshold": 15
}
PropertyTypeDefaultDescription
MaintainabilityIndexThresholdinteger20Procedures scoring at or below this value trigger a warning.

Lower the threshold if you want to allow moderately complex code without warnings. Raise it if you want 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": "LC0008",
            "action": "Error"
        }
    ]
}

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

#pragma warning disable LC0008
procedure LegacyImport()
begin
    // Complex but stable legacy code
end;
#pragma warning restore LC0008

See Configuration for more details on all configuration options.

Improving the Maintainability Index

The three components that drive the Maintainability Index are Halstead Volume (code vocabulary size), Cyclomatic Complexity (number of decision paths), and Lines of Code. Reducing any of these in a procedure will improve its score. Below are practical strategies for AL code.

Extract helper procedures

Break large procedures into smaller, focused helpers. This reduces Lines of Code and Halstead Volume in each individual procedure, and often lowers Cyclomatic Complexity as well.

Use early returns

Replace deeply nested if/else chains with guard clauses that exit the procedure early:

procedure ProcessOrder(var SalesHeader: Record "Sales Header")
begin
    if not SalesHeader.FindFirst() then
        exit;
    if SalesHeader.Status <> SalesHeader.Status::Open then
        exit;

    // Main logic here — no deep nesting
end;

Simplify complex conditionals

Extract compound boolean expressions into well-named helper procedures or variables:

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

This keeps the calling procedure’s complexity low and makes the condition self-documenting.

Break down large case statements

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

Example

The following procedure has a low Maintainability Index because of its length, deep nesting, and many decision branches:

procedure ImportData(var ImportBuffer: Record "Import Buffer") // LC0008: Maintainability index below configured 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 is shorter, has fewer branches, and scores higher on the Maintainability Index:

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

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