CrackedRuby logo

CrackedRuby

Reek

A comprehensive guide to using Reek, Ruby's code smell detector, for static code analysis and quality improvement.

Testing and Quality Code Quality Tools
8.3.2

Overview

Reek examines Ruby code and detects code smells—indicators of poor design and maintenance problems. The tool analyzes source files, classes, methods, and modules to identify patterns that suggest refactoring opportunities. Reek operates as both a command-line utility and a Ruby library, scanning code for over 20 different smell types including feature envy, data clumps, long parameter lists, and nested iterators.

The core functionality centers around the Reek::Source class which parses Ruby code, and Reek::Examiner which applies detection rules. Smell detectors inherit from Reek::SmellDetector and implement pattern recognition for specific code quality issues. Each detector produces Reek::SmellWarning objects containing details about detected problems.

require 'reek'

# Analyze a string of code
source = Reek::Source.from_s(<<~RUBY)
  class Customer
    def initialize(name, email, phone, address)
      @name = name
      @email = email  
      @phone = phone
      @address = address
    end
  end
RUBY

examiner = Reek::Examiner.new(source)
examiner.smells.each do |smell|
  puts "#{smell.smell_type}: #{smell.message}"
end
# => LongParameterList: Customer#initialize has 4 parameters

Reek integrates with development workflows through configuration files, rake tasks, and continuous integration systems. The configuration system uses YAML files to customize detection rules, disable specific smells, and exclude code patterns from analysis.

# Analyze a file directly
examiner = Reek::Examiner.new(Reek::Source.from_file('app/models/user.rb'))
puts examiner.smells.map(&:message)

The tool supports multiple output formats including text, JSON, XML, and custom formatters. Reek maintains compatibility with Ruby static analysis ecosystems and provides programmatic access to smell detection results for custom tooling and reporting systems.

Basic Usage

Installing Reek requires adding the gem to your project and running basic analysis commands. The command-line interface provides immediate feedback on code quality issues across entire codebases or specific files.

gem install reek
# or add to Gemfile: gem 'reek'

The primary command reek accepts file paths, directories, or reads from STDIN. Running reek without arguments analyzes all Ruby files in the current directory structure.

# Analyze specific files
system("reek app/models/user.rb app/controllers/")

# Analyze with specific format
system("reek --format json app/")

# Check exit status
result = system("reek lib/")
puts "Analysis passed" if result

Programmatic analysis through the Ruby API provides finer control over smell detection and result processing. The Reek::Examiner class serves as the primary interface for code analysis.

require 'reek'

code = <<~RUBY
  class OrderProcessor
    def process(order)
      if order.valid?
        if order.payment_method == 'credit_card'
          charge_credit_card(order)
        elsif order.payment_method == 'paypal'
          charge_paypal(order)
        elsif order.payment_method == 'bank_transfer'
          charge_bank(order)
        end
        send_confirmation(order)
        update_inventory(order)
      else
        log_error(order.errors)
      end
    end
  end
RUBY

source = Reek::Source.from_s(code)
examiner = Reek::Examiner.new(source)

examiner.smells.each do |smell|
  puts "File: #{smell.source}"
  puts "Line: #{smell.lines.first}"
  puts "Smell: #{smell.smell_type}"
  puts "Method: #{smell.context}"
  puts "Message: #{smell.message}"
  puts "---"
end

Configuration files control which smells Reek detects and how strictly rules apply. The .reek.yml file in the project root defines global settings, while inline comments provide method-level control.

# .reek.yml
detectors:
  LongParameterList:
    max_params: 3
  NestedIterators:
    enabled: false
  UtilityFunction:
    exclude:
      - 'MyClass#helper_method'

Inline configuration uses magic comments to disable specific smells for individual methods or classes.

class DataProcessor
  # :reek:LongMethod
  def complex_calculation(a, b, c, d)
    # Method implementation with many lines
    result = a * b
    result += c if c > 0
    result -= d if d < 0
    result *= 1.1 if weekend?
    result
  end
  
  # :reek:UtilityFunction
  def self.format_currency(amount)
    "$#{amount.round(2)}"
  end
end

The rake integration provides convenient access to Reek analysis within Ruby build systems and continuous integration pipelines.

# Rakefile
require 'reek/rake/task'

