How does EK9 detect magic literals?

← Code Quality · Ref: Q317

EK9 detects magic literals using a two-tier system. Magic literals are unnamed numeric or string values embedded directly in code. They obscure intent and make code harder to maintain.

TIER 1: NUMERIC LITERALS AT DECISION POINTS (E11064)

An Integer or Float literal used as an operand of a comparison or a range is flagged. Comparisons are decision points where the meaning of the value matters most. Instead of 'if age >= 18', use a named value 'if age >= legalAdultAge'. The operators are ==, <>, !=, <, >, <=, >=, <=>, <~>, contains and matches (so 'values contains 5' is flagged too).
Not flagged by E11064: the values 0, 1, -1, 0.0 and 1.0; every other literal type (String, RegEx, Character, Boolean, Date, Money and so on - 'word == "admin"' and 'word matches /ad.*/' are fine); and literals inside a constant declaration, require/assert, a constrain declaration or a named argument. A for-range's bounds (for i in 1 ... 10) are not checked.

TIER 2: REPEATED STRING LITERALS (E11065)

A plain double-quoted String literal with 3 or more characters of content that appears 3 or more times in the same file, or 4 or more times across files of the same module, is flagged. Repetition means the value has significance worth naming. Only Strings are counted: numeric, Boolean, Character and RegEx literals, backtick templates and "" are never counted. Literals inside a constant declaration, a list or dict literal, require/assert, a constrain declaration, a range, string concatenation or a named argument are not counted either. A constructor call is NOT exempt: label the arguments or name a shared constant.

CONSTANT ORGANISATION (E11066-E11067)

E11066: a module's constants are spread across 3 or more files - consolidate them into one file.
E11067: a module has 10 or more constants across 2 or more files - give them a dedicated constants file.
Neither check is about constant naming.

NAMED CONSTANTS PATTERN

Define meaningful names at the top of the function or in a constants block:

  maximumRetries <- 3
  timeoutSeconds <- 30
  minimumPasswordLength <- 8

The name documents WHY this value was chosen.

See Q311 for the full quality checks catalog. See Q140 for defining constants. See Q141 for constant immutability.

Example

#!ek9
defines module qa.codequality.magic

  defines function

    <?-
      Demonstrates using named constants instead of magic literals.
      Every threshold has a meaningful name that documents its purpose.
    -?>
    classifyTemperature() as pure
      -> temperatureCelsius as Float
      <- classification as String: "moderate"

      freezingPoint <- 0.0
      coldThreshold <- 10.0
      hotThreshold <- 30.0

      if temperatureCelsius <= freezingPoint
        classification: "freezing"
      else if temperatureCelsius <= coldThreshold
        classification: "cold"
      else if temperatureCelsius >= hotThreshold
        classification: "hot"

    <?-
      Named constants make password validation self-documenting.
    -?>
    isPasswordStrong() as pure
      -> password as String
      <- rtn as Boolean: false

      minimumLength <- 8
      if length password >= minimumLength
        rtn: true

  defines program

    MagicLiteralsDemo()
      stdout <- Stdout()

      currentTemp <- 25.0
      weatherReport <- classifyTemperature(currentTemp)
      stdout.println(`Temperature ${currentTemp}C is ${weatherReport}`)

      testPassword <- "SecurePass123"
      strongEnough <- isPasswordStrong(testPassword)
      stdout.println(`Password strong: ${strongEnough}`)

Common mistakes

E11064 — A Float literal such as 30.0 in a comparison is a magic literal (only 0.0 and 1.0 are exempt). Name it hotThreshold so the reader knows what the value means. See ek9 -h E11064 for details.

Incorrect:

      if temperatureCelsius <= freezingPoint
        classification: "freezing"
      else if temperatureCelsius <= coldThreshold
        classification: "cold"
      else if temperatureCelsius >= 30.0

Correct:

      hotThreshold <- 30.0

      if temperatureCelsius <= freezingPoint
        classification: "freezing"
      else if temperatureCelsius <= coldThreshold
        classification: "cold"
      else if temperatureCelsius >= hotThreshold

E11064 — Using a raw literal 8 in a comparison is a magic literal. Name it minimumLength to document why this value was chosen. See ek9 -h E11064 for details.

Incorrect:

if length password >= 8

Correct:

minimumLength <- 8
      if length password >= minimumLength
Other ways to ask this
  • What are magic numbers in EK9?
  • Why does EK9 reject literal values in comparisons?
  • How do I fix E11064 magic literal error?

Coming from another language?

Java: SonarQube has MagicNumber rule but optional and configurable. Checkstyle MagicNumber only catches numerics, not strings. Rust: clippy has no magic number detection. Go: no magic number detection. Python: pylint has magic-value-comparison but optional. C++: no standard magic number detection. EK9: two-tier magic literal detection, mandatory compiler error: numeric literals at comparisons and ranges (E11064, except 0/1/-1/0.0/1.0), and repeated String literals (E11065, 3+ per file or 4+ per module).

Keywords: E11064, E11065, clean-code, comparison, constant, literal, magic, metric, named, number, quality, repeated, value