Why must comparison values use named constants instead of bare literals?

← Code Quality · Ref: Q695

EK9 detects 'magic literals' in comparisons and requires them to be extracted to named constants. This makes code self-documenting and maintainable.

MAGIC LITERAL DETECTION (E11064)

Bare numeric or string literals in comparisons are flagged:

  if temperature > 100.0        // E11064: what does 100.0 mean?
  if temperature > BOILING_POINT // Correct: named constant

CONSTANT DECLARATION

Use 'defines constant' to declare named values:

  defines constant
    BOILING_POINT <- 100.0
    FREEZING_POINT <- 0.0
    MAX_RETRIES <- 3

WHAT TRIGGERS E11064

Literals in comparison expressions (==, <>, <, >, <=, >=) are checked. Literals in declarations and assignments are not flagged.

BENEFITS

1. Self-documenting: BOILING_POINT vs 100.0
2. Single source of truth: change one constant, all comparisons update
3. Searchable: find all uses of BOILING_POINT

See Q310 for code quality overview. See Q313 for coding standards. See Q694 for named arguments.

Example

defines module qa.codequality.namedconstants

  defines constant

    //Named constants make comparisons self-documenting
    FREEZING_CELSIUS <- 0.0
    BOILING_CELSIUS <- 100.0
    ADULT_THRESHOLD <- 18
    RETIREMENT_THRESHOLD <- 65
    MAX_PASSWORD_LENGTH <- 128
    MIN_PASSWORD_LENGTH <- 8

  defines function

    <?-
      Correct: comparisons use named constants.
      Every threshold value has a meaningful name.
    -?>
    classifyWater() as pure
      -> temperatureCelsius as Float
      <- phase as String: "liquid"

      if temperatureCelsius <= FREEZING_CELSIUS
        phase: "solid"
      else if temperatureCelsius >= BOILING_CELSIUS
        phase: "gas"

    <?-
      Correct: age thresholds as named constants.
    -?>
    ageCategory() as pure
      -> personAge as Integer
      <- category as String: "adult"

      if personAge < ADULT_THRESHOLD
        category: "minor"
      else if personAge >= RETIREMENT_THRESHOLD
        category: "senior"

    <?-
      Correct: password length limits as named constants.
    -?>
    validatePasswordLength() as pure
      -> passwordText as String
      <- isValid as Boolean: false

      passwordLength <- length passwordText
      isValid: passwordLength >= MIN_PASSWORD_LENGTH and passwordLength <= MAX_PASSWORD_LENGTH

  defines program

    NamedConstantsDemo()
      stdout <- Stdout()

      stdout.println(classifyWater(-5.0))
      stdout.println(classifyWater(50.0))
      stdout.println(classifyWater(105.0))

      stdout.println(ageCategory(15))
      stdout.println(ageCategory(30))
      stdout.println(ageCategory(70))

      stdout.println(`Valid password: ${validatePasswordLength("secureP1")}`)
      stdout.println(`Too short: ${validatePasswordLength("abc")}`)

Common mistakes

E50060 — String has no toUpperCase() method in EK9. Use upperCase() instead. See ek9 -h E50060 for details.

Incorrect:

stdout.println(classifyWater(-5.0).toUpperCase())

Correct:

stdout.println(classifyWater(-5.0))
Other ways to ask this
  • What is E11064 magic literal in comparison?
  • How do I avoid magic numbers in EK9?
  • Must I extract all literals to constants?
  • What triggers the magic literal check?

Coming from another language?

Java: no magic literal enforcement (only static analysis tools). Python: no enforcement. C++: no enforcement. Rust: Clippy warns about magic numbers. Go: no enforcement. EK9: compile error for magic literals in comparisons.

Keywords: named, migrate, literal, E11064, comparison, self-documenting, clean-code, metric, magic, constant, quality