Reek::Rake::Task.new do |t|
  t.source_files = 'lib/**/*.rb'
  t.config_file = '.reek.yml'
  t.fail_on_error = true
end

Advanced Usage

Reek supports extensive customization through detector configuration, custom smell patterns, and integration with complex development workflows. Advanced configuration enables fine-tuning smell detection thresholds and excluding specific code patterns from analysis.

The configuration system provides granular control over individual smell detectors. Each detector accepts specific parameters that modify detection behavior and sensitivity levels.

# Advanced .reek.yml configuration
detectors:
  LongMethod:
    max_statements: 15
    exclude:
      - 'DatabaseMigration#up'
      - 'DatabaseMigration#down'
  
  FeatureEnvy:
    enabled: true
    exclude:
      - 'ActiveRecord::Base#'  # Exclude all AR methods
  
  DataClump:
    max_copies: 3
    min_clump_size: 3
    
  DuplicateMethodCall:
    max_calls: 2
    allow_calls:
      - 'new'
      - 'size'
      - 'length'

Custom smell detection patterns extend Reek's analysis capabilities through Ruby metaprogramming and AST manipulation. Creating custom detectors requires understanding Reek's internal architecture and the Parser gem's abstract syntax trees.

# Custom smell detector example
module Reek
  module SmellDetectors
    class ExcessiveComments < SmellDetector
      def self.smell_type
        'ExcessiveComments'
      end
      
      def self.default_config
        {
          'max_comments_per_method' => 5
        }
      end
      
      def sniff(ctx)
        return [] unless ctx.is_a?(Reek::Context::MethodContext)
        
        comment_count = count_comments(ctx)
        return [] if comment_count <= max_comments
        
        [warning(
          ctx,
          message: "Method #{ctx.full_name} has #{comment_count} comments",
          lines: ctx.exp.line_numbers
        )]
      end
      
      private
      
      def max_comments
        config['max_comments_per_method']
      end
      
      def count_comments(ctx)
        # Implementation to count comments in method context
        source_lines = ctx.exp.source_buffer.source.lines
        method_lines = source_lines[ctx.exp.line_numbers.min..ctx.exp.line_numbers.max]
        method_lines.count { |line| line.strip.start_with?('#') }
      end
    end
  end
end

Multi-project configurations handle complex codebases with different quality standards across components. Directory-specific configuration files override global settings for targeted analysis.

# Project structure with layered configuration
project_root/
├── .reek.yml                    # Global settings
├── lib/
│   ├── .reek.yml               # Library-specific rules
│   └── core/
│       └── .reek.yml           # Core module rules
└── spec/
    └── .reek.yml               # Test-specific configuration

# lib/.reek.yml - stricter rules for core code
detectors:
  LongMethod:
    max_statements: 10
  ComplexityScore:
    max_score: 8
    
# spec/.reek.yml - relaxed rules for tests  
detectors:
  LongMethod:
    enabled: false
  UtilityFunction:
    enabled: false

Programmatic configuration modification allows dynamic rule adjustment based on runtime conditions or external factors.

class AdaptiveReekAnalysis
  def analyze_with_context(source_path, strictness_level)
    config = base_configuration
    
    case strictness_level
    when :strict
      config.merge!(strict_overrides)
    when :legacy
      config.merge!(legacy_overrides)  
    end
    
    examiner = Reek::Examiner.new(
      Reek::Source.from_file(source_path),
      configuration: Reek::Configuration::AppConfiguration.new(config)
    )
    
    process_results(examiner.smells, strictness_level)
  end
  
  private
  
  def base_configuration
    {
      'detectors' => {
        'LongMethod' => { 'max_statements' => 12 },
        'LongParameterList' => { 'max_params' => 4 }
      }
    }
  end
  
  def strict_overrides
    {
      'detectors' => {
        'LongMethod' => { 'max_statements' => 8 },
        'ComplexityScore' => { 'max_score' => 6 }
      }
    }
  end
end

Integration with code review workflows requires custom formatting and filtering based on changed files or specific review criteria.

