What are common EK9 anti-patterns to avoid?

← Design Patterns and Idioms · Ref: Q267

Here are common anti-patterns in EK9 with their corrections.

1. OVERUSING ANY

Bad: Taking Any parameters everywhere loses type safety. The compiler cannot check operations at compile time and dispatchers must handle every type.
Good: Use specific types or define a trait. When you need polymorphism, traits give you a controlled contract.

2. DEEP INHERITANCE HIERARCHIES

Bad: Chains of A extends B extends C extends D create fragile, tightly coupled code. EK9 types are closed by default for this reason.
Good: Use composition. Wrap the inner type as a field and expose only the methods you need. Use trait delegation to forward methods automatically.

3. IGNORING GUARD EXPRESSIONS

Bad: Assuming a value is always set and using it directly. This leads to operating on unset values with undefined results.
Good: Always use guard expressions for Optional and Result access. The guard ensures the block only executes when the value is set.

4. EXCEPTIONS FOR EXPECTED FAILURES

Bad: Using try/catch for validation, parsing, or user input errors. Exceptions are for unexpected failures.
Good: Use Result for expected failures. Result makes the caller handle both success and error paths explicitly.

5. MUTABLE SHARED STATE

Bad: Multiple threads accessing the same mutable variable without synchronization.
Good: Use MutexLock to protect shared state. The lock ensures only one thread accesses the state at a time.

6. GIANT MONOLITHIC PROGRAMS

Bad: One massive program block with hundreds of lines doing everything.
Good: Decompose into small, focused functions. Mark functions as pure where possible for testability and compiler verification.

See Q29 for guard expressions and tri-state. See Q212 for composition over inheritance. See Q213 for mutex lock thread safety. See Q254 for when Any is appropriate. See Q259 for Result vs exceptions. See Q313 for code smell detection including god classes and data clumps.

Example

defines module qa.patterns.antipatterns

  defines trait

    // GOOD: Specific trait instead of Any
    Describable
      describe() as abstract
        <- rtn as String?

  defines class

    // GOOD: Implement the trait for type safety
    Product with trait of Describable
      productName as String?

      default private Product()

      Product()
        -> pName as String
        this.productName :=? String(pName)

      override describe()
        <- rtn as String: String(productName)

      default operator ?

    // GOOD: Composition over deep inheritance
    OrderSummary
      items as List of String: List() of String

      addItem()
        -> item as String
        items += item

      itemCount() as pure
        <- rtn as Integer: length items

      summary() as pure
        <- rtn as String: `Order with ${length items} items`

      default operator ?

  defines program

    AntiPatternsDemo()
      stdout <- Stdout()

      // === GOOD: Specific types via trait ===

      product <- Product("Widget")
      stdout.println(product.describe())

      // === GOOD: Composition wrapper ===

      order <- OrderSummary()
      order.addItem("Item A")
      order.addItem("Item B")
      order.addItem("Item C")
      stdout.println(order.summary())

      // === GOOD: Guard expression ===

      parsed <- Integer("not-a-number")
      if validNum <- parsed
        stdout.println(`Valid: ${validNum}`)
      else
        stdout.println("Invalid input handled safely")

      // === GOOD: Guarded assignment for defaults ===

      setting <- String()
      setting :=? "safe-default"
      stdout.println(`Setting: ${setting}`)

      stdout.println("Anti-patterns avoided")

Common mistakes

E50001 — Renaming the variable means later references to 'order' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.

Incorrect:

orderXYZ <- OrderSummary()

Correct:

order <- OrderSummary()

E50001 — Renaming the variable means later references to 'product' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.

Incorrect:

productXYZ <- Product("Widget")

Correct:

product <- Product("Widget")
Other ways to ask this
  • What mistakes should I avoid when writing EK9 code?
  • What are bad practices in EK9?
  • What should I not do in EK9?

Coming from another language?

Java: null everywhere, deep inheritance (AbstractFactoryBeanProcessor), checked exception abuse, mutable shared state, God classes. Python: duck typing hides type errors, mutable default arguments, global state, bare except clauses. Go: error return values ignored, interface pollution, goroutine leaks. Rust: fighting the borrow checker instead of redesigning, unwrap() everywhere, unsafe blocks. EK9: anti-patterns center on overusing Any (losing type safety), ignoring guards (operating on unset values), and inheritance over composition (fragile hierarchies).

Keywords: pattern, mistake, idiom, error, practice, design, anti-pattern, common, avoid, bad, pitfall, wrong, smell