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 |
More from kaakati/rails-enterprise-dev
flutter conventions & best practices
Dart 3.x and Flutter 3.x conventions, naming patterns, code organization, null safety, and async/await best practices
55getx state management patterns
GetX controllers, reactive state, dependency injection, bindings, navigation, and best practices
52tailadmin ui patterns
TailAdmin dashboard UI framework patterns and Tailwind CSS classes. ALWAYS use this skill when: (1) Building any dashboard or admin panel interface, (2) Creating data tables, cards, charts, or metrics displays, (3) Implementing forms, buttons, alerts, or modals, (4) Building navigation (sidebar, header, breadcrumbs), (5) Any UI work that should follow TailAdmin design. This skill REQUIRES fetching from the official GitHub repository to ensure accurate class usage - NEVER invent classes.
39mvvm-architecture
Expert MVVM decisions for iOS/tvOS: choosing between ViewModel patterns (state enum vs published properties vs Combine), service layer boundaries, dependency injection strategies, and testing approaches. Use when designing ViewModel architecture, debugging data flow issues, or deciding where business logic belongs. Trigger keywords: MVVM, ViewModel, ObservableObject, @StateObject, service layer, dependency injection, unit test, mock, architecture
36rails localization (i18n) - english & arabic
Comprehensive internationalization skill for Ruby on Rails applications with proper English and Arabic translations, RTL support, pluralization rules, date/time formatting, and culturally appropriate content adaptation.
34rspec testing patterns
Complete guide to testing Ruby on Rails applications with RSpec. Use this skill when writing unit tests, integration tests, system tests, or when setting up test infrastructure including factories, shared examples, and mocking strategies.
31