MockingBird's training loops carry the most structural debt — 5 functions to fix first

Three of MockingBird's five highest-risk functions live in monotonic_align/core.c, a Cython-generated file, while both training loop entry points carry cyclomatic complexity above 60 — structural debt that has sat untouched for over 1,100 days.

Stephen Collins ·
Generated by hotspots · free & open source
Activity Risk17.28Low
Hottest Functiontrain

Antipatterns Detected

god_function9complex_branching6long_function6exit_heavy5deeply_nested5stale_complex5

Run this on your own codebase

Hotspots runs locally in under a minute — no account, no data leaves your machine.

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god-function and why does it matter in MockingBird?

A god-function is a single function that owns too many distinct responsibilities — it calls a large number of other functions, makes decisions that logically belong in separate components, and grows long enough that no one person can hold all its behavior in working memory at once. Fan-out, the count of distinct functions directly called, is the primary coupling signal: a fan-out above 15 is a strong warning, and above 40 it typically indicates a function that acts as a hub for an entire subsystem. In MockingBird, the `train` function in `models/vocoder/hifigan/train.py` has a fan-out of 99 — it directly invokes nearly a hundred distinct callees, from process-group initialization to dataset construction, which means a change to any one of those dependencies could require coordinated reasoning about this single function. Nine functions across the top hotspots carry the god-function pattern, making it the most common structural antipattern in the analyzed set.

How do I reduce cyclomatic complexity in Python?

The most effective technique is extract-method refactoring: identify a coherent logical phase inside a complex function — checkpoint loading, model initialization, dataset setup — and move it into a named helper with a clear return contract. A cyclomatic complexity above 15 warrants splitting; above 30, it should be treated as a blocking concern before new features are added. For the `train` function in `models/synthesizer/train.py`, which has a CC of 86, I would start by pulling the checkpoint-compatibility block — the `if weights_fpath.exists()` branch that loads and validates model state — into a standalone function such as `load_or_initialize_weights`. That extraction alone likely removes ten or more branches from the parent function and makes the compatibility logic independently testable. Replacing deeply nested conditionals with guard clauses (early returns that handle the error or edge case and exit immediately) is a complementary technique that reduces nesting depth at the same time.

Is MockingBird actively maintained?

The structural picture is mixed. All five top hotspots have zero commits in the last 30 days and were last modified 1,109 days ago — the core training infrastructure has been dormant for over three years. However, the data also shows active development in `skills/speak/scripts/`, with functions like `main` in `render_timeline.py` (1 touch in the last 30 days, changed 1 day ago) and `synthesize` in `noiz_tts.py` (1 touch in the last 30 days, changed 1 day ago) sitting in the "fire" quadrant. That suggests the project is alive and being extended in its higher-level scripting layer, while the foundational model training code has stabilized (or been set aside). Active development and high structural debt are not mutually exclusive — the training loops can be both old and risky precisely because they haven't been refactored to match the maturity the project has otherwise reached.

How do I reproduce this analysis?

The analysis was run against commit `28dc5e1` of `babysor/MockingBird` using the Hotspots CLI, available at github.com/hotspots-dev/hotspots. After checking out the repository at that commit with `git checkout 28dc5e1`, run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root. The same command works on any local git repository without additional configuration, and `--explain-patterns` is what populates the antipattern labels used throughout this post.

What does activity-weighted risk mean?

Activity-weighted risk combines a function's structural complexity — derived from cyclomatic complexity, nesting depth, and fan-out — with a signal from recent commit frequency, so functions that are both hard to understand and actively changing score the highest. A function with a cyclomatic complexity of 86 that hasn't been touched in over three years, like `train` in `models/synthesizer/train.py` (an activity-weighted risk of 17.28 and zero touches in the last 30 days), scores lower than it would if it were being changed weekly, because the near-term probability of a regression is lower when no one is modifying it. The intent is to help prioritize refactoring effort where it reduces the probability of bugs being introduced right now, not just where the code looks complicated in isolation — though as this repository illustrates, very high structural complexity in dormant code still warrants attention, because the blast radius when it is next changed is severe regardless of how long it has sat still.

The most striking finding from this analysis is the concentration of risk: three of the top five hotspots live in a single file, monotonic_align/core.c, and the remaining two are monolithic training loop functions in separate model directories. All five sit in the “debt” quadrant — zero commits in the last 30 days, untouched for 1,109 days — so the risk argument here is blast radius, not active regression. MockingBird has 1,051 analyzed functions, 155 of which score as critical; the five I focus on below represent the highest activity-weighted risk scores in the repository and collectively embody every major antipattern the analysis detected. I would start with train in models/synthesizer/train.py not because anyone is changing it right now, but because its cyclomatic complexity of 86 and fan-out of 83 mean the next person who touches it is walking into one of the most structurally perilous functions in the codebase.

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

FunctionFileRiskCCNDFO
trainmodels/synthesizer/train.py17.386683
_unellipsifymonotonic_align/core.c17.247543
__pyx_memview_slicemonotonic_align/core.c17.254537
__Pyx_ParseOptionalKeywordsmonotonic_align/core.c16.133515
trainmodels/vocoder/hifigan/train.py16.067899

