CrackedRuby CrackedRuby

Technical Debt Management

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