def next_after(book) = …
def first_books = … def unlocked_for(student) = … 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_interfacedoes 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.
Related tools
dead_api
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
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
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 →