Large Repo Analysis

MockingBird is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 5 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

Codemod / Tooling Files in Results

Three of the five top hotspots — _unellipsify, __pyx_memview_slice, and __Pyx_ParseOptionalKeywords — are functions inside monotonic_align/core.c. The source excerpts confirm this is Cython-generated C code: the __Pyx_ prefix, __Pyx_RefNannyDeclarations, __Pyx_INCREF/__Pyx_GOTREF macros, and the View.MemoryView comment annotations are all Cython runtime boilerplate. The high cyclomatic complexity and fan-out scores reflect the verbosity of Cython’s reference-counting and error-handling code generation, not maintainer decisions. To exclude this file from future analyses, add the following to .hotspotsrc.json: { "exclude": ["monotonic_align/core.c"] }. This will surface the real application-layer hotspots without generated code consuming three of the top five slots.

Repository triage at a glance

Quadrant distribution across 1,051 analyzed functions
Fire16Debt395Watch13OK627

1,051 functions analyzed

The quadrant picture tells a clear story: 395 functions are structural debt and only 16 are in the “fire” quadrant — actively changing and complex. The top five hotspots are all debt. That doesn’t mean they’re safe; it means the risk is deferred, not resolved.

Detected Antipatterns
God Function×9God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Complex Branching×6Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
Long Function×6Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Exit Heavy×5Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
Deeply Nested×5Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Stale Complex×5Stale Complex
High structural complexity but untouched for a long time — structural debt that will bite whoever opens it next.

God-function is the dominant pattern across the top hotspots. In Python, where duck typing means that a high fan-out count understates actual coupling — types are resolved at runtime, so each of those 83 or 99 callees could silently accept something unexpected — a god-function with broad fan-out is a particularly fragile artifact.


train — models/synthesizer/train.py

train
models/synthesizer/train.py
17.28
critical
CC 86
ND 6
FO 83
touches/30d 0
Cyclomatic Complexity 86
threshold: 30
Fan-Out 83
threshold: 15

This is the top-ranked function in the repository, with an activity-weighted risk score of 17.28. The source excerpt makes clear why: train owns the entire Tacotron training lifecycle in a single function — directory scaffolding, device detection, GPU batch-size validation, model instantiation with a full hyperparameter spread, optimizer setup, and checkpoint loading. That’s before the training loop itself begins.

A cyclomatic complexity of 86 means there are 86 independent execution paths through this function. Every path is a potential bug surface and, in practice, a required test case. The nesting depth of 6 means some of those branches are six levels deep — the compatibility check for symbol-table shape differences is a visible example, where a loaded checkpoint triggers a cascade of conditional JSON config loading or dumping inside an already-nested GPU check. The fan-out of 83 distinct callees means a change anywhere in this function’s dependency graph can have unexpected side effects here, and vice versa.

This function has 0 commits in the last 30 days and hasn’t been touched in 1,109 days. There are no bug-linked commits or reverts on the file, so there’s no historical defect signal pushing urgency — but that dormancy is itself a risk factor. When training behavior next needs to change (a new schedule, a different checkpoint format, distributed training adjustments), the developer doing that work will need to reason through 86 paths and 83 callees simultaneously.

Recommendation: Extract the setup phase — directory creation, device selection, batch-size validation, and model instantiation — into named helper functions before anyone adds new training logic. Getting the cyclomatic complexity below 20 is achievable by pulling each logical phase into its own callable. The if weights_fpath.exists() compatibility block alone is a strong candidate for extraction.


_unellipsify — monotonic_align/core.c

_unellipsify
monotonic_align/core.c
17.22
critical
CC 47
ND 5
FO 43
touches/30d 0
Cyclomatic Complexity 47
threshold: 10

Three of the five top hotspots live in monotonic_align/core.c. That concentration matters: whoever owns this file owns a significant share of the repository’s structural risk budget, even if no one has touched it in 1,109 days.

_unellipsify is a Cython-generated C function (see the vendor note below for why these score so highly). Its job, as the source makes clear, is to expand ellipsis indices in memory-view slice operations into full slice tuples — the Python equivalent of resolving arr[..., 0] into explicit per-dimension slices. The implementation iterates over index tuples, tracks ellipsis and slice presence with integer flags, and builds a result list through multiple branching paths. A cyclomatic complexity of 47 and nesting depth of 5 reflect the combinatorial logic of handling mixed index types across arbitrary dimensions, and the exit_heavy pattern flags the multiple early-return and error-exit paths throughout.

The fan-out of 43 is high for what is notionally a utility function, driven by the Cython runtime’s reference-counting macros (__Pyx_INCREF, __Pyx_GOTREF, error-handling jumps) that appear as distinct callees in static analysis.

Recommendation: This file should be excluded from routine refactoring targets — it is generated code (see vendor note). The actionable step is to add the exclusion pattern to .hotspotsrc.json so the generated C stops consuming attention in future analyses.


__pyx_memview_slice — monotonic_align/core.c

