runtimeanalyz.ing

← All capabilities

split_interface

CLI · LSP · MCP Pro

The role interfaces in a class. Groups a class's public methods by which callers used them in the recording, and lists each group as a role interface to extract.

Scheduler
next_after ( book Book )
first_books
Curriculum Sequencing
def next_after(book) = …
def first_books = …
Enrollment
unlocked_for ( student Student )
Unlocking
def unlocked_for(student) = …
Report
weeks_for ( book Book )
final_books
Reporting
def weeks_for(book) = …
def final_books = …
Curriculum has three callers, and no caller uses methods from more than one group. Each group is a role Curriculum plays and a candidate for an object of its own.

The problem

Curriculum in the example app has fourteen public methods, in app/models/curriculum.rb. Each caller uses a few of them and depends on all fourteen. A double of Curriculum has to choose which of the fourteen to stub, and a change to the class is a change to something every caller depends on.

The interface segregation principle says no client should depend on methods it doesn’t use. Which methods each client uses is recorded in the callers, so reading the class does not show where to split it.

The fix

Ask which callers use which methods:

$ ra split_interface Curriculum

Curriculum
· 4 roles · 0 callers span more than one
  for Report             final_books, weeks_for                                       Report
  for Scheduler          first_books, next_after                                      Scheduler
  for Billing::Checkout  for, unlock_paid_books                                       Billing::Checkout
  for Enrollment         unlocked_for, level_for, books_at, archived?, prerequisites  Enrollment
never called in any recorded run: name
· extract 4 role interfaces   ISP

The four callers use four groups of methods with no method in common, and no recorded run called name. Enrollment’s group has five methods because Enrollment#eligible_books has feature envy toward Curriculum. feature_envy reports that method, and once it moves, Enrollment needs unlocked_for alone.

Extract each role and give each caller the one it uses. You choose the names:

class Curriculum
  def sequencing = Sequencing.new(self)
  def unlocking = Unlocking.new(self)
  def reporting = Reporting.new(self)
end

Scheduler.new(curriculum.sequencing)

After the split, Enrollment no longer depends on the reporting methods, so a change to them does not affect it, and a double in Enrollment’s spec has one method to stub instead of fourteen.

In a test-first loop this belongs to the refactor step. The class has just been written to pass its tests, and the roles are cheaper to extract at that point than they will be later.

Where it shows up

  • On the command line, run ra split_interface Klass.
  • Over MCP, your agent gets the same roles as structured blocks, so it can draft the extraction.
  • In your editor (LSP), a hint on the class name and a quick-fix that extracts the roles are planned.

How it works

Every recorded call includes its caller’s class and its receiver’s class. For each class, split_interface groups its public methods by which callers use them. When the callers divide into groups that share no methods, each group is reported as a role. The class’s full public method list comes from the source, so the methods no recorded run called are listed too.

Run it on your app

The output on this page comes from our example Rails app, and it is regenerated whenever a tool changes. These are the commands behind it. On your own app, run ra record first, then put your own class, method or file in their place.

ra split_interface Curriculum

The class is app/models/curriculum.rb, and its callers are Scheduler, Enrollment, Report and Billing::Checkout.

Reading the result

  • The report is a hint. A caller using part of a class is normal, and a class used in four different ways can be correct, so split_interface does not fail a build.
  • Overlapping groups make a weaker suggestion. When most callers share a core method the groups are not disjoint, and the report says how many callers span more than one role.

Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.

dead_api

LSP · CLI Pro

Find dead public API. The public methods that no caller reached in any recorded run, including those whose only caller is their own spec. Shown as a hint in your editor.

See more →

observed_shape

CLI · LSP · MCP Pro

The interface a call depends on. The messages your code sends to a value, read from recorded runs. They are the interface a call depends on, whether or not anyone declared it.

See more →

specs_that_reach

CLI · LSP · MCP Pro

Run only the specs that reach your change. The spec examples whose recorded runs reach a method. After a change you rerun those examples alone, and the red-green loop takes seconds.

See more →