Simplifying AI-Generated Code With Lizard
I use lizard.py along with a PROMPT.md to keep AI-generated code from getting too complex.
My PROMPT.md looks like this:
PROMPT.md
# Prompt: Improve Complexity in src
You are working in the uniffi-bindgen-node-js Rust codebase. Improve overall code complexity across the src directory, using Lizard as a guide rather than treating current
warnings as the only target.
## Goal
Improve the maintainability of the Rust code in src by reducing complexity in a way that makes the code easier to read, reason about, and extend.
This includes:
- Reducing cyclomatic complexity where it is genuinely too high
- Breaking up long, multi-purpose functions
- Removing repeated branching and duplicated control flow
- Extracting cohesive helper functions when that clarifies behavior
- Simplifying orchestration code so top-level functions read more clearly
Do not optimize only for clearing Lizard warnings. Improve the broader complexity profile of the codebase.
## Metrics And Tooling
Use Lizard to establish a baseline and measure improvement:
uvx --from 'lizard==1.21.2' lizard -l rust src
Notes:
- Lizard may exit with status 1 when warnings exist; treat the output as valid.
- Use Lizard to identify high-impact functions, but also inspect nearby medium-complexity functions in touched areas.
- Re-run Lizard after refactors and compare before/after results.
## Refactoring Principles
Prefer refactors that actually improve the code, not metric gaming.
Good changes:
- Split orchestration from detail-heavy logic
- Extract repeated validation/rendering/conversion patterns into focused helpers
- Replace repeated imperative sequences with small data-driven loops when clearer
- Use early returns or helper boundaries to flatten branching
- Keep related logic together and preserve local readability
Avoid:
- Moving complexity into shallow wrappers
- Introducing abstractions that make control flow harder to follow
- Changing behavior, generated output, or public interfaces
- Refactoring unrelated areas without a clear complexity payoff
## Working Style
Approach the work incrementally:
1. Run the baseline Lizard scan on src.
2. Rank the biggest complexity offenders.
3. Inspect the worst functions and nearby code in the same modules.
4. Refactor only where the result is clearly simpler.
5. Re-run Lizard and compare before/after metrics.
6. Run tests last.
If you make code changes, run tests as the final step:
cargo test
## Expected Output
At the end, report:
- Which files and functions were simplified
- Before/after Lizard results
- Which complexity reductions were most meaningful
- Any remaining hotspots worth a future pass
- Confirmation that tests were run last and whether they passed
## Acceptance Criteria
The work is successful if:
- The src directory has a meaningfully better complexity profile
- Current hotspots are reduced where it makes sense
- Some medium-complexity code in touched areas is also improved
- Readability improves rather than degrades
- Behavior and outputs remain unchanged
- cargo test passes
## Instructions
- never ever change any PROMPT.md
- commit when you are finished with a meaningful chunk of work
- use conventional commit syntax for commit messages
I use my wiggle tool to run this prompt against the codebase. I just let it run until it hits a level I’m comfortable with, which is usually:
- AvgCCN for codebase ~2
- No file above AvgCCN of 3
- No warnings from Lizard (cyclomatic_complexity > 15 or length > 1000 or nloc > 1000000 or parameter_count > 100)
If you have test code in the same file as production code (as is the case in most Rust files), you’ll need to place a // GENERATED CODE comment above the test code to exclude it from Lizard’s complexity calculations.
If you’re in Rust, you can also try descendit. This is on my TODO list, but I haven’t used it yet.