Expression Too Complex

ID

csharp.expression_too_complex

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Complexity

Language

CSharp

Tags

complexity, control-flow, readability

Description

Reports one expression that combines more short-circuit and conditional operators than a reader can follow. The complexity of an expression is the number of &&, || and ternary ?: operators it contains; a count above the threshold — maxLogicalOperators, 3 by default — is reported.

Rationale

A dense condition is hard to read not because it is long but because every operator doubles the number of truth-value combinations the reader has to keep in mind. Precedence makes it worse: && binds tighter than ||, so a || b && c does not group the way the eye reads it, and the grouping is invisible without parentheses. This is exactly where a wrong term or a missing negation survives review — nothing in the layout marks the operand that is wrong, and every unit test that happens to exercise one path passes.

Only the outermost expression of a group is reported, so a single dense condition produces one issue rather than one per nesting level. A lambda or anonymous-method body counts as its own expression, so an inline predicate is measured on its own rather than inflating the expression it appears in. Bitwise & and | are not counted: they do not short-circuit and are covered separately. Comparisons are not counted either — they carry no branching of their own.

This is an expression-level measure and is deliberately separate from the function-level cyclomatic complexity rule. A function can sit comfortably inside its complexity budget and still hold one unreadable condition, and a function with many simple branches can exceed the function-level budget without containing a single complex expression.

public class Gate
{
    public bool CanEnter(bool member, bool paid, bool banned, bool invited, bool staff)
    {
        if (member && paid && !banned && invited && staff)      // FLAW — 4 operators
        {
            return true;
        }

        if (member && paid && !banned)                          // OK — 2 operators
        {
            return true;
        }

        return false;
    }

    public bool Readable(bool member, bool paid, bool banned, bool invited, bool staff)
    {
        bool inGoodStanding = member && paid && !banned;        // OK — named, 2 operators
        bool hasOverride = invited || staff;                    // OK — named, 1 operator
        return inGoodStanding || hasOverride;                   // OK — 1 operator
    }
}

Remediation

Give the intermediate conditions names. Assigning each part of the condition to a well-named local turns the operator soup into a sentence, costs nothing at run time thanks to short-circuiting being preserved by the compiler in most shapes, and makes the wrong term visible. For a condition that belongs to a domain concept, promote it to a small predicate method or a property on the type it interrogates. Where the expression selects among several results rather than testing one thing, a switch expression usually reads better than stacked ternaries.

Configuration

Property Default Description

maxLogicalOperators

3

Number of short-circuit and conditional operators (&&, ||, ?:) in one expression, strictly greater than which the expression is flagged. Raise it for a codebase that tolerates denser conditions; lower it to 2 to push every compound condition towards named intermediates.