Overview
Technical debt represents the implied cost of rework caused by choosing quick or limited solutions instead of better approaches that would take longer. The term, coined by Ward Cunningham in 1992, describes how suboptimal design decisions accumulate over time, requiring future effort to correct. Like financial debt, technical debt incurs "interest" in the form of increased maintenance costs, slower development velocity, and higher defect rates.
Technical debt manifests in multiple forms: code that works but violates design principles, missing documentation, inadequate test coverage, outdated dependencies, architectural shortcuts, and deferred refactoring. Not all technical debt is problematic—deliberate debt taken to meet deadlines can be acceptable if tracked and addressed systematically. The distinction between deliberate and accidental debt determines management strategy.
Organizations that fail to manage technical debt experience declining productivity, increased bug rates, and difficulty implementing new features. Studies indicate that technical debt can consume 20-40% of development capacity in mature systems. The key is not eliminating all debt—impossible and often uneconomical—but maintaining debt at sustainable levels through continuous identification and remediation.
# Example of technical debt: duplicated validation logic
class User
def valid_email?
email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
end
end
class Subscription
def valid_email?
email =~ /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
end
end
# Technical debt resolved through extraction
module EmailValidation
EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
def valid_email?(email)
email =~ EMAIL_REGEX
end
end
class User
include EmailValidation
end
class Subscription
include EmailValidation
end
Key Principles
Technical debt management operates on several fundamental principles. First, visibility: debt must be identified and tracked before it can be managed. Hidden debt accumulates until it causes system failures or blocks critical features. Second, quantification: measuring debt through metrics like code complexity, test coverage, and time-to-fix enables prioritization decisions. Third, intentionality: distinguishing deliberate debt (conscious trade-offs) from accidental debt (poor practices) guides remediation strategy.
The debt quadrant framework categorizes debt into four types. Reckless-deliberate debt occurs when teams ship code knowing it's wrong, with no plan to fix it. Prudent-deliberate debt represents conscious decisions to incur short-term costs for long-term benefits, documented and scheduled for resolution. Reckless-inadvertent debt results from ignorance or poor practices, discovered later. Prudent-inadvertent debt emerges when teams learn better approaches after initial implementation.
Interest rates vary by debt type. High-interest debt—code touched frequently or blocking new features—demands immediate attention. Low-interest debt in stable, rarely-modified code can remain indefinitely. The critical calculation compares interest costs (ongoing maintenance burden) against principal costs (effort to fix). When interest exceeds principal, immediate remediation makes economic sense.
Debt contextualization matters. A quick hack in prototype code carries different implications than the same hack in production systems. Time-to-market pressure may justify deliberate debt if repayment is scheduled. The Martin Fowler debt quadrant helps teams communicate about different debt types and their appropriate management strategies.
# High-interest technical debt: complex conditional in frequently modified code
class OrderProcessor
def process(order)
if order.status == 'pending' && order.payment_method == 'credit_card' &&
order.amount > 100 && order.user.verified? && !order.items.any?(&:out_of_stock?)
charge_credit_card(order)
elsif order.status == 'pending' && order.payment_method == 'paypal' &&
order.amount > 100 && order.user.verified?
process_paypal(order)
elsif order.status == 'pending' && order.payment_method == 'bank_transfer'
# ... more conditions
end
end
end
# Debt reduction through strategy pattern
class OrderProcessor
def process(order)
return unless order.processable?
payment_strategy(order).execute(order)
end
private
def payment_strategy(order)
case order.payment_method
when 'credit_card' then CreditCardStrategy.new
when 'paypal' then PayPalStrategy.new
when 'bank_transfer' then BankTransferStrategy.new
end
end
end
class CreditCardStrategy
def execute(order)
# Single responsibility: credit card processing
end
end
Implementation Approaches
The incremental approach addresses debt gradually through scheduled remediation sprints. Teams allocate 10-20% of each iteration to debt reduction, selecting items based on interest rates and business value. This approach prevents debt accumulation while maintaining feature velocity. The challenge involves balancing immediate feature demands against long-term system health.
Boy Scout Rule implementation reduces debt opportunistically: "Leave code cleaner than you found it." When modifying code, developers fix nearby issues—rename unclear variables, extract methods, add tests. This approach distributes debt reduction across normal development work without dedicated time allocation. The limitation: it addresses only code under active development, leaving stable but problematic code untouched.
The refactoring milestone approach dedicates entire iterations to debt reduction. Teams identify major architectural issues, schedule focused refactoring periods, and tackle systemic problems requiring coordinated changes. This suits large-scale restructuring—database migrations, framework upgrades, major architectural shifts—that cannot occur incrementally. The risk: feature development pauses, creating business pressure to cancel or abbreviate debt work.
Debt bankruptcy declares certain code beyond repair, scheduling complete rewrites. This extreme approach applies when incremental fixes exceed rewrite costs or when fundamental architectural problems prevent further evolution. Teams must justify bankruptcy through detailed cost-benefit analysis—rewrites carry high risk and often take longer than estimated.
# Incremental approach: tracking debt items
class TechnicalDebtItem
attr_accessor :description, :location, :interest_rate, :principal_cost
def initialize(description, location, interest_rate, principal_cost)
@description = description
@location = location
@interest_rate = interest_rate # hours per month
@principal_cost = principal_cost # hours to fix
end
def payback_period
principal_cost / interest_rate.to_f
end
def priority_score
# Higher score = higher priority
interest_rate / principal_cost.to_f
end
end
debt_items = [
TechnicalDebtItem.new(
"Duplicated validation logic across 5 models",
"app/models/*.rb",
8, # 8 hours/month fixing validation bugs
4 # 4 hours to extract to concern
),
TechnicalDebtItem.new(
"Missing test coverage in payment processing",
"app/services/payment_processor.rb",
12, # 12 hours/month debugging payment issues
16 # 16 hours to add comprehensive tests
)
]
# Prioritize by ROI
prioritized = debt_items.sort_by { |item| -item.priority_score }
# => [duplicated validation (score: 2.0), missing tests (score: 0.75)]
Practical Examples
Consider a Rails application with inadequate database indexing. Queries slow as data grows, consuming developer time investigating performance issues and applying ad-hoc fixes. The technical debt: missing strategic indexes on foreign keys and frequently queried columns. The interest: 2-3 hours weekly diagnosing slow queries. The principal: 8 hours analyzing query patterns and adding appropriate indexes.
# Before: missing indexes create performance debt
class CreateOrders < ActiveRecord::Migration[7.0]
def change
create_table :orders do |t|
t.integer :user_id
t.string :status
t.decimal :total
t.timestamps
end
end
end
# Queries accumulate N+1 problems
class OrdersController < ApplicationController
def index
@orders = Order.where(user_id: current_user.id)
.where(status: 'pending')
# Missing index on user_id and status causes slow queries
end
end
# After: debt paid through strategic indexing
class AddIndexesToOrders < ActiveRecord::Migration[7.0]
def change
add_index :orders, :user_id
add_index :orders, :status
add_index :orders, [:user_id, :status]
end
end
Another common example involves God Objects—classes accumulating excessive responsibilities over time. A User model handling authentication, authorization, profile management, notification preferences, and billing creates high coupling and makes changes risky. Each modification requires understanding the entire class, testing becomes complex, and multiple developers conflict when modifying the same file.
# God Object accumulating responsibilities
class User < ApplicationRecord
# Authentication
has_secure_password
def authenticate_with_token(token)
# ... authentication logic
end
# Authorization
def can_access?(resource)
# ... authorization logic
end
# Profile management
def update_profile(attributes)
# ... profile logic
end
# Notifications
def notification_preferences
# ... notification logic
end
# Billing
def process_payment(amount)
# ... payment logic
end
# Result: 500+ line class, high churn rate, frequent merge conflicts
end
# Debt reduction through service extraction
class User < ApplicationRecord
has_secure_password
has_one :profile
has_one :notification_settings
has_one :billing_account
end
class AuthenticationService
def authenticate_user(user, token)
# Focused authentication logic
end
end
class AuthorizationService
def authorize(user, resource)
# Focused authorization logic
end
end
class PaymentProcessor
def process_payment(user, amount)
# Focused payment logic
end
end
Missing error handling represents another debt category. Initial implementations often assume happy paths, deferring exception handling. As systems mature, unhandled errors cause production incidents, requiring emergency patches. The accumulated debt: multiple code paths lacking proper error handling. The interest: frequent production incidents and emergency fixes. The principal: systematic error handling implementation.
# Technical debt: missing error handling
class ApiClient
def fetch_data(endpoint)
response = HTTP.get(endpoint)
JSON.parse(response.body)
end
end
# Production failures from network issues, invalid JSON, timeouts
# Each incident requires investigation and hotfix
# Debt remediation: comprehensive error handling
class ApiClient
class NetworkError < StandardError; end
class InvalidResponseError < StandardError; end
class TimeoutError < StandardError; end
def fetch_data(endpoint)
response = HTTP.timeout(5).get(endpoint)
raise NetworkError, "HTTP #{response.code}" unless response.success?
JSON.parse(response.body)
rescue HTTP::TimeoutError
raise TimeoutError, "Request timeout after 5 seconds"
rescue JSON::ParserError => e
raise InvalidResponseError, "Invalid JSON: #{e.message}"
rescue HTTP::ConnectionError => e
raise NetworkError, "Connection failed: #{e.message}"
end
end
# Usage with error handling
begin
data = client.fetch_data('/api/users')
rescue ApiClient::TimeoutError
# Handle timeout specifically
log_error("API timeout")
fallback_response
rescue ApiClient::NetworkError
# Handle network issues
log_error("Network error")
cached_response
rescue ApiClient::InvalidResponseError
# Handle parsing errors
log_error("Invalid response")
default_response
end
Common Patterns
The Strangler Fig pattern gradually replaces legacy systems by incrementally routing traffic to new implementations. Teams build new functionality alongside old code, migrate behavior piece by piece, and eventually remove legacy components. This pattern manages large-scale architectural debt without system rewrites. Implementation requires clear interfaces between old and new systems, feature flags for routing decisions, and careful migration planning.
# Strangler Fig: incrementally replacing legacy payment system
class PaymentGateway
def initialize
@legacy_gateway = LegacyPaymentSystem.new
@new_gateway = ModernPaymentService.new
end
def process_payment(order)
if use_new_system?(order)
@new_gateway.process(order)
else
@legacy_gateway.process(order)
end
end
private
def use_new_system?(order)
# Gradual rollout through feature flags
return true if order.user.beta_tester?
return true if order.amount < 1000
# Percentage-based rollout
Flipper.enabled?(:new_payment_system, order.user)
end
end
# Over time, increase rollout percentage until legacy system removed
The Parallel Change pattern (also called Expand-Contract) manages interface changes through three phases: expand the interface to support both old and new usage, migrate callers to the new interface, contract by removing old interface support. This pattern eliminates big-bang migrations and reduces risk.
# Parallel Change: renaming method without breaking existing code
class Order
# Phase 1: Expand - add new method, keep old one
def shipping_cost
# deprecated
calculate_shipping_cost
end
def calculate_shipping_cost
# new implementation
base_cost + weight_surcharge + distance_surcharge
end
# Phase 2: Migrate - update all callers
# (happens over multiple PRs)
# Phase 3: Contract - remove deprecated method
# def shipping_cost
# # removed after migration complete
# end
end
# Add deprecation warning during migration
def shipping_cost
warn "[DEPRECATED] Order#shipping_cost is deprecated, use calculate_shipping_cost"
calculate_shipping_cost
end
The Branch by Abstraction pattern isolates large changes behind abstractions, enabling incremental migration without breaking existing functionality. Teams create abstraction layers, implement new behavior behind the abstraction, gradually migrate callers, then remove the abstraction once migration completes.
# Branch by Abstraction: replacing ORM
class RepositoryFactory
def self.create_user_repository
if ENV['USE_NEW_ORM']
NewOrmUserRepository.new
else
ActiveRecordUserRepository.new
end
end
end
class ActiveRecordUserRepository
def find(id)
User.find(id)
end
def save(user)
user.save!
end
end
class NewOrmUserRepository
def find(id)
NewOrm::User.find_by_id(id)
end
def save(user)
NewOrm::User.persist(user)
end
end
# Application code uses abstraction
class UserService
def initialize
@repository = RepositoryFactory.create_user_repository
end
def get_user(id)
@repository.find(id)
end
end
The Characterization Test pattern creates tests for legacy code before refactoring. These tests document current behavior (even buggy behavior) to detect unintended changes during refactoring. Once refactoring completes, teams update tests to reflect correct behavior.
# Characterization tests for legacy code
RSpec.describe LegacyOrderCalculator do
# Document current behavior before refactoring
it "calculates total with existing logic" do
order = Order.new(subtotal: 100, tax_rate: 0.08)
calculator = LegacyOrderCalculator.new
# Testing actual current behavior, even if wrong
expect(calculator.total(order)).to eq(108.00)
end
it "handles edge case with zero tax rate" do
order = Order.new(subtotal: 100, tax_rate: 0)
calculator = LegacyOrderCalculator.new
# Document bug: returns nil instead of subtotal
expect(calculator.total(order)).to be_nil
end
end
# After refactoring, update tests to correct behavior
RSpec.describe RefactoredOrderCalculator do
it "calculates total correctly" do
order = Order.new(subtotal: 100, tax_rate: 0.08)
calculator = RefactoredOrderCalculator.new
expect(calculator.total(order)).to eq(108.00)
end
it "handles zero tax rate correctly" do
order = Order.new(subtotal: 100, tax_rate: 0)
calculator = RefactoredOrderCalculator.new
# Bug fixed: now returns subtotal
expect(calculator.total(order)).to eq(100.00)
end
end
Tools & Ecosystem
RuboCop provides automated code quality enforcement through configurable cops (rules). It detects style violations, code smells, and potential bugs. Teams configure RuboCop to match their standards, run it in CI pipelines, and gradually enable stricter rules as debt decreases. RuboCop generates metrics reports showing violation trends over time.
# .rubocop.yml configuration
AllCops:
NewCops: enable
TargetRubyVersion: 3.2
Metrics/MethodLength:
Max: 15
Exclude:
- 'db/migrate/*'
Metrics/ClassLength:
Max: 150
Style/Documentation:
Enabled: false # Disabled temporarily, track as debt
# Run RuboCop with auto-correction
# rubocop --auto-correct
# Generate offense count report
# rubocop --format offenses
Reek detects code smells—structural patterns indicating deeper problems. It identifies long parameter lists, feature envy, data clumps, and other design issues. Reek configurations exclude specific detectors or paths, enabling gradual improvement without blocking development.
# .reek.yml configuration
detectors:
TooManyStatements:
max_statements: 8
UtilityFunction:
enabled: false
FeatureEnvy:
enabled: true
DataClump:
max_copies: 2
exclude_paths:
- 'db/migrate'
- 'spec'
# Run Reek on specific files
# reek app/models/user.rb
# Output:
# app/models/user.rb -- 3 warnings:
# [8]:FeatureEnvy: User#format_name refers to params more than self
# [15]:TooManyStatements: User#process_order has 12 statements
# [25]:DataClump: User takes parameters [first_name, last_name, email]
Flay detects structural code duplication beyond simple copy-paste. It identifies similar code patterns with different variable names or slight variations. Flay reports help prioritize extraction opportunities.
# Run Flay to detect duplication
# flay app/services/*.rb
# Output:
# Total score (lower is better): 156
#
# 1) Similar code found in:
# app/services/order_processor.rb:23
# app/services/subscription_processor.rb:31
# Nodes: 15
#
# Duplicated code:
# def validate_payment_method
# return false unless payment_method.present?
# return false unless VALID_METHODS.include?(payment_method)
# true
# end
Code Climate and similar platforms aggregate metrics from multiple tools, tracking technical debt over time. They assign letter grades (A-F) to files based on complexity, duplication, and test coverage. Teams set quality gates blocking merges that increase debt beyond thresholds.
SimpleCov measures test coverage, identifying untested code paths. Coverage reports highlight files and methods lacking tests, helping prioritize test writing efforts.
# SimpleCov configuration in spec/spec_helper.rb
require 'simplecov'
SimpleCov.start 'rails' do
minimum_coverage 80
maximum_coverage_drop 5
add_filter '/spec/'
add_filter '/config/'
add_group 'Services', 'app/services'
add_group 'Models', 'app/models'
add_group 'Controllers', 'app/controllers'
end
# Generate coverage report
# rspec
# open coverage/index.html
Rails Best Practices scans Rails applications for common mistakes and anti-patterns. It detects N+1 queries, missing database indexes, law of Demeter violations, and other Rails-specific issues.
# Install and run rails_best_practices
gem install rails_best_practices
rails_best_practices .
# Output:
# app/controllers/orders_controller.rb:23 - use includes to avoid N+1 queries
# app/models/user.rb:45 - law of Demeter violation
# db/schema.rb:89 - missing index on foreign key: orders.user_id
Brakeman scans Rails applications for security vulnerabilities—SQL injection, XSS, command injection, and other issues. It runs in CI pipelines, blocking merges introducing security debt.
# Run Brakeman security scanner
brakeman --confidence-level 2
# Output:
# Confidence: High
# Category: SQL Injection
# File: app/controllers/users_controller.rb
# Line: 45
# Code: User.where("email = '#{params[:email]}'")
# Message: Possible SQL injection
Common Pitfalls
Organizations often fail to track technical debt systematically, relying on tribal knowledge and developer memory. Without written debt inventories, teams duplicate effort investigating the same issues repeatedly and fail to prioritize high-interest debt. The solution: maintain debt registers tracking location, description, interest rate, and principal cost.
# Anti-pattern: tribal knowledge of debt
# (Scattered TODO comments, undocumented issues)
# Better: structured debt tracking
class TechnicalDebtRegister
class DebtItem
attr_accessor :id, :title, :location, :type, :impact,
:interest_rate, :principal_cost, :created_at
end
def self.load_from_yaml
YAML.load_file('technical_debt.yml').map do |item|
DebtItem.new(item)
end
end
end
# technical_debt.yml
# - id: TD-001
# title: "Missing indexes on orders table"
# location: "db/schema.rb"
# type: "performance"
# impact: "high"
# interest_rate: 8
# principal_cost: 4
# created_at: 2024-01-15
Teams frequently treat all technical debt as equally urgent, attempting to fix everything simultaneously. This disperses effort across low-value changes while high-interest debt continues accruing costs. The solution: calculate debt priority scores based on interest rates, principal costs, and business impact. Address high-priority items first, accepting low-interest debt when economically justified.
Another common mistake involves excessive refactoring—fixing code that works adequately. Developers sometimes pursue perfect code structure when the existing implementation performs acceptably and requires infrequent modification. The Rule of Three suggests tolerating duplication until patterns emerge in three or more places before extracting abstractions. Premature abstraction creates unnecessary complexity.
# Over-abstraction: creating complex structure for simple cases
class EmailSender
def initialize(strategy_factory)
@strategy_factory = strategy_factory
end
def send(email)
strategy = @strategy_factory.create(email.type)
strategy.deliver(email)
end
end
class EmailStrategyFactory
def create(type)
STRATEGIES[type].new
end
end
# Simpler sufficient solution for two email types
class EmailSender
def send(email)
case email.type
when :transactional
TransactionalMailer.deliver(email)
when :marketing
MarketingMailer.deliver(email)
end
end
end
# Wait for third type before abstracting
Teams often address symptoms rather than root causes. A slow query prompts adding caching without investigating why the query performs poorly. Missing error handling leads to generic try-catch blocks instead of proper error propagation. Treating symptoms creates more debt through workarounds accumulating complexity.
The big rewrite fallacy tempts teams facing substantial debt. Developers argue existing code is unmaintainable and complete rewrites offer the only solution. Reality shows rewrites typically take 2-3 times longer than estimated, recreate bugs from the original system, and introduce new problems. The Netscape rewrite story stands as a cautionary tale. Incremental refactoring succeeds more reliably than ground-up rebuilds.
Delayed debt payment magnifies costs through compound interest. Code initially requiring 4 hours to refactor becomes coupled with other components, requiring 20 hours after six months. Teams justifying "we'll fix it later" discover later never arrives or costs exceed earlier estimates. The Boy Scout Rule—leave code cleaner than found—prevents debt accumulation through continuous small improvements.
Organizations sometimes institutionalize debt through poor processes. Code reviews that don't check for debt patterns, CI pipelines lacking quality gates, sprint planning that never allocates debt remediation time—these systemic issues perpetuate debt accumulation. The solution requires process changes: debt discussions in sprint planning, quality metrics in CI, and code review checklists including debt detection.
# Institutionalized debt: processes missing quality checks
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run tests
run: bundle exec rspec
# Missing: linting, security scanning, coverage checks
# Improved CI with quality gates
name: CI
on: [push]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: bundle install
- name: Run RuboCop
run: bundle exec rubocop
- name: Run Reek
run: bundle exec reek
- name: Security scan
run: bundle exec brakeman
- name: Check test coverage
run: bundle exec rspec
- name: Verify coverage threshold
run: |
coverage=$(cat coverage/.last_run.json | jq '.result.line')
if (( $(echo "$coverage < 80" | bc -l) )); then
echo "Coverage $coverage% below threshold 80%"
exit 1
fi
Reference
Technical Debt Categories
| Category | Description | Example | Priority Metric |
|---|---|---|---|
| Code Debt | Poorly structured or duplicated code | Copy-pasted validation logic | Modification frequency × complexity |
| Design Debt | Architectural shortcuts or violations | God objects, tight coupling | Number of dependents × change rate |
| Documentation Debt | Missing or outdated documentation | Undocumented APIs | Support time spent × question frequency |
| Test Debt | Inadequate or missing tests | Untested payment processing | Bug rate × debugging time |
| Infrastructure Debt | Outdated dependencies or tooling | Unsupported library versions | Security vulnerabilities + maintenance burden |
| Build Debt | Slow or unreliable build processes | 30-minute test suite | Developer wait time × frequency |
Debt Measurement Metrics
| Metric | Description | Calculation | Target Range |
|---|---|---|---|
| Code Complexity | Cyclomatic complexity of methods | Branches + decisions + 1 | 1-10 per method |
| Code Duplication | Percentage of duplicated code | (Duplicated lines / Total lines) × 100 | Below 5% |
| Test Coverage | Percentage of code executed by tests | (Covered lines / Total lines) × 100 | Above 80% |
| Technical Debt Ratio | Remediation cost vs development cost | (Fix time / Development time) × 100 | Below 5% |
| Debt Interest Rate | Monthly cost of maintaining debt | Hours spent on debt-related issues | Varies by debt item |
| Debt Principal | Cost to eliminate debt | Hours required to refactor properly | Varies by debt item |
Prioritization Framework
| Priority Level | Interest Rate | Principal Cost | Action |
|---|---|---|---|
| Critical | High (>8 hrs/month) | Low (<8 hrs) | Fix immediately |
| High | High (>8 hrs/month) | Medium (8-40 hrs) | Schedule within sprint |
| Medium | Medium (2-8 hrs/month) | Medium (8-40 hrs) | Plan for next quarter |
| Low | Low (<2 hrs/month) | Any | Accept indefinitely |
| Technical Bankruptcy | Any | Very High (>200 hrs) | Consider rewrite |
Ruby Static Analysis Tools
| Tool | Purpose | Configuration File | Key Features |
|---|---|---|---|
| RuboCop | Style and code quality | .rubocop.yml | Auto-correction, custom cops, CI integration |
| Reek | Code smell detection | .reek.yml | Configurable detectors, smell catalog |
| Flay | Structural duplication | Command-line options | AST-based comparison, similarity scoring |
| Brakeman | Security vulnerability scanning | config/brakeman.yml | Rails-specific checks, confidence levels |
| SimpleCov | Test coverage measurement | spec/spec_helper.rb | Coverage thresholds, exclusion patterns |
| Rails Best Practices | Rails anti-patterns | config.yaml | N+1 detection, Law of Demeter |
| Fasterer | Performance optimization | .fasterer.yml | Idiomatic speed improvements |
| Debride | Dead code detection | Command-line options | Unused method identification |
Debt Remediation Patterns
| Pattern | Use Case | Advantages | Disadvantages |
|---|---|---|---|
| Strangler Fig | Replacing legacy systems | Low risk, incremental progress | Longer total time, temporary complexity |
| Parallel Change | Interface modifications | No breaking changes | Requires three-phase coordination |
| Branch by Abstraction | Large-scale replacements | Isolates changes, reversible | Additional abstraction layer |
| Characterization Tests | Legacy code refactoring | Safety net for changes | Tests wrong behavior initially |
| Extract Service | Breaking up God Objects | Clear boundaries, single responsibility | Network calls, distributed complexity |
Common Code Smells
| Smell | Indicator | Example Location | Refactoring |
|---|---|---|---|
| Long Method | Method > 15 lines | Controllers, service objects | Extract Method |
| Large Class | Class > 150 lines | Models with multiple concerns | Extract Class, Extract Module |
| Long Parameter List | Method > 3 parameters | Constructor, complex methods | Parameter Object, Builder |
| Duplicated Code | Similar blocks in multiple places | Validation, formatting | Extract Method, Strategy Pattern |
| Feature Envy | Method uses another object's data | Data access crossing boundaries | Move Method |
| Data Clumps | Same group of parameters repeated | Multiple methods with same params | Parameter Object |
| Primitive Obsession | Using primitives instead of objects | Status strings, money as floats | Value Object |
| Switch Statements | Complex conditionals on type | Type-based branching | Polymorphism, Strategy |
Debt Tracking Template
| Field | Description | Example Value |
|---|---|---|
| ID | Unique identifier | TD-042 |
| Title | Brief description | Missing indexes on orders table |
| Location | File or component path | db/schema.rb, app/models/order.rb |
| Type | Debt category | Performance |
| Impact | Business impact level | High - affects checkout speed |
| Interest Rate | Monthly cost in hours | 8 hours/month investigating slow queries |
| Principal Cost | Hours to fix | 4 hours to add indexes |
| Priority Score | Interest / Principal | 2.0 (high priority) |
| Created Date | When debt identified | 2024-01-15 |
| Owner | Team or person responsible | Backend Team |
| Status | Current state | Scheduled for Sprint 12 |
Quality Gate Thresholds
| Gate | Metric | Threshold | Action on Failure |
|---|---|---|---|
| Code Coverage | Line coverage percentage | Minimum 80% | Block merge |
| Complexity | Method cyclomatic complexity | Maximum 10 | Require refactoring |
| Duplication | Duplicate code percentage | Maximum 5% | Request consolidation |
| Security | Brakeman warnings | Zero high-confidence issues | Block deployment |
| Performance | Test suite duration | Maximum 10 minutes | Investigate slow tests |
| Dependencies | Outdated gems | Zero with security issues | Update before merge |