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:
| Score | Rating | Meaning |
|---|---|---|
| 20 – 100 | 🟢 Green | Good maintainability |
| 10 – 19 | 🟡 Yellow | Moderate maintainability — consider reviewing |
| 0 – 9 | 🔴 Red | Low 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
}
| Property | Type | Default | Description |
|---|---|---|---|
MaintainabilityIndexThreshold | integer | 20 | Procedures 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.
After changing alcops.json in VS Code, reload the window (Ctrl+Shift+P → Developer: Reload Window) for the new settings to take effect.
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;
Related content
- Code Metrics — Maintainability Index range and meaning — Microsoft Learn
- Calculating the Maintainability Index — Sourcery
- Classic Maintainability Index — ObjectScript Quality
- Measurement of Maintainability Index — Verifysoft
- Maintainability Index — BlueGrid