Technical Debt Patterns
Technical Debt Patterns
Expert patterns for detecting, categorizing, and prioritizing technical debt in Rails applications.
Decision Tree: Debt Category Identification
What type of issue is this?
│
├─ Code structure problem?
│ ├─ Method too long (>20 lines) → Code Smell: Long Method
│ ├─ Class too large (>150 lines) → Code Smell: Large Class
│ ├─ Excessive parameter passing → Code Smell: Data Clump
│ └─ Method uses another object's data → Code Smell: Feature Envy
│
├─ Complexity issue?
│ ├─ Flog score >60 → Complexity: High
│ ├─ Cyclomatic complexity >10 → Complexity: High
│ ├─ Deep nesting (>3 levels) → Complexity: Nesting
│ └─ Too many conditionals → Complexity: Conditional
│
├─ Security concern?
│ ├─ SQL injection risk → Security: SQL Injection
│ ├─ XSS vulnerability → Security: XSS
│ ├─ Mass assignment issue → Security: Mass Assignment
│ └─ Outdated gem with CVE → Security: Dependency
│
├─ Outdated code?
│ ├─ Rails deprecation warning → Deprecation: Rails
│ ├─ Ruby version warning → Deprecation: Ruby
│ └─ Deprecated gem API → Deprecation: Gem
│
├─ Performance problem?
│ ├─ N+1 query pattern → Performance: N+1
│ ├─ Missing database index → Performance: Index
│ ├─ Memory bloat → Performance: Memory
│ └─ Slow query → Performance: Query
│
└─ Architecture violation?
├─ Fat controller → Architecture: Controller
├─ God object/model → Architecture: God Object
├─ Circular dependency → Architecture: Circular
└─ Layer violation → Architecture: Layering
NEVER Do These (Critical Anti-Patterns)
NEVER ignore security debt because "we'll fix it later":
# WRONG - Ignoring SQL injection
User.where("name = '#{params[:name]}'") # Security debt accumulates risk
# RIGHT - Fix immediately or track with Critical severity
User.where(name: params[:name])
→ Security debt has exponential risk growth. Track as Critical, not backlog.
NEVER create technical debt to "fix" technical debt:
# WRONG - Adding wrapper to hide complexity
class PaymentWrapper
def process
@legacy_payment.complex_legacy_method # Just hiding the problem
end
end
# RIGHT - Either refactor properly or track explicitly
# If time-constrained, create beads issue with clear scope
→ Debt wrappers compound into "debt squared". Refactor or track, don't hide.
NEVER disable linters globally to silence debt warnings:
# WRONG - Global disable in .rubocop.yml
Metrics/MethodLength:
Enabled: false # Hides ALL long method debt
# RIGHT - Explicit inline disable with justification
# rubocop:disable Metrics/MethodLength -- Legacy payment processor, tracked in PROJ-123
def complex_legacy_method
# ...
end
# rubocop:enable Metrics/MethodLength
→ Global disables hide debt accumulation. Use inline disables with tracking.
NEVER skip team discussion on Critical/High debt:
# WRONG - Solo decision on major debt
"I found a god object, I'll refactor it this sprint"
# RIGHT - Team alignment first
1. Document finding in debt report
2. Create beads issue with severity
3. Discuss in sprint planning
4. Get consensus on approach
→ Major refactoring affects the whole team. Collaborate before large changes.
NEVER estimate features without considering debt in affected areas:
# WRONG - Ignoring debt in estimates
"Add payment retry logic: 2 story points"
# RIGHT - Include debt impact
"Add payment retry logic: 5 story points
- 2 points: feature implementation
- 3 points: PaymentService complexity (Flog 127) requires refactoring first"
→ Debt adds hidden cost. Include remediation in feature estimates.
Code Smell Detection Patterns
Long Method (>20 lines)
Detection:
# Find methods longer than 20 lines
awk '
/^[[:space:]]*def / { start = NR; name = $2 }
/^[[:space:]]*end/ && start > 0 {
len = NR - start
if (len > 20) print FILENAME ":" name " (" len " lines)"
start = 0
}
' app/**/*.rb
Severity Thresholds:
| Lines | Severity |
|---|---|
| 20-40 | Medium |
| 40-80 | High |
| >80 | Critical |
Large Class (>150 lines)
Detection:
# Find classes larger than 150 lines
for file in app/models/*.rb app/services/*.rb; do
lines=$(wc -l < "$file" 2>/dev/null)
if [ "$lines" -gt 150 ]; then
echo "$file: $lines lines"
fi
done
Severity Thresholds:
| Lines | Severity |
|---|---|
| 150-300 | Medium |
| 300-500 | High |
| >500 | Critical |
Feature Envy
Method uses another object's data more than its own.
Detection Pattern:
# Smell: Method calls another object's methods repeatedly
def calculate_total(order)
order.items.sum(&:price) +
order.shipping_cost +
order.tax_amount -
order.discount_amount
end
# Fix: Move method to Order class
class Order
def calculate_total
items.sum(&:price) + shipping_cost + tax_amount - discount_amount
end
end
Data Clump
Same group of parameters passed together repeatedly.
Detection Pattern:
# Smell: Repeated parameter group
def create_user(name, email, phone, address)
def update_user(name, email, phone, address)
def validate_user(name, email, phone, address)
# Fix: Extract to value object
class ContactInfo
attr_reader :name, :email, :phone, :address
end
def create_user(contact_info)
def update_user(contact_info)
Complexity Metrics
Flog Score Thresholds
| Score | Rating | Action |
|---|---|---|
| < 30 | Low | No action needed |
| 30-60 | Medium | Consider refactoring |
| 60-100 | High | Plan refactoring |
| > 100 | Critical | Immediate refactoring |
Running Flog:
# Overall complexity score
flog -q -g app/
# Top 10 most complex methods
flog -q app/ | head -10
# Score for specific file
flog app/services/payment_service.rb
Cyclomatic Complexity
Measures independent execution paths through code.
| Complexity | Rating | Risk |
|---|---|---|
| 1-5 | Low | Easy to test |
| 6-10 | Medium | Moderate risk |
| 11-20 | High | Difficult to test |
| >20 | Critical | Very high risk |
Severity Scoring Framework
Calculate overall severity using weighted factors:
Severity Score = (Blast × 0.30) + (Fix × 0.20) + (Risk × 0.30) + (Age × 0.10) + (Freq × 0.10)
| Factor | Weight | 1 (Low) | 3 (Medium) | 5 (High) |
|---|---|---|---|---|
| Blast Radius | 30% | Single file | Module | System-wide |
| Fix Complexity | 20% | Trivial | Moderate | Major refactor |
| Risk Level | 30% | Cosmetic | Functional | Security/Data |
| Age | 10% | < 6 months | 6mo-2yr | > 2 years |
| Frequency | 10% | Rare path | Normal | Hot path |
Severity Categories:
- Critical: Score >= 4.0 (SLA: 1 sprint)
- High: Score >= 3.0 (SLA: 2 sprints)
- Medium: Score >= 2.0 (SLA: Quarterly)
- Low: Score < 2.0 (Opportunistic)
Quick Reference Tables
Detection Tools
| Tool | Purpose | Command |
|---|---|---|
| Flog | Complexity scoring | flog -q app/ |
| Reek | Code smell detection | reek app/ |
| Rubocop | Style + metrics | rubocop --format json |
| Brakeman | Security vulnerabilities | brakeman -q |
| bundler-audit | Gem CVEs | bundle-audit check |
| rails_best_practices | Rails anti-patterns | rails_best_practices |
Effort Estimation
| Category | Typical Effort |
|---|---|
| Long Method refactor | 2-4 hours |
| Large Class extraction | 1-3 days |
| God Object decomposition | 3-5 days |
| N+1 query fix | 1-2 hours |
| Security vulnerability | 2-8 hours |
| Deprecation update | 2-4 hours |
| Circular dependency fix | 2-5 days |
Beads Integration
# Create debt issue
bd create --type task --priority 1 \
--title "Tech Debt: [Description]" \
--description "[Details]"
# Track debt item
bd update PROJ-123 --status in_progress
# Close after remediation
bd close PROJ-123 --reason "Refactored in commit abc123"
References
For detailed patterns in each category, see:
references/code-smells.md- Long method, large class, feature envy, data clumpreferences/complexity-metrics.md- Flog, cyclomatic, cognitive complexityreferences/deprecation-tracking.md- Rails, Ruby, gem deprecationsreferences/security-debt.md- Brakeman categories, OWASP patternsreferences/performance-debt.md- N+1 queries, indexes, memoryreferences/testing-debt.md- Coverage gaps, flaky testsreferences/architecture-debt.md- God objects, circular deps, layers
Integration with Other Skills
| Skill | Integration Point |
|---|---|
code-quality-gates |
Rubocop/Sorbet findings feed into debt report |
refactoring-workflow |
Debt items become refactoring targets |
rails-conventions |
Convention violations are architecture debt |
codebase-inspection |
Inspector discovers debt during analysis |