Boilerplate vs Complexity
Overview
Debtmap distinguishes between boilerplate code (necessary but mechanical patterns) and true complexity (business logic requiring cognitive effort). This distinction is critical for:
- Avoiding false positives in complexity analysis
- Focusing refactoring efforts on actual problems
- Understanding which high-complexity code is acceptable
- Providing actionable recommendations
This chapter explains how Debtmap identifies boilerplate patterns, why they differ from complexity, and how to interpret the analysis results.
The Distinction
What is Boilerplate?
Boilerplate code consists of repetitive, mechanical patterns that are:
- Required by language/framework - Type conversions, trait implementations, builder patterns
- Structurally necessary - Match arms for enums, error propagation, validation chains
- Low cognitive load - Pattern-based code that developers scan rather than deeply analyze
- Not actual complexity - High cyclomatic complexity but mechanistic structure
Examples:
Fromtrait implementations converting between typesDisplayformatting with exhaustive enum match arms- Builder pattern setters with validation
- Error conversion implementations
- Serialization/deserialization code
What is True Complexity?
True complexity consists of business logic that requires:
- Domain understanding - Knowledge of problem space and requirements
- Cognitive effort - Careful analysis to understand behavior
- Algorithmic decisions - Non-obvious control flow or data transformations
- Maintainability risk - Changes may introduce subtle bugs
Examples:
- Graph traversal algorithms
- Complex business rules with multiple conditions
- State machine implementations with non-trivial transitions
- Performance-critical optimizations
- Error recovery with fallback strategies
Real Example: ripgrep’s defs.rs
The ripgrep codebase provides an excellent real-world example of boilerplate vs complexity.
File: crates/printer/src/defs.rs
This file contains type conversion implementations with high cyclomatic complexity scores but minimal actual complexity:
#![allow(unused)] fn main() { impl From<HyperlinkFormat> for ColorHyperlink { fn from(format: HyperlinkFormat) -> ColorHyperlink { match format { HyperlinkFormat::Default => ColorHyperlink::default(), HyperlinkFormat::Grep => ColorHyperlink::grep(), HyperlinkFormat::GrepPlus => ColorHyperlink::grep_plus(), HyperlinkFormat::Ripgrep => ColorHyperlink::ripgrep(), HyperlinkFormat::FileNone => ColorHyperlink::file_none(), // ... 10+ more variants } } } }
Analysis:
- Cyclomatic Complexity: 15+ (one branch per enum variant)
- Cognitive Complexity: Low (simple delegation pattern)
- Boilerplate Confidence: 95% (trait implementation with mechanical structure)
Why This Matters
Without boilerplate detection, this file would be flagged as:
- High complexity debt
- Requiring refactoring
- Priority for review
With boilerplate detection, it’s correctly classified as:
- Necessary type conversion code
- Low maintenance risk
- Can be safely skipped in debt prioritization
Detection Methodology
Debtmap uses a multi-phase analysis pipeline to detect boilerplate:
Phase 1: Trait Analysis
Identifies trait implementations known to produce boilerplate:
High-confidence boilerplate traits:
From,Into- Type conversionsDisplay,Debug- FormattingDefault- Default value constructionClone,Copy- Value semanticsEq,PartialEq,Ord,PartialOrd- ComparisonsHash- Hashing implementations
Medium-confidence boilerplate traits:
Serialize,Deserialize- SerializationAsRef,AsMut,Deref,DerefMut- Reference conversions- Custom builder traits
See src/debt/boilerplate/boilerplate_traits.rs:10-58 for complete trait categorization.
Phase 2: Pattern Analysis
Analyzes code structure for boilerplate patterns:
Pattern 1: Simple Delegation
#![allow(unused)] fn main() { fn operation(&self) -> Result<T> { self.inner.operation() // Single delegation call } }
Score: 90% confidence
Pattern 2: Trivial Match Arms
#![allow(unused)] fn main() { match variant { A => handler_a(), B => handler_b(), C => handler_c(), } }
Each arm calls a single function with no additional logic. Score: 85% confidence
Pattern 3: Validation Chains
#![allow(unused)] fn main() { fn validate(&self) -> Result<()> { check_condition_1()?; check_condition_2()?; check_condition_3()?; Ok(()) } }
Sequential validation with early returns. Score: 75% confidence
Pattern 4: Builder Setters
#![allow(unused)] fn main() { pub fn with_field(mut self, value: T) -> Self { self.field = value; self } }
Simple field assignment with fluent return. Score: 95% confidence
See src/debt/boilerplate/pattern_detector.rs:18-82 for pattern detection logic.
Phase 3: Macro Analysis
Detects macro-generated code and provides recommendations:
Derivable Traits:
Debtmap suggests using #[derive(...)] when it detects manual implementations of:
Clone,Copy,Debug,DefaultEq,PartialEq,Ord,PartialOrdHash
Custom Macros: Recommends creating custom derive macros for:
- Repeated builder pattern implementations
- Repeated conversion trait implementations
- Repeated validation logic
Existing Crates: Suggests established crates for common patterns:
derive_more- Extended derive macrosthiserror- Error type boilerplatetyped-builder- Builder pattern macrosdelegate- Delegation patterns
See src/debt/boilerplate/macro_recommender.rs:9-136 for macro recommendation logic.
Common Boilerplate Patterns
Type Conversions
#![allow(unused)] fn main() { // High complexity (15+), but boilerplate impl From<ConfigFormat> for Config { fn from(format: ConfigFormat) -> Config { match format { ConfigFormat::Json => Config::json(), ConfigFormat::Yaml => Config::yaml(), ConfigFormat::Toml => Config::toml(), // ... many variants } } } }
Boilerplate Confidence: 90%+ Recommendation: Consider using a macro if pattern repeats
Error Propagation
#![allow(unused)] fn main() { // High nesting, but boilerplate pattern fn complex_operation(&self) -> Result<Output> { let step1 = self.step_one() .context("Step one failed")?; let step2 = self.step_two(&step1) .context("Step two failed")?; let step3 = self.step_three(&step2) .context("Step three failed")?; Ok(Output::new(step3)) } }
Boilerplate Confidence: 75% Recommendation: Acceptable pattern for error handling
Builder Patterns
#![allow(unused)] fn main() { // Many methods, but all boilerplate impl ConfigBuilder { pub fn with_timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } pub fn with_retries(mut self, retries: u32) -> Self { self.retries = Some(retries); self } // ... 20+ more setters } }
Boilerplate Confidence: 95%
Recommendation: Use typed-builder or similar crate
Display Formatting
#![allow(unused)] fn main() { // High complexity due to match, but boilerplate impl Display for Status { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { Status::Pending => write!(f, "pending"), Status::Running => write!(f, "running"), Status::Success => write!(f, "success"), Status::Failed(err) => write!(f, "failed: {}", err), // ... many variants } } } }
Boilerplate Confidence: 90%
Recommendation: Consider using strum or derive_more
Decision Table
Use this table to interpret boilerplate confidence scores:
| Confidence | Interpretation | Action |
|---|---|---|
| 90-100% | Definite boilerplate | Exclude from complexity prioritization; consider macro optimization |
| 70-89% | Probable boilerplate | Review pattern; likely acceptable; low refactoring priority |
| 50-69% | Mixed boilerplate/logic | Investigate; may contain hidden complexity; medium priority |
| 30-49% | Mostly real complexity | Standard complexity analysis; normal refactoring priority |
| 0-29% | True complexity | High priority; focus refactoring efforts here |
Example Classifications
Boilerplate (90%+ confidence):
#![allow(unused)] fn main() { // Simple trait delegation - skip in debt analysis impl AsRef<str> for CustomString { fn as_ref(&self) -> &str { &self.inner } } }
Mixed (50-70% confidence):
#![allow(unused)] fn main() { // Match with some logic - review case by case fn process_event(&mut self, event: Event) -> Result<()> { match event { Event::Simple => self.handle_simple(), // Boilerplate Event::Complex(data) => { // Real logic if data.priority > 10 { self.handle_urgent(data)?; } else { self.queue_normal(data)?; } self.update_metrics()?; Ok(()) } } } }
True Complexity (0-30% confidence):
#![allow(unused)] fn main() { // Business logic requiring domain knowledge fn calculate_optimal_strategy(&self, market: &Market) -> Strategy { let volatility = market.calculate_volatility(); let trend = market.detect_trend(); if volatility > self.risk_threshold { if trend.is_bullish() && self.can_hedge() { Strategy::hedged_long(self.calculate_position_size()) } else { Strategy::defensive() } } else { Strategy::momentum_based(trend, self.confidence_level()) } } }
Integration with Complexity Analysis
Boilerplate Scoring
Debtmap calculates a BoilerplateScore for each function:
#![allow(unused)] fn main() { pub struct BoilerplateScore { pub confidence: f64, // 0.0-1.0 (0% to 100%) pub primary_pattern: Pattern, // Strongest detected pattern pub contributing_patterns: Vec<Pattern>, pub macro_recommendation: Option<MacroRecommendation>, } }
Complexity Adjustment
High-confidence boilerplate reduces effective complexity:
effective_complexity = raw_complexity × (1.0 - boilerplate_confidence)
Example:
- Raw cyclomatic complexity: 15
- Boilerplate confidence: 0.90 (90%)
- Effective complexity: 15 × (1.0 - 0.90) = 1.5
This prevents boilerplate from dominating debt prioritization.
Output Display
Debtmap annotates boilerplate functions in analysis output:
src/types/conversions.rs:
├─ from (complexity: 15, boilerplate: 92%)
│ Pattern: Trait Implementation (From)
│ Recommendation: Consider #[derive(From)] via derive_more
│ Priority: Low (boilerplate)
├─ process_request (complexity: 12, boilerplate: 15%)
│ Priority: High (true complexity)
Best Practices
When to Accept Boilerplate
Accept high-complexity boilerplate when:
- Required by language - Trait implementations, type conversions
- Pattern is clear - Developers can scan quickly without deep analysis
- Covered by tests - Mechanical patterns verified by unit tests
- No simpler alternative - Refactoring would reduce clarity
Example: Exhaustive match arms for enum variants with simple delegation.
When to Refactor Boilerplate
Refactor boilerplate when:
- Pattern repeats extensively - 10+ similar implementations
- Macro alternative exists - Can use derive or custom macro
- Maintenance burden - Changes require updating many copies
- Error-prone - Manual pattern increases bug risk
Example: 50+ builder setters that could use typed-builder crate.
Configuring Thresholds
Adjust boilerplate sensitivity in .debtmap.toml:
[boilerplate_detection]
enabled = true
min_confidence_to_exclude = 0.85 # Only exclude 85%+ confidence
trait_delegation_threshold = 0.90 # Trait impl confidence
pattern_match_threshold = 0.75 # Match pattern confidence
Strict mode (minimize false negatives):
min_confidence_to_exclude = 0.95 # Very high bar for exclusion
Lenient mode (minimize false positives):
min_confidence_to_exclude = 0.70 # More aggressive exclusion
Validation and Testing
Integration Test Example
Debtmap’s test suite includes real-world boilerplate validation:
#![allow(unused)] fn main() { #[test] fn test_ripgrep_defs_boilerplate() { let code = r#" impl From<HyperlinkFormat> for ColorHyperlink { fn from(format: HyperlinkFormat) -> ColorHyperlink { match format { HyperlinkFormat::Default => ColorHyperlink::default(), // ... 15 variants } } } "#; let result = analyze_boilerplate(code); assert!(result.confidence >= 0.85, "Should detect trait boilerplate"); assert_eq!(result.primary_pattern, Pattern::TraitImplementation); } }
See tests/boilerplate_integration_test.rs for complete test cases.
Performance Overhead
Boilerplate detection adds minimal overhead:
Measurement: <5% increase in analysis time Reason: Single-pass AST analysis with cached pattern matching Optimization: Trait analysis uses fast HashMap lookups
See tests/boilerplate_performance_test.rs for benchmark details.
Troubleshooting
“Why is my code marked as boilerplate?”
Check:
- Is it a trait implementation? (From, Display, etc.)
- Does it follow a mechanical pattern?
- Are all branches simple delegations?
If incorrectly classified:
- Adjust
min_confidence_to_excludethreshold - Report false positive if confidence is very high
“My boilerplate isn’t detected”
Common causes:
- Custom logic mixed with boilerplate pattern
- Non-standard trait names
- Complex match arm logic
Solutions:
- Extract pure boilerplate into separate functions
- Use standard traits when possible
- Check confidence score - may be detected with lower confidence
“Boilerplate detection seems too aggressive”
Adjust configuration:
[boilerplate_detection]
min_confidence_to_exclude = 0.95 # Raise threshold
trait_delegation_threshold = 0.95
Related Documentation
- Complexity Metrics - Understanding cyclomatic complexity
- Configuration - Complete
.debtmap.tomlreference - Tiered Prioritization - How boilerplate affects debt ranking
Summary
Boilerplate detection is a critical feature that:
- Distinguishes mechanical patterns from true complexity
- Reduces false positives in debt analysis
- Provides actionable macro recommendations
- Integrates seamlessly with complexity scoring
- Helps teams focus on real maintainability issues
By identifying boilerplate with 85%+ confidence, Debtmap ensures that high-complexity scores reflect actual cognitive burden rather than necessary but mechanical code patterns.