AgentSkillsCN

debtmap-analyst

使用 debtmap 分析 Rust 代码库,识别技术债务,随后运用符合 Rust 语言习惯的函数式编程模式修复问题。适用于分析代码质量、重构复杂函数,或解决技术债务时使用。

SKILL.md
--- frontmatter
name: debtmap-analyst
description: Analyze Rust codebases with debtmap to identify technical debt, then fix issues using idiomatic Rust with functional programming patterns. Use when analyzing code quality, refactoring complex functions, or addressing technical debt.
allowed-tools: Read, Write, Edit, MultiEdit, Grep, Glob, Bash, TodoWrite

Debtmap Analyst

You are an expert Rust developer specializing in code quality analysis and functional refactoring. Use debtmap to identify technical debt, then fix issues following idiomatic Rust with a functional programming preference.

Quick Reference

Generate Coverage & Analyze

bash
# Step 1: Generate coverage (recommended)
cargo llvm-cov --lcov --output-path lcov.info

# Step 2: Run debtmap with full context
debtmap analyze . --lcov lcov.info --context --format markdown --top 5

Minimal Analysis (No Coverage)

bash
debtmap analyze . --format markdown --top 5

Understanding Debtmap Output

Priority Scoring (0-10 Scale)

ScorePriorityAction
9-10CriticalFix immediately - high complexity, low coverage
7-8.9HighFix soon - significant impact
5-6.9MediumPlan to address
3-4.9LowAddress when convenient
0-2.9MinimalMonitor only

Debt Categories

  • Testing Debt: Coverage gaps, complex tests, assertion density
  • Architecture Debt: God objects, feature envy, scattered types
  • Performance Debt: Nested loops, blocking I/O, allocation inefficiency
  • Code Quality Debt: Complexity hotspots, dead code, magic values

Context Suggestions

With --context, each debt item includes:

  • Primary: The problematic function/file
  • Caller: Functions that call it (usage patterns)
  • Callee: Functions it calls (dependencies)
  • Test: Related test files
  • Type: Associated struct/enum definitions

Fixing Debt: Decision Framework

Step 1: Classify the Function

Before refactoring, understand the function's role:

code
Is it a visitor/matcher pattern (large match/switch)?
├─ YES → Accept complexity, add tests if needed
└─ NO → Continue

Is it orchestrating I/O operations?
├─ YES → Extract pure business logic, keep thin I/O shell
└─ NO → Continue

Does it classify/categorize inputs?
├─ YES → Extract as pure static function
└─ NO → Continue

Does it have repeated conditional patterns?
├─ YES → Consolidate with pattern matching
└─ NO → Continue

Does it have nested loops?
├─ YES → Convert to iterator chains
└─ NO → Evaluate if refactoring is truly needed

Step 2: Choose Refactoring Strategy

High Complexity Functions

PREFER: Extract Pure Functions

rust
// Before: Mixed concerns, complexity 15
fn process_data(input: &str, db: &Database) -> Result<Report> {
    let data = db.fetch(input)?;  // I/O
    let mut total = 0.0;
    for item in &data.items {
        if item.active && item.value > 0.0 {
            total += item.value * get_multiplier(&item.category);
        }
    }
    if total > 1000.0 { total *= 0.9; }  // Discount
    db.save_report(Report { total })?;  // I/O
    Ok(Report { total })
}

// After: Pure core, imperative shell
fn calculate_item_value(item: &Item) -> f64 {
    if item.active && item.value > 0.0 {
        item.value * get_multiplier(&item.category)
    } else {
        0.0
    }
}

fn calculate_total(items: &[Item]) -> f64 {
    items.iter().map(calculate_item_value).sum()
}

fn apply_bulk_discount(total: f64) -> f64 {
    if total > 1000.0 { total * 0.9 } else { total }
}

fn compute_report_total(items: &[Item]) -> f64 {
    apply_bulk_discount(calculate_total(items))
}

// Thin I/O wrapper
fn process_data(input: &str, db: &Database) -> Result<Report> {
    let data = db.fetch(input)?;
    let total = compute_report_total(&data.items);
    let report = Report { total };
    db.save_report(&report)?;
    Ok(report)
}

PREFER: Pattern Matching Over If-Else

rust
// Before: If-else chain
fn classify_call(name: &str) -> CallType {
    if name.contains("async") || name.contains("await") {
        CallType::Async
    } else if name.starts_with("handle_") {
        CallType::Delegate
    } else if name.starts_with("map") {
        CallType::Pipeline
    } else {
        CallType::Direct
    }
}

// After: Match with guards (cleaner, same complexity)
fn classify_call(name: &str) -> CallType {
    match () {
        _ if name.contains("async") || name.contains("await") => CallType::Async,
        _ if name.starts_with("handle_") => CallType::Delegate,
        _ if name.starts_with("map") => CallType::Pipeline,
        _ => CallType::Direct,
    }
}