class CodeReviewAnalyzer
  def analyze_diff(base_commit, head_commit)
    changed_files = git_changed_files(base_commit, head_commit)
    ruby_files = changed_files.select { |f| f.end_with?('.rb') }
    
    results = {}
    ruby_files.each do |file|
      examiner = Reek::Examiner.new(Reek::Source.from_file(file))
      new_smells = filter_new_smells(examiner.smells, base_commit, file)
      results[file] = new_smells if new_smells.any?
    end
    
    generate_review_report(results)
  end
  
  private
  
  def filter_new_smells(smells, base_commit, file)
    base_content = git_file_content(base_commit, file)
    return smells if base_content.nil?
    
    base_examiner = Reek::Examiner.new(Reek::Source.from_s(base_content))
    base_smell_signatures = base_examiner.smells.map(&:smell_signature)
    
    smells.reject { |smell| base_smell_signatures.include?(smell.smell_signature) }
  end
end

Error Handling & Debugging

Reek analysis failures occur when code contains syntax errors, encoding issues, or when detector logic encounters unexpected AST structures. Error handling strategies ensure analysis continues despite individual file problems and provide diagnostic information for troubleshooting.

Syntax error handling prevents analysis failures when examining codebases with syntax problems. Reek catches parser exceptions and reports them as analysis errors rather than crashing the entire process.

class RobustAnalyzer
  def analyze_directory(path)
    results = { successes: [], errors: [] }
    
    Dir.glob("#{path}/**/*.rb").each do |file|
      begin
        source = Reek::Source.from_file(file)
        examiner = Reek::Examiner.new(source)
        results[:successes] << {
          file: file,
          smells: examiner.smells
        }
      rescue Reek::Errors::IncomprehensibleSourceError => e
        results[:errors] << {
          file: file,
          error: :syntax_error,
          message: e.message
        }
      rescue Encoding::UndefinedConversionError => e
        results[:errors] << {
          file: file,
          error: :encoding_error,
          message: "Encoding issue: #{e.message}"
        }
      rescue => e
        results[:errors] << {
          file: file,
          error: :unexpected,
          message: e.message,
          backtrace: e.backtrace.first(5)
        }
      end
    end
    
    results
  end
end

Configuration validation prevents runtime errors from invalid detector settings or malformed YAML files. Validation catches common configuration mistakes before analysis begins.

class ConfigurationValidator
  def self.validate(config_path)
    config = YAML.load_file(config_path)
    errors = []
    
    if config['detectors']
      config['detectors'].each do |detector_name, settings|
        errors.concat(validate_detector(detector_name, settings))
      end
    end
    
    if config['directories']
      config['directories'].each do |dir_config|
        errors.concat(validate_directory_config(dir_config))
      end
    end
    
    unless errors.empty?
      raise Reek::Errors::ConfigurationError, 
            "Configuration errors:\n#{errors.join("\n")}"
    end
    
    config
  rescue Psych::SyntaxError => e
    raise Reek::Errors::ConfigurationError,
          "YAML syntax error in #{config_path}: #{e.message}"
  end
  
  private
  
  def self.validate_detector(name, settings)
    detector_class = "Reek::SmellDetectors::#{name}".constantize
    valid_keys = detector_class.default_config.keys
    
    invalid_keys = settings.keys - valid_keys
    invalid_keys.map { |key| "Unknown setting '#{key}' for #{name}" }
  rescue NameError
    ["Unknown detector: #{name}"]
  end
end

Debugging smell detection requires understanding how Reek analyzes code structure and why specific patterns trigger detection rules. Debug output reveals the analysis process and helps identify false positives.

class SmellDebugger
  def debug_method_analysis(source_code, method_name)
    source = Reek::Source.from_s(source_code)
    examiner = Reek::Examiner.new(source)
    
    # Find the specific method context
    method_context = find_method_context(examiner.context, method_name)
    return unless method_context
    
    puts "Method: #{method_context.full_name}"
    puts "Lines: #{method_context.exp.line_numbers}"
    puts "Statements: #{count_statements(method_context)}"
    puts "Parameters: #{method_context.parameters.length}"
    
    # Run individual detectors with debug info
    examiner.configuration.smell_detectors.each do |detector_class|
      detector = detector_class.new(examiner.configuration)
      smells = detector.sniff(method_context)
      
      if smells.any?
        puts "\n#{detector_class.smell_type}:"
        smells.each do |smell|
          puts "  #{smell.message}"
          puts "  Config: #{detector.config}"
        end
      end
    end
  end
  
  private
  
  def find_method_context(context, method_name)
    return context if context.matches?([method_name])
    
    context.children.each do |child|
      result = find_method_context(child, method_name)
      return result if result
    end
    
    nil
  end
