Complexity limits and budget bars
StandardFeeSchedule.feeFor prices a driving test by age band and vehicle category - twelve branches, built to sit near half the compiler's complexity budget. Read service.ek9. To play it in the IDE: Help > Lessons > Quality and reports…, or ek9 -ide -lesson complexity-limits.
Start from
The code the lesson starts from: copy it into a file of your own and follow the steps.
domain.ek9
#!ek9 <?- Driving Test Centre - a candidate's age. REQUIREMENT: a candidate is old enough to book a driving test from age 17 (inclusive) up to 70 (inclusive) - the Centre does not test anyone outside that range at all. -?> defines module quality.centre.domain defines constant MINIMUM_AGE <- 17 defines type CandidateAge as Integer constrain as >= 17 and <= 70 defines function // REQUIREMENT: eligible for a test from MINIMUM_AGE (inclusive) - the Centre's own minimum. isMinimumAge() -> age as CandidateAge <- rtn as Boolean: age > MINIMUM_AGE //EOF
vehicles.ek9
#!ek9 <?- Driving Test Centre - vehicle categories, in their own file (same module as domain.ek9, which a module may span) so the Generate Edge-Case Tests… lesson can scope its whole-file run to domain.ek9 alone - just CandidateAge and isMinimumAge - without these four trivial classes. -?> defines module quality.centre.domain defines trait Vehicle category() as pure <- rtn as String? defines class Car with trait of Vehicle override category() as pure <- rtn as String: "car" default operator ? Motorcycle with trait of Vehicle override category() as pure <- rtn as String: "motorcycle" default operator ? Lorry with trait of Vehicle override category() as pure <- rtn as String: "lorry" default operator ? QuadBike with trait of Vehicle override category() as pure <- rtn as String: "quad-bike" default operator ? //EOF
scoring.ek9
#!ek9 <?- Driving Test Centre - theory test scoring, in its own file (same module as domain.ek9) so the Generate Edge-Case Tests… lesson can scope its whole-file run to domain.ek9 alone, without this function's require/division in the same scope. -?> defines module quality.centre.domain defines function // REQUIREMENT: a candidate's average theory test score across every attempt made so far - // attempts is always at least one (a candidate who has never sat the test has no average yet). averageScore() -> totalMarks as Float attempts as Float <- rtn as Float? require attempts > 0.0 rtn: totalMarks / attempts //EOF
service.ek9
#!ek9 <?- Driving Test Centre - the booking service: a fee schedule, injected into an application, used by a program. Two modules (quality.centre.service depends on quality.centre.domain) for the quality report's Module Dependencies page; AbstractFeeSchedule/StandardFeeSchedule for its Type Hierarchy and DI Wiring pages. -?> defines module quality.centre.service references quality.centre.domain::CandidateAge defines constant JUNIOR_AGE <- 25 SENIOR_AGE <- 60 defines class BookingService schedule as AbstractFeeSchedule! quote() -> age as CandidateAge category as String <- rtn as Money: schedule.feeFor(age, category) default operator ? defines component AbstractFeeSchedule as abstract feeFor() as abstract -> age as CandidateAge category as String <- rtn as Money? default operator ? // REQUIREMENT: the test fee depends on the candidate's age band and the vehicle category - // three age bands across four categories, the Centre's full published fee schedule. StandardFeeSchedule is AbstractFeeSchedule override feeFor() -> age as CandidateAge category as String <- rtn as Money: 0.00#GBP isCar <- category == "car" isMotorcycle <- category == "motorcycle" isLorry <- category == "lorry" isQuadBike <- category == "quad-bike" isJunior <- age <= JUNIOR_AGE isSenior <- age <= SENIOR_AGE if isCar and isJunior rtn: 34.00#GBP else if isCar and isSenior rtn: 40.00#GBP else if isCar rtn: 38.00#GBP else if isMotorcycle and isJunior rtn: 28.00#GBP else if isMotorcycle and isSenior rtn: 32.00#GBP else if isMotorcycle rtn: 29.00#GBP else if isLorry and isJunior rtn: 60.00#GBP else if isLorry and isSenior rtn: 65.00#GBP else if isLorry rtn: 62.00#GBP else if isQuadBike and isJunior rtn: 24.00#GBP else if isQuadBike and isSenior rtn: 27.00#GBP else if isQuadBike rtn: 26.00#GBP default operator ? defines application CentreApp register StandardFeeSchedule() as AbstractFeeSchedule defines program QuoteFee() with application of CentreApp stdout <- Stdout() service <- BookingService() candidate <- CandidateAge(30) fee <- service.quote(candidate, "car") stdout.println(`Fee: ${fee}`) //EOF
dev/tests.ek9
#!ek9 <?- Driving Test Centre - a FEW hand-written tests, deliberately leaving real gaps: isMinimumAge is only tried at one interior age (never the boundary, where the off-by-one lives), averageScore is only tried with ordinary marks (never the require/division edge), and only Car's category() is tried at all - Motorcycle, Lorry, QuadBike and the whole fee schedule are untested. Coverage, input variety and the budget bars all show what is missing. -?> defines module quality.centre.tests references quality.centre.domain::averageScore quality.centre.domain::CandidateAge quality.centre.domain::Car quality.centre.domain::isMinimumAge defines program @Test AdultIsEligible() candidate <- CandidateAge(30) assert isMinimumAge(candidate) @Test CarCategoryIsCar() car <- Car() assert car.category() == "car" @Test AverageOfTwoAttempts() assert averageScore(150.0, 2.0) == 75.0 //EOF
Step 1: Complexity badges in the editor
Choose View > Toggles > Complexity Badges (⌥⌘C).
A red circle marked C appears beside feeFor's own declaration line - CRITICAL, the editor's tightest severity. Hover it for the exact numbers: cyclomatic, cognitive and nesting depth, the same three the report names.
Step 2: Run the quality report
Run > Quality Report (Shift+Cmd+R) writes the report this lesson reads next. Choose Quality Report.
Step 3: The budget bars
On index.html, Code Quality Metrics: Complexity shows max 27, avg 2.7, 18 of 45 headroom - the bar itself is amber (warning), because 27 of 45 is 60%, over the report's own 50% line. Cognitive reads max 12 of 35 (34%) - still green on this bar, even though the SAME method read red in the editor a moment ago: the editor's own threshold (20) is far tighter than the compiler's hard limit (35).
feeFor's own module page names it directly beside the bar - quality.centre.service::StandardFeeSchedule.feeFor, the worst (and only) construct behind both numbers. 45 and 35 are not advisory: E11010 and E11021 refuse a method that crosses them outright, so the budget bar is reporting distance from a line the compiler itself enforces.
The result
That is complexity, read two ways: the editor's badge is an early warning with tight thresholds (15 cyclomatic, 20 cognitive), long before anything is close to failing; the report's bar is the compiler's own hard stop (45, 35) - amber at half, red at 80%. feeFor sits at 60% of the hard limit, with 18 points of headroom left before E11010 would refuse it outright. AI assistants read the same two numbers from ek9_query_complexity, no editor or report needed.
Where this lesson comes from
- Answers -
ek9 -qand the IDE's Answers tab: 696, 765. - For AI assistants - the MCP tool
ek9_query_complexity.
In the EK9 reference: Compile-Time Code Quality Enforcement