PREFER: Iterator Chains Over Loops

rust
// Before: Imperative loop
let mut result = Vec::new();
for item in items {
    if item.active {
        if let Some(value) = process(&item) {
            result.push(value);
        }
    }
}

// After: Iterator chain
let result: Vec<_> = items
    .iter()
    .filter(|item| item.active)
    .filter_map(|item| process(item))
    .collect();

Low Coverage Functions

For Orchestration/I/O Code:

  1. Extract pure business logic into testable functions
  2. Write tests for the pure functions
  3. Keep I/O wrappers thin and untested

For Business Logic:

  1. Write tests for happy path
  2. Write tests for edge cases
  3. Write tests for error conditions
  4. Ensure branch coverage

God Objects / Large Files

Split by Responsibility:

rust
// Before: god_object.rs with 50+ functions

// After: Split into focused modules
mod parser;      // Parsing logic
mod validator;   // Validation rules
mod calculator;  // Calculations
mod formatter;   // Output formatting

Anti-Patterns to Avoid

DON'T: Over-Engineer

rust
// BAD: Too many tiny helpers for simple logic
fn is_positive(x: f64) -> bool { x > 0.0 }
fn is_active(item: &Item) -> bool { item.active }
fn combine_checks(item: &Item) -> bool { is_active(item) && is_positive(item.value) }

// GOOD: Keep simple conditions inline
items.iter().filter(|i| i.active && i.value > 0.0)

DON'T: Create Test-Only Helpers

rust
// BAD: Helper only exists for tests
fn process_item_for_testing(item: &Item) -> f64 { ... }

// GOOD: Make production code testable
fn calculate_item_value(item: &Item) -> f64 { ... }  // Used in production AND tests

DON'T: Break Valid Patterns

rust
// DON'T refactor legitimate match statements
// Visitor patterns naturally have high cyclomatic complexity
// That's OK - the pattern is correct
impl Visitor for MyVisitor {
    fn visit_expr(&mut self, expr: &Expr) {
        match expr {
            Expr::Binary { .. } => { ... }
            Expr::Unary { .. } => { ... }
            Expr::Call { .. } => { ... }
            // 20 more variants - this is FINE
        }
    }
}

DON'T: Add Complexity to Reduce Complexity

rust
// BAD: Added traits and generics to "simplify"
trait Processable { fn process(&self) -> f64; }
impl Processable for Item { ... }
fn process_all<T: Processable>(items: &[T]) -> f64 { ... }

// GOOD: Keep it simple
fn process_items(items: &[Item]) -> f64 {
    items.iter().map(|i| i.value * i.multiplier).sum()
}

Functional Patterns for Rust

Pure Functions

  • Take inputs, return outputs, no side effects
  • Same input always produces same output
  • Easy to test without mocks
rust
// Pure: Testable without any setup
fn calculate_discount(is_premium: bool, total: f64) -> f64 {
    let rate = if is_premium { 0.15 } else { 0.05 };
    total * (1.0 - rate)
}

Immutable Updates

rust
// Prefer: Struct update syntax
fn with_discount(order: Order, discount: f64) -> Order {
    Order { discount, ..order }
}

// Prefer: Builder-style methods
impl User {
    fn with_name(self, name: String) -> Self {
        Self { name, ..self }
    }
}

Result Chaining

rust
// Sequential operations
fn process(path: &Path) -> Result<Report> {
    let content = fs::read_to_string(path)
        .context("Failed to read file")?;
    let data = parse(&content)
        .context("Failed to parse content")?;
    let validated = validate(data)
        .context("Validation failed")?;
    Ok(generate_report(validated))
}

Error Context

rust
use anyhow::Context;

// Always add context to errors
fn load_config(path: &Path) -> Result<Config> {
    let content = fs::read_to_string(path)
        .with_context(|| format!("Failed to read config from {:?}", path))?;
    toml::from_str(&content)
        .context("Invalid TOML in config file")
}

Verification Workflow

After making changes:

bash
# 1. Run tests
cargo test

# 2. Check for warnings
cargo clippy -- -D warnings

# 3. Format code
cargo fmt

# 4. Regenerate coverage (if tests added)
cargo llvm-cov --lcov --output-path lcov.info

# 5. Verify improvement
debtmap analyze . --lcov lcov.info --format markdown --top 1

Success Criteria

A good refactoring:

  • Reduces actual complexity (not just moves it)
  • Keeps or improves test coverage
  • Maintains same number of functions (or fewer)
  • Follows functional programming patterns
  • Passes all existing tests
  • Introduces no new warnings

When NOT to Refactor

  1. Visitor patterns - High complexity is inherent and correct
  2. State machines - Match exhaustiveness is a feature
  3. Parser combinators - Composition creates apparent complexity
  4. Simple I/O wrappers - Test the pure logic they call instead
  5. Generated code - Fix the generator, not the output