end

False positive management requires systematic approaches to identify legitimate code patterns that trigger smell detection incorrectly. Documenting and excluding false positives maintains analysis accuracy while preserving code quality standards.

class FalsePositiveManager
  def initialize(config_file = '.reek.yml')
    @config_file = config_file
    @config = load_config
  end
  
  def suppress_smell(file_path, method_name, smell_type, reason)
    exclusion_path = "#{extract_class_name(file_path)}##{method_name}"
    
    @config['detectors'] ||= {}
    @config['detectors'][smell_type] ||= {}
    @config['detectors'][smell_type]['exclude'] ||= []
    
    unless @config['detectors'][smell_type]['exclude'].include?(exclusion_path)
      @config['detectors'][smell_type]['exclude'] << exclusion_path
      
      add_comment_to_config(smell_type, exclusion_path, reason)
      save_config
    end
  end
  
  def review_suppressed_smells
    return {} unless @config['detectors']
    
    suppressed = {}
    @config['detectors'].each do |detector, config|
      next unless config['exclude']
      
      config['exclude'].each do |exclusion|
        suppressed[exclusion] = {
          detector: detector,
          reason: extract_comment(detector, exclusion)
        }
      end
    end
    
    suppressed
  end
  
  private
  
  def load_config
    File.exist?(@config_file) ? YAML.load_file(@config_file) : {}
  end
  
  def save_config
    File.write(@config_file, @config.to_yaml)
  end
end

Performance debugging identifies analysis bottlenecks in large codebases. Profiling Reek execution reveals which files or detectors consume excessive processing time.

class ReekProfiler
  def profile_analysis(directory)
    require 'benchmark'
    
    results = {
      total_time: 0,
      file_times: {},
      detector_times: Hash.new(0),
      slow_files: []
    }
    
    Dir.glob("#{directory}/**/*.rb").each do |file|
      file_time = Benchmark.realtime do
        analyze_with_detector_profiling(file, results[:detector_times])
      end
      
      results[:file_times][file] = file_time
      results[:total_time] += file_time
      
      results[:slow_files] << [file, file_time] if file_time > 1.0
    end
    
    results[:slow_files].sort_by! { |_, time| -time }
    results
  end
  
  private
  
  def analyze_with_detector_profiling(file, detector_times)
    source = Reek::Source.from_file(file)
    examiner = Reek::Examiner.new(source)
    
    examiner.configuration.smell_detectors.each do |detector_class|
      time = Benchmark.realtime do
        detector = detector_class.new(examiner.configuration)
        detector.run(examiner.context)
      end
      detector_times[detector_class.smell_type] += time
    end
  end
end

Production Patterns

Production deployments of Reek require integration with continuous integration systems, automated quality gates, and team-wide development workflows. Effective production usage balances code quality enforcement with development velocity and handles diverse codebase characteristics across team members and project phases.

Continuous integration integration establishes quality gates that prevent code quality regression while avoiding false failures from legitimate code patterns. CI configurations must account for legacy code, emergency fixes, and gradual quality improvement strategies.

# ci/quality_check.rb
class QualityGate
  def initialize(config = {})
    @baseline_file = config[:baseline_file] || '.reek_baseline.yml'
    @fail_threshold = config[:fail_threshold] || 0
    @warning_threshold = config[:warning_threshold] || 5
  end
  
  def run_quality_check
    current_smells = analyze_current_codebase
    baseline_smells = load_baseline_smells
    
    new_smells = identify_new_smells(current_smells, baseline_smells)
    regression_count = new_smells.length
    
    generate_ci_report(current_smells, new_smells, regression_count)
    
    case
    when regression_count > @fail_threshold
      exit_with_failure("Quality gate failed: #{regression_count} new smells detected")
    when regression_count > @warning_threshold
      puts "Warning: #{regression_count} new smells detected (threshold: #{@warning_threshold})"
      exit 0
    else
      puts "Quality gate passed: #{regression_count} new smells"
      exit 0
    end
  end
  
  private
  
  def analyze_current_codebase
    results = {}
    
    Dir.glob("app/**/*.rb", "lib/**/*.rb").each do |file|
      next if file.match?(/\A(db\/migrate|vendor)/)
      
      examiner = Reek::Examiner.new(Reek::Source.from_file(file))
      smells = examiner.smells.map do |smell|
        {
          file: file,
          line: smell.lines.first,
          type: smell.smell_type,
          context: smell.context,
          signature: smell_signature(smell)
        }
      end
      results[file] = smells
    end
    
    results
  end
  
  def identify_new_smells(current, baseline)
    current_signatures = extract_all_signatures(current)
    baseline_signatures = extract_all_signatures(baseline)
    
    new_signature_set = current_signatures - baseline_signatures
    find_smells_by_signatures(current, new_signature_set)
  end
end

Monitoring and alerting systems track code quality trends over time and alert teams to significant quality degradation. Production monitoring integrates with existing observability platforms and provides actionable quality metrics.

class QualityMonitor
  def initialize(metrics_client)
    @metrics = metrics_client
  end
  
  def collect_quality_metrics(repository_path)
    analysis_start = Time.now
    
    smell_counts = Hash.new(0)
    file_count = 0
    total_lines = 0
    
    Dir.glob("#{repository_path}/**/*.rb").each do |file|
      next if excluded_path?(file)
      
      file_count += 1
      total_lines += count_lines(file)
      
      examiner = Reek::Examiner.new(Reek::Source.from_file(file))
      examiner.smells.each { |smell| smell_counts[smell.smell_type] += 1 }
    end
    
    analysis_duration = Time.now - analysis_start
    
    send_metrics(smell_counts, file_count, total_lines, analysis_duration)
    check_quality_thresholds(smell_counts, file_count)
  end
  
  private
  
  def send_metrics(smell_counts, file_count, total_lines, duration)
    @metrics.gauge('code_quality.files_analyzed', file_count)
    @metrics.gauge('code_quality.total_lines', total_lines)
    @metrics.gauge('code_quality.analysis_duration_ms', duration * 1000)
    
    smell_counts.each do |smell_type, count|
      @metrics.gauge("code_quality.smells.#{smell_type.downcase}", count)
    end
    
    total_smells = smell_counts.values.sum
    smell_density = total_lines > 0 ? (total_smells.to_f / total_lines * 1000).round(2) : 0
    @metrics.gauge('code_quality.smell_density_per_kloc', smell_density)
  end
  
  def check_quality_thresholds(smell_counts, file_count)
    total_smells = smell_counts.values.sum
    smell_ratio = total_smells.to_f / file_count
    
    if smell_ratio > 10.0
      @metrics.increment('code_quality.alerts.high_smell_ratio')
      alert_team("High smell ratio detected: #{smell_ratio.round(2)} smells per file")
    end
    
    smell_counts.each do |smell_type, count|
      threshold = quality_thresholds[smell_type]
      if threshold && count > threshold
        @metrics.increment("code_quality.alerts.#{smell_type.downcase}_threshold")
        alert_team("#{smell_type} threshold exceeded: #{count} instances")
      end
    end
  end
end

Team workflow integration requires balancing individual developer feedback with team-wide quality standards. Workflow integration includes pre-commit hooks, editor integration, and development environment setup that provides immediate feedback without disrupting development flow.

# tools/pre_commit_quality.rb
class PreCommitQualityCheck
  def run
    staged_files = git_staged_ruby_files
    return exit_success("No Ruby files staged") if staged_files.empty?
    
    problems = []
    
    staged_files.each do |file|
      file_problems = analyze_staged_file(file)
      problems.concat(file_problems) if file_problems.any?
    end
    
    if problems.any?
      display_problems(problems)
      offer_auto_fixes(problems)
    else
      puts "Quality check passed for #{staged_files.length} files"
      exit 0
    end
  end
  
  private
  
  def analyze_staged_file(file)
    # Get staged content, not working directory content
    staged_content = `git show :#{file}`
    return [] if staged_content.empty?
    
    examiner = Reek::Examiner.new(Reek::Source.from_s(staged_content))
    
    examiner.smells.map do |smell|
      {
        file: file,
        line: smell.lines.first,
        type: smell.smell_type,
        message: smell.message,
        context: smell.context,
        auto_fixable: auto_fixable_smell?(smell)
      }
    end
  end
  
  def offer_auto_fixes(problems)
    auto_fixable = problems.select { |p| p[:auto_fixable] }
    
    if auto_fixable.any?
      puts "\nSome issues can be automatically fixed:"
      auto_fixable.each do |problem|
        puts "  #{problem[:file]}:#{problem[:line]} - #{problem[:type]}"
      end
      
      print "Apply automatic fixes? [y/N]: "
      if STDIN.gets.chomp.downcase == 'y'
        apply_auto_fixes(auto_fixable)
        puts "Fixes applied. Please review and commit again."
        exit 1
      end
    end
    
    exit 1
  end
end

Scaling strategies for large codebases require parallel analysis, incremental improvement approaches, and selective quality enforcement. Large-scale production usage must handle repositories with thousands of files while maintaining reasonable analysis times.

class ScalableAnalyzer
  def initialize(config = {})
    @worker_count = config[:workers] || processor_count
    @chunk_size = config[:chunk_size] || 50
    @analysis_timeout = config[:timeout] || 300
  end
  
  def analyze_repository(repo_path)
    files = discover_ruby_files(repo_path)
    puts "Analyzing #{files.length} files with #{@worker_count} workers"
    
    file_chunks = files.each_slice(@chunk_size).to_a
    results = Parallel.map(file_chunks, in_processes: @worker_count) do |chunk|
      analyze_file_chunk(chunk)
    end
    
    aggregate_results(results.flatten)
  end
  
  private
  
  def analyze_file_chunk(files)
    chunk_results = []
    
    files.each do |file|
      begin
        Timeout.timeout(@analysis_timeout / @chunk_size) do
          examiner = Reek::Examiner.new(Reek::Source.from_file(file))
          chunk_results << {
            file: file,
            smells: examiner.smells.map(&:to_h),
            analysis_time: Time.now
          }
        end
      rescue Timeout::Error
        chunk_results << {
          file: file,
          error: "Analysis timeout",
          analysis_time: Time.now
        }
      rescue => e
        chunk_results << {
          file: file,
          error: e.message,
          analysis_time: Time.now
        }
      end
    end
    
    chunk_results
  end
  
  def aggregate_results(all_results)
    successful = all_results.reject { |r| r[:error] }
    failed = all_results.select { |r| r[:error] }
    
    total_smells = successful.sum { |r| r[:smells]&.length || 0 }
    
    {
      summary: {
        files_analyzed: successful.length,
        files_failed: failed.length,
        total_smells: total_smells,
        analysis_date: Time.now
      },
      results: successful,
      failures: failed
    }
  end
end

Common Pitfalls

Reek analysis produces false positives when code patterns appear problematic but serve legitimate purposes. Understanding common false positive scenarios prevents over-configuration and maintains code quality standards while accommodating necessary design patterns.

Configuration proliferation occurs when teams add exclusions reactively without understanding root causes. Excessive configuration undermines code quality goals and creates maintenance overhead that reduces analysis effectiveness.

# Problematic: Over-configured .reek.yml
detectors:
  LongMethod:
    exclude:
      - 'UserController#create'
      - 'UserController#update' 
      - 'OrderProcessor#process'
      - 'PaymentHandler#charge'
      - 'ReportGenerator#generate'
      - 'DataImporter#import'
      # ... 50+ exclusions
      
# Better: Address underlying design issues
class UserController
  def create
    user_creator = UserCreationService.new(user_params)
    
    if user_creator.create
      redirect_to user_path(user_creator.user)
    else
      handle_creation_errors(user_creator.errors)
    end
  end
  
  private
  
  def handle_creation_errors(errors)
    flash[:error] = errors.full_messages.join(', ')
    render :new
  end
end

Smell interpretation mistakes lead to inappropriate refactoring that degrades code quality while attempting to satisfy Reek requirements. Common misinterpretations include treating all long methods as problematic and misunderstanding feature envy detection.

# Problematic: Misinterpreting FeatureEnvy
class Order
  def calculate_total
    # Reek reports FeatureEnvy because this method uses @line_items extensively
    @line_items.sum { |item| item.price * item.quantity } +
      @line_items.sum { |item| item.tax_amount } +
      @line_items.sum(&:shipping_cost)
  end
end

# Wrong solution: Moving logic to LineItem
class LineItem
  def contribute_to_total
    price * quantity + tax_amount + shipping_cost
  end
end

class Order
  def calculate_total
    @line_items.sum(&:contribute_to_total)
  end
end

# Better solution: Understanding the smell context
class Order
  def calculate_total
    subtotal + total_tax + total_shipping
  end
  
  private
  
  def subtotal
    @line_items.sum { |item| item.price * item.quantity }
  end
  
  def total_tax
    @line_items.sum(&:tax_amount)
  end
  
  def total_shipping
    @line_items.sum(&:shipping_cost)
  end
end

Legacy code analysis requires different strategies than greenfield development. Applying modern quality standards to existing codebases without considering evolutionary approaches leads to analysis paralysis and resistance to quality improvements.

class LegacyAnalysisStrategy
  def analyze_legacy_system(base_path)
    # Establish baseline without failing builds
    current_state = capture_current_quality_state(base_path)
    establish_quality_baseline(current_state)
    
    # Identify improvement opportunities
    improvement_candidates = identify_low_hanging_fruit(current_state)
    technical_debt_hotspots = find_high_change_files(base_path)
    
    generate_improvement_roadmap(improvement_candidates, technical_debt_hotspots)
  end
  
  private
  
  def establish_quality_baseline(current_state)
    baseline_config = {
      'detectors' => {}
    }
    
    # Set thresholds based on current worst cases plus buffer
    current_state[:detector_stats].each do |detector, stats|
      baseline_config['detectors'][detector] = {
        'max_allowed_violations' => stats[:count] * 1.1
      }
    end
    
    File.write('.reek_baseline.yml', baseline_config.to_yaml)
  end
  
  def identify_low_hanging_fruit(current_state)
    # Focus on files with recent changes and fixable smells
    current_state[:files].select do |file_analysis|
      has_recent_changes?(file_analysis[:file]) &&
      has_easily_fixable_smells?(file_analysis[:smells])
    end
  end
  
  def has_easily_fixable_smells?(smells)
    easily_fixed = %w[LongParameterList DuplicateMethodCall NestedIterators]
    smells.any? { |smell| easily_fixed.include?(smell[:type]) }
  end
end

Performance degradation in large codebases occurs when analysis configurations become inefficient or when file patterns trigger expensive detection algorithms. Performance issues compound in continuous integration environments where analysis time directly impacts development velocity.

class PerformanceOptimizer
  def optimize_analysis_config(repo_path)
    # Identify expensive detectors through profiling
    detector_performance = profile_detectors(repo_path)
    file_size_distribution = analyze_file_sizes(repo_path)
    
    optimization_recommendations = []
    
    # Recommend disabling expensive detectors on large files
    expensive_detectors = detector_performance
      .select { |_, time| time > 100 } # ms per file
      .keys
      
    if expensive_detectors.any? && file_size_distribution[:large_files].any?
      optimization_recommendations << {
        type: :detector_exclusion,
        detectors: expensive_detectors,
        file_pattern: file_size_distribution[:large_files],
        reason: 'Performance optimization for large files'
      }
    end
    
    # Recommend excluding auto-generated files
    generated_patterns = detect_generated_file_patterns(repo_path)
    if generated_patterns.any?
      optimization_recommendations << {
        type: :directory_exclusion,
        patterns: generated_patterns,
        reason: 'Skip analysis of generated code'
      }
    end
    
    apply_optimizations(optimization_recommendations)
  end
  
  private
  
  def profile_detectors(repo_path)
    sample_files = Dir.glob("#{repo_path}/**/*.rb").sample(20)
    detector_times = Hash.new { |h, k| h[k] = [] }
    
    sample_files.each do |file|
      source = Reek::Source.from_file(file)
      examiner = Reek::Examiner.new(source)
      
      examiner.configuration.smell_detectors.each do |detector_class|
        time = Benchmark.realtime do
          detector = detector_class.new(examiner.configuration)
          detector.run(examiner.context)
        end
        detector_times[detector_class.smell_type] << time * 1000 # Convert to ms
      end
    end
    
    # Calculate average times
    detector_times.transform_values { |times| times.sum / times.length }
  end
end

Team adoption resistance stems from perceived workflow disruption and disagreement about code quality standards. Successful adoption requires gradual introduction, team buy-in, and clear communication about quality goals versus development productivity.

