Skip to content

Add median metrics to aggregate analysis#18

Closed
daniel-munoz wants to merge 1 commit into
mainfrom
median-metrics
Closed

Add median metrics to aggregate analysis#18
daniel-munoz wants to merge 1 commit into
mainfrom
median-metrics

Conversation

@daniel-munoz

Copy link
Copy Markdown
Owner

Summary

Adds MedianFunctionLength and MedianComplexity to AggregateMetrics, complementing the existing averages and P95 percentiles. Medians are more robust to outliers than averages for skewed distributions, giving a better picture of the typical function in a codebase.

  • New calculateMedians / calculateMedian helpers in the aggregator
  • New fields serialized as median_function_length / median_complexity
  • Unit tests for odd/even counts and empty input

🤖 Generated with Claude Code

Add MedianFunctionLength and MedianComplexity to AggregateMetrics,
complementing the existing averages and P95 percentiles. Medians are
more robust to outliers than averages for skewed distributions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
})

metrics.MedianComplexity = calculateMedian(allFunctions, func(fn *parser.FunctionMetrics) int {
return fn.Lines

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MedianComplexity is computed from fn.Lines instead of fn.Complexity — this looks like a copy-paste from the MedianFunctionLength block directly above. As written, median_complexity will always be identical to median_function_length and never reflects actual cyclomatic complexity.

metrics.MedianComplexity = calculateMedian(allFunctions, func(fn *parser.FunctionMetrics) int {
return fn.Lines
})

Suggested change
return fn.Lines
return fn.Complexity

Comment on lines +80 to +89
values := make([]int, len(functions))
for i, fn := range functions {
values[i] = getValue(fn)
}

mid := len(values) / 2
if len(values)%2 == 0 {
return (values[mid-1] + values[mid]) / 2
}
return values[mid]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calculateMedian indexes the middle element(s) of values without sorting first, so it returns an arbitrary element from the original traversal order rather than the true median. The sibling calculateP95 below (which also uses sort already imported in this file) calls sort.Ints(values) before indexing — this function is missing the equivalent call. The new tests don't catch it because their fixtures happen to already be in ascending order.

func calculateMedian(functions []*parser.FunctionMetrics, getValue func(*parser.FunctionMetrics) int) int {
values := make([]int, len(functions))
for i, fn := range functions {
values[i] = getValue(fn)
}
mid := len(values) / 2
if len(values)%2 == 0 {
return (values[mid-1] + values[mid]) / 2
}
return values[mid]
}

Suggested change
values := make([]int, len(functions))
for i, fn := range functions {
values[i] = getValue(fn)
}
mid := len(values) / 2
if len(values)%2 == 0 {
return (values[mid-1] + values[mid]) / 2
}
return values[mid]
values := make([]int, len(functions))
for i, fn := range functions {
values[i] = getValue(fn)
}
sort.Ints(values)
mid := len(values) / 2
if len(values)%2 == 0 {
return (values[mid-1] + values[mid]) / 2
}
return values[mid]

@daniel-munoz

Copy link
Copy Markdown
Owner Author

Test PR for the review workflow — both planted bugs were correctly flagged with inline comments. Closing without merging.

@daniel-munoz
daniel-munoz deleted the median-metrics branch July 26, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant