The function I find most striking is compileNamedQuery in named.go: cyclomatic complexity of 44, nesting depth of 7, and 2026 days since last change. It is a long-untouched parser that the next developer will have to reason about cold, which is why I read it as long-term debt rather than an emergency. sqlx has 133 functions in this snapshot, 10 of them in the critical band, and all 39 functions in the debt quadrant are structurally complex but quiet, with no fire-quadrant functions at all. I would start with scanAll because it carries the highest activity-weighted risk score, 14.93, and sits on the main path that maps database rows onto Go structs.
The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).
Top 5 Hotspots
| Function | File | Risk | CC | ND | FO |
|---|---|---|---|---|---|
scanAll | sqlx.go | 14.9 | 40 | 3 | 30 |
compileNamedQuery | named.go | 14.5 | 44 | 7 | 9 |
In | bind.go | 13.7 | 27 | 3 | 12 |
scanAny | sqlx.go | 12.2 | 24 | 1 | 18 |
getMapping | reflectx/reflect.go | 11.7 | 37 | 4 | 19 |
133 functions analyzed
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.God Function×3God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.Long Function×3Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.Stale Complex×3Stale Complex
High structural complexity but untouched for a long time — structural debt that will bite whoever opens it next.Complex Branching×2Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.Deeply Nested×1Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
The distribution is unusual: no function lands in the fire or watch quadrants. Every risky function in this snapshot is debt, meaning complex and currently still. Across the top five, none has been touched in the last year, and the time since last change ranges from 45 days to 2730 days. I am not saying this code is poorly written. I am saying the next change will be expensive, and the risk is concentrated in a few places.
Three of the five functions are flagged as god functions and three as stale and complex. That combination is what I would watch: a long, branchy function that nobody has needed to modify in years is a function nobody currently has fresh context on.
scanAll — sqlx.go
This is the highest-ranked function in the snapshot, with an activity-weighted risk score of 14.93. It was last changed 45 days ago, the most recent change among the top five, but it has zero touches in the last year, so it belongs in the debt quadrant and not the fire quadrant. Cyclomatic complexity is 40 with a modest nesting depth of 3, and fan-out is 30, the highest in this set.
From the source excerpt, scanAll validates the destination pointer, resolves the slice element type via reflection, checks column counts, builds field traversals, and then loops over rows in two separate branches: one for struct destinations and one for scannable base types. Most of those steps have their own error return, which is why the exit-heavy pattern fires. Each early return is a distinct test case, and with 30 distinct callees, a failure could originate in many places.
The file-level history adds weight. sqlx.go shows 2 bug-linked commits out of 80 total, and a bug-fix rate of 45%. That does not prove scanAll is defective, since the signals are file-level, but a complex function in a file with that history deserves stronger review priority than the same complexity in a clean file. There are no reverts, and no distinct authors in the last 90 days.
I would extract the validation preamble (pointer-kind and nil checks, slice type resolution) into one helper, and the two row-loop branches into separate functions. The struct branch and the scannable branch share almost nothing after the column check, so splitting them should cut the cyclomatic complexity roughly in half and make each error path independently testable.
compileNamedQuery — named.go
This function has the highest cyclomatic complexity in the set at 44, and a nesting depth of 7, the deepest of the five. It hasn’t been changed in 2026 days. Its activity-weighted risk score is 14.49.
From the excerpt, it walks the query one byte at a time with a state flag for being inside a named parameter. Along the way it handles the double-colon escape, the assignment-operator case, allowed-character checks and the end-of-string edge case, then switches on bind type to emit the right placeholder style. That is a hand-rolled tokenizer and a placeholder rewriter in one body. The flagged patterns are complex branching, deep nesting and stale complexity, and they match what I read.
The file history is mixed. named.go shows no bug-linked commits and no reverts across 53 total commits, but a bug-fix rate of 36%, and review comment density is the second highest of the group. I read that as a file that has attracted attention without a recorded defect trail, so I would frame this as maintainability debt with a high blast radius when next touched.
First, I would extract the placeholder emission switch (named, question, dollar and at-style bind vars) into its own function. That removes a chunk of the nesting and the duplicated digit-appending loops. The remaining state machine is then easier to unit test per transition.
In — bind.go
In expands slice arguments into repeated placeholders for IN-style queries. It has a cyclomatic complexity of 27, a nesting depth of 3, fan-out of 12, and an activity-weighted risk score of 13.67. It hasn’t been changed in 1412 days.
The excerpt shows two distinct phases: a first pass that classifies each argument (calling the driver’s value hook where present, detecting slices, rejecting empty slices) and a second pass that scans the query for question marks and rebuilds it. Several explicit error returns, including the empty-slice case and the case where bind variables outnumber arguments, fit the exit-heavy and long-function patterns. There is also an early return when no slices are found, with a comment acknowledging that some errors won’t be caught on that path. That is a documented behavioural fork a test suite should pin down.
bind.go shows 1 bug-linked commit out of 48 total and a bug-fix rate of 23%, with no reverts. That is a lighter historical signal than sqlx.go, so I would call this primarily structural debt. The natural split follows the two phases already visible in the code: one function that builds the argument metadata, one that rewrites the query. Each would drop well below the threshold where I would want to intervene.
scanAny — sqlx.go
scanAny is the single-row counterpart to scanAll, defined on the row type. Cyclomatic complexity is 24 with a nesting depth of just 1, fan-out is 18, and the activity-weighted risk score is 12.19. It hasn’t been changed in 2730 days, the longest dormancy of the five.
The flat nesting is the notable detail: this is a long sequence of guard clauses, each with its own return, which is what the exit-heavy pattern captures. It reads more easily than scanAll, but every guard is still a path to test. Several of those guards duplicate checks in scanAll, including the pointer-kind check, the nil check and the column-count check, with slightly different error wording.
It shares sqlx.go’s file-level history: 2 bug-linked commits out of 80 and a bug-fix rate of 45%. I would pull the shared destination validation out of both scanAny and scanAll into a common helper. That removes the duplication, makes the error messages consistent, and shrinks both functions at once.
getMapping — reflectx/reflect.go
getMapping builds the field map for a struct type, and the reflectx path suggests it underpins every struct-to-column mapping in the library. Cyclomatic complexity is 37, nesting depth is 4, fan-out is 19, and the activity-weighted risk score is 11.74. It hasn’t been changed in 2149 days.
The excerpt shows a breadth-first queue over struct types, with a labelled continue to skip recursive fields, tag parsing, skip rules for ignored and unexported fields, and separate handling for anonymous embedded structs versus nested struct fields. The flagged patterns are complex branching, god function, long function and stale complexity, and I agree with all four.
The file shows no bug-linked commits and a bug-fix rate of 36% across 42 total commits, but its review comment density is 3.0, by far the highest of any file in this set. That means this file has drawn substantially more review discussion per commit than the others, which I read as a sign contributors have found it hard to change confidently. The cycle-detection loop, the per-field skip rules, and the embedded-versus-nested branches are the three separable concerns. I would extract each into its own function and cover the embedded-struct and recursive-type cases with explicit tests before touching the queue logic.
What I would do first
None of these five is a live regression risk today, since all five have zero touches in the last year. The danger is that someone without recent context modifies them under time pressure. My order: split scanAll and scanAny together around a shared validation helper, since they share a file and a defect history; then decompose compileNamedQuery, where the nesting depth makes review hardest; then getMapping, where the review-comment density suggests friction; and In last, since its two phases are already cleanly separated.
Patterns Found
Antipatterns detected across the top functions in this snapshot:
| Pattern | Occurrences |
|---|---|
exit_heavy | 3 |
god_function | 3 |
long_function | 3 |
stale_complex | 3 |
complex_branching | 2 |
deeply_nested | 1 |
These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.
See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function, stale_complex.
Reproduce This Analysis
git clone https://github.com/jmoiron/sqlx
cd sqlx
git checkout 41dac167fdad5e3fd81d66cafba0951dc6823a30
hotspots analyze . --mode snapshot --explain-patterns --force
To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.
I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →