Ruby Science

Divergent Change

A class suffers from Divergent Change when it changes for multiple reasons.

Symptoms

  • You can’t easily describe what the class does in one sentence.
  • The class is changed more frequently than other classes in the application.
  • Different changes to the class aren’t related to each other.

Example

# app/controllers/summaries_controller.rb
class SummariesController < ApplicationController
  def show
    @survey = Survey.find(params[])
    @summaries = @survey.summarize(summarizer)
  end

  private

  def summarizer
    case params[]
    when 'breakdown'
      Breakdown.new
    when 'most_recent'
      MostRecent.new
    when 'your_answers'
      UserAnswer.new(current_user)
    else
      raise "Unknown summary type: #{params[]}"
    end
  end
end

This controller has many reasons to change:

  • Control flow logic related to summaries, such as authentication.
  • Any instance in which a summarizer strategy is added or changed.

Solutions

Prevention

You can prevent divergent change from occurring by following the single responsibility principle. If a class has only one responsibility, it has only one reason to change.

Following the open/closed principle limits future changes to classes, including divergent change.

Following composition over inheritance will make it easier to create small classes, preventing divergent change.

If a large portion of the class is devoted to instantiating subclasses, try following the dependency inversion principle.

You can use churn to discover which files are changing most frequently. This isn’t a direct relationship, but frequently changed files often have more than one responsibility and thus more than one reason to change.

Ruby Science

The canonical reference for writing fantastic Rails applications from authors who have created hundreds.

Work with us to make a new Rails app, or to maintain, improve, or scale your existing app.