Why does EK9 limit inheritance depth?

← Code Quality · Ref: Q315

EK9 limits inheritance depth to prevent the fragile base class problem and deep hierarchies that become impossible to reason about.

THRESHOLDS (E11019)

Class and trait: maximum 4 levels of inheritance.
Function: maximum 3 levels.
Record: maximum 2 levels.

Records have the strictest limit because they represent data. Data types rarely need deep hierarchies. Functions have a moderate limit. Classes and traits have the most generous limit, but still capped.

THE FRAGILE BASE CLASS PROBLEM

When a class inherits through many levels, changes to any ancestor can break descendants in unexpected ways. A change to a method in level 1 cascades through levels 2, 3, 4, and beyond. The developer modifying level 1 cannot predict the impact on level 5.

JAVA AWT/SWING CAUTIONARY TALE

Java AWT and Swing have inheritance depths of 6-8 levels. Component to Container to JComponent to JPanel to custom panels. This made Swing notoriously difficult to customise and debug. Any override had to account for behaviour at every level of the hierarchy.

USE DELEGATION INSTEAD

When you hit the inheritance depth limit, use the 'by' delegation keyword. Delegation composes behaviour without creating deep hierarchies. The delegating class controls exactly which methods are forwarded.

See Q109 for composition with delegation. See Q311 for the full quality checks catalog. See Q212 for composition over inheritance.

Example

defines module qa.codequality.inheritance

  defines class

    <?-
      A shallow, well-structured hierarchy.
      Level 0: Shape (abstract base)
      Level 1: Polygon (concrete with delegation-ready design)
    -?>
    Shape as abstract
      shapeName as String: String()

      Shape()
        -> shapeName as String
        this.shapeName :=? shapeName

      name() as pure
        <- rtn as String: shapeName

      area() as pure abstract
        <- rtn as Float?

      override operator ? as pure
        <- rtn as Boolean: shapeName?

      operator $ as pure
        <- rtn as String: shapeName

    <?-
      Level 1 of inheritance.
      Keeps the hierarchy shallow and focused.
    -?>
    Rectangle is Shape
      rectangleWidth as Float: Float()
      rectangleHeight as Float: Float()

      Rectangle()
        ->
          width as Float
          height as Float
        super("Rectangle")
        this.rectangleWidth :=? width
        this.rectangleHeight :=? height

      override area() as pure
        <- rtn as Float: rectangleWidth * rectangleHeight

      override operator ? as pure
        <- rtn as Boolean: rectangleWidth? and rectangleHeight?

      override operator $ as pure
        <- rtn as String: `Rectangle(${rectangleWidth} x ${rectangleHeight})`

  defines program

    InheritanceDepthDemo()
      stdout <- Stdout()

      width <- 5.0
      height <- 3.0
      rect <- Rectangle(width, height)

      stdout.println(`Shape: ${rect}`)
      stdout.println(`Area: ${rect.area()}`)

Common mistakes

E11051 — Calling the pure area() method as a bare statement discards its Float return value, which EK9 forbids - capture or use the result. See ek9 -h E11051 for details.

Incorrect:

rect.area()

Correct:

stdout.println(`Area: ${rect.area()}`)

E08090 — Declaring a variable that is never referenced is dead code. Every variable must be used. See ek9 -h E08090 for details.

Incorrect:

unusedWidth <- 5.0
      rect <- Rectangle(5.0, 3.0)

Correct:

width <- 5.0
      height <- 3.0
      rect <- Rectangle(width, height)
Other ways to ask this
  • What is the maximum inheritance depth in EK9?
  • How does E11019 work?
  • Why can I only extend 4 levels deep in EK9?

Coming from another language?

Java: no inheritance depth limit, AWT/Swing goes 6-8 deep, frameworks encourage deep hierarchies. Python: no inheritance depth limit, multiple inheritance can create diamond depth issues. C++: no inheritance depth limit, virtual inheritance adds complexity. Rust: no inheritance at all (trait composition only). Go: no inheritance (struct embedding only). Kotlin: no depth limit but sealed classes limit hierarchy breadth. EK9: hard limits (class 4, function 3, record 2) enforced by compiler.

Keywords: hierarchy, delegation, Swing, limit, quality, inheritance, metric, AWT, class, depth, base, E11019, migrate, fragile, clean-code