__pyx_memview_slice
monotonic_align/core.c
17.21
critical
CC 54
ND 5
FO 37
touches/30d 0
Cyclomatic Complexity 54
threshold: 10

The second function from core.c in the top five, __pyx_memview_slice implements the low-level memory-view slicing operation that _unellipsify feeds into. The source shows it managing source and destination __Pyx_memviewslice structs, iterating over each dimension to compute start/stop/step values, and handling the distinction between a base memoryview and a _memoryviewslice sub-type — with an assertion on ndim > 0 guarding entry.

At CC 54, this is the most complex function in core.c among the top five. The deeply_nested and exit_heavy patterns confirm that the dimension-iteration logic branches heavily and exits through multiple error paths — each requiring its own reference-count cleanup, which the Cython runtime enforces. With 0 touches in 30 days and 1,109 days since last change, the blast-radius risk here is identical to _unellipsify: low near-term probability of being changed, but high consequence if it is.

Recommendation: Same as _unellipsify — exclude monotonic_align/core.c from the hotspot scan. The complexity here is an artifact of Cython’s code generation, not of maintainer decisions.


__Pyx_ParseOptionalKeywords — monotonic_align/core.c

__Pyx_ParseOptionalKeywords
monotonic_align/core.c
16.09
critical
CC 33
ND 5
FO 15
touches/30d 0

The third core.c function in the top five handles keyword argument parsing for Cython-wrapped Python callables. The source shows it walking a kwds dictionary, matching each key against an array of known argument names, handling both Python 2 PyString and Python 3 PyUnicode paths through #if PY_MAJOR_VERSION < 3 preprocessor guards, and branching on duplicate-argument detection (goto arg_passed_twice) and unknown keyword types (goto invalid_keyword_type).

The cyclomatic complexity of 33 is moderate-to-high, but it is entirely a product of the dual-string-type compatibility logic and the argument-matching loop structure — both Cython boilerplate. The exit_heavy and deeply_nested patterns are consistent with a function that must handle multiple error conditions through goto-based cleanup paths, a C idiom that static analysis correctly reads as multiple independent exit paths.

Recommendation: Exclude alongside the rest of core.c. There is no maintainer action to take here.


train — models/vocoder/hifigan/train.py

train
models/vocoder/hifigan/train.py
16
critical
CC 67
ND 8
FO 99
touches/30d 0
Cyclomatic Complexity 67
threshold: 30
Fan-Out (Distinct Callees) 99
threshold: 15
Max Nesting Depth 8
threshold: 4

This is the highest fan-out function in the top five — 99 distinct callees — and the deepest nesting depth at 8. The source excerpt shows why: it is the HiFi-GAN vocoder’s complete training entry point, initializing distributed process groups, creating three model components (Generator, MultiPeriodDiscriminator, MultiScaleDiscriminator) on the target CUDA device, loading or skipping checkpoints for both generator and discriminator optimizer states, wrapping models in DistributedDataParallel conditionally on GPU count, configuring two separate optimizers and two learning-rate schedulers, and then building the dataset and dataloader pipeline — all before the epoch loop starts.

A nesting depth of 8 means there are control structures eight levels deep in this function. In Python, that typically signals that rank-conditional logic, GPU-count branching, and checkpoint-existence checks are all nested inside one another rather than composed through helper functions. The god_function and long_function patterns are the direct consequence: this function knows too much about too many things.

At 0 touches in 30 days and 1,109 days since last change, this is structural debt. But with fan-out of 99 in Python — where duck typing means those 99 callees interact through runtime-resolved interfaces — the blast radius of any future modification is severe. A change to how distributed training is initialized, or to how checkpoint filenames are constructed, ripples through a function that has no seams at which to contain the change.

Recommendation: I would extract at minimum three named helpers before any new feature work touches this function: one for checkpoint discovery and loading, one for model and optimizer setup, and one for dataset and dataloader construction. That decomposition alone would bring the cyclomatic complexity below 25 and the fan-out below 40, and it would make each phase independently testable. The nesting depth of 8 should drop naturally as the conditional logic distributes across smaller functions.


What about the active changes in the repository?

While all five top hotspots are debt, the data also surfaces the most recently active code: functions in skills/speak/scripts/ — specifically render_timeline.py and noiz_tts.py — were each touched within the last day and sit in the “fire” quadrant. The main function in render_timeline.py (activity-weighted risk of 14.47, CC 33, 1 touch in 30 days) and synthesize in noiz_tts.py (activity-weighted risk of 13.36, CC 28, 1 touch in 30 days) are actively changing with meaningful structural complexity. They don’t break into the top five because their structural scores are lower than the training loops, but they carry live regression risk precisely because changes are landing in them right now. I’d put them on the watch list for the next sprint.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
god_function9
complex_branching6
long_function6
exit_heavy5
deeply_nested5
stale_complex5

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.

Reproduce This Analysis

git clone https://github.com/babysor/MockingBird
cd MockingBird
git checkout 28dc5e14f12d7c754612af2fde8e78a4b03f8616
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 5

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 →

Related Analyses