class TeamAdoptionStrategy
  def implement_gradual_adoption(team_config)
    phases = [
      {
        name: 'Awareness Phase',
        duration: '2 weeks',
        actions: [
          'Install Reek locally for interested developers',
          'Run analysis on small modules only',
          'Share interesting findings in team meetings'
        ],
        success_criteria: 'Team understands Reek capabilities'
      },
      {
        name: 'Experiment Phase', 
        duration: '4 weeks',
        actions: [
          'Add Reek to CI without failing builds',
          'Establish quality baseline',
          'Create team coding standards document'
        ],
        success_criteria: 'Baseline established, no workflow disruption'
      },
      {
        name: 'Enforcement Phase',
        duration: 'Ongoing',
        actions: [
          'Enable quality gates for new code only',
          'Regular quality review meetings',
          'Incremental improvement goals'
        ],
        success_criteria: 'Quality gates integrated into workflow'
      }
    ]
    
    execute_adoption_phases(phases, team_config)
  end
  
  private
  
  def execute_adoption_phases(phases, config)
    phases.each do |phase|
      puts "Starting #{phase[:name]}"
      
      phase[:actions].each do |action|
        puts "  #{action}"
        # Implementation depends on team infrastructure
      end
      
      wait_for_phase_completion(phase, config)
    end
  end
end

Reference

Core Classes and Methods

Class Purpose Key Methods
Reek::Examiner Main analysis interface #initialize(source), #smells, #context
Reek::Source Code source abstraction .from_file(path), .from_s(string), #origin
Reek::SmellWarning Individual smell result #message, #smell_type, #lines, #context
Reek::Configuration::AppConfiguration Configuration management #smell_detectors, #directory_directives
Reek::SmellDetector Base detector class #run(context), #sniff(context), #config

Smell Detector Types

Smell Type Description Default Threshold Configurable Options
LongMethod Methods with too many statements 15 statements max_statements
LongParameterList Methods with excessive parameters 4 parameters max_params
FeatureEnvy Methods using external objects excessively N/A enabled, exclude
DataClump Same group of parameters across methods 3 occurrences max_copies, min_clump_size
LargeClass Classes with too many methods 25 methods max_methods
DuplicateMethodCall Repeated identical method calls 2 calls max_calls, allow_calls
NestedIterators Blocks within blocks 2 levels max_allowed_nesting
ComplexityScore High cyclomatic complexity 8 score max_score
UtilityFunction Public methods not using instance variables N/A public_methods_only
IrresponsibleModule Classes/modules without comments N/A enabled

Configuration File Structure

# Global detector settings
detectors:
  DetectorName:
    enabled: true|false
    max_statements: integer
    exclude:
      - 'ClassName#method_name'
      - 'ModuleName#'

# Directory-specific overrides  
directories:
  "lib/":
    detectors:
      LongMethod:
        max_statements: 10
        
  "spec/":
    detectors:
      UtilityFunction:
        enabled: false

# File exclusions
exclude_paths:
  - "vendor/"
  - "db/migrate/"
  - "*.generated.rb"

Command Line Options

Option Argument Description
--format text, json, xml, yaml, html Output format
--config path/to/config.yml Configuration file path
--smell SmellType Report only specific smell type
--no-documentation-url N/A Omit documentation URLs from output
--sort-by smelliness, file Sort order for results
--single-line N/A Show one smell per line
--no-progress N/A Disable progress indicator
--force-exclusion N/A Exclude files even if explicitly specified
--stdin N/A Read source code from STDIN

Exit Codes

Code Meaning
0 No smells detected or analysis successful
2 Smells detected (configurable threshold)
1 Application error (invalid options, syntax errors)

Inline Comment Directives

# Disable all smells for method
# :reek:all
def problematic_method
end

# Disable specific smell types
# :reek:LongMethod:FeatureEnvy
def complex_method
end

# Disable for entire class
class LegacyClass
  # :reek:all
end

Integration Patterns

Integration Implementation Configuration
Rake Task require 'reek/rake/task' Reek::Rake::Task.new
Git Hook Shell script with reek --format json Exit code checking
CI/CD Docker container or gem installation Quality gate thresholds
Editor Language server protocol integration Real-time feedback
Metrics Programmatic API with custom reporting Trend analysis