class Curriculum
def unlocked_for(student) = …
def archived_books = …
end class Report
def legacy_totals = …
end archived_books is reached by the spec
written for it and by no other code, and legacy_totals has no caller, so both are
shown with a hint in your editor.
The problem
A public method is hard to remove, because it is hard to be sure that nothing still calls it. If
nobody knows whether Curriculum#archived_books has a caller, it stays, and it goes on being
tested, documented and maintained.
A static tool cannot answer the question in Ruby. A method can be called through send,
public_send, a callback or a name built from a string, so a search that finds no references does
not show that the method is unused.
Test-first development adds a second kind of dead method: one you wrote a test for and made pass, which no other code then needed. Its spec calls it, so a search for callers finds one. This is speculative generality, and such a method is cheapest to delete while it is still new.
The fix
In your editor, opening app/models/curriculum.rb publishes a hint on each dead method’s line:
Curriculum#name: no caller in any recorded run
Curriculum#archived_books: only caller: its own spec (spec/models/curriculum_spec.rb:39)
The command line gives the same report for a file or a directory:
$ ra dead_api app/models/curriculum.rb
dead_api app/models/curriculum.rb
· 13 public methods · 1 dead · 1 spec-only
· Curriculum#name: no caller in any recorded run
app/models/curriculum.rb:11
· Curriculum#archived_books: only caller: its own spec (spec/models/curriculum_spec.rb:39)
app/models/curriculum.rb:37
Over all of app/models it finds 107 public methods, 10 dead and 15 spec-only. Among them are
Report#legacy_totals, which has no caller in any recorded run, and Report#csv_rows, whose only
caller is spec/models/report_spec.rb:7.
Delete the method along with the spec that called it. The suite still passes, because that spec was the only code that needed the method. If a rake task or a job you didn’t record does need it, you will find out when you record that run.
How it works
The static index lists every public method the source declares. The recorder wraps every method
defined in your app, so a call is recorded however it was made: directly, through send or
public_send, or from a callback. dead_api joins the index to the recording.
It flags two kinds of method:
- A method with no recorded caller.
- A method whose only callers are its own spec examples. Each call tree is stamped with the example
that produced it, so the tool can tell
curriculum_spec.rbcallingarchived_booksdirectly apart from app code calling it.
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 dead_api app/models
ra dead_api app/models/curriculum.rb
The flagged methods are app/models/curriculum.rb:37 and app/models/report.rb:6 and :14.
Reading the result
- The result is as complete as your recording. A method called by a rake task, a rarely hit path or a job you didn’t run is reported as dead. Record those runs, or treat each result as a method to check before you delete it.
- It publishes hints. It does not remove a method and it does not fail a build.
prod_dead_apiasks the same question of the app-run trees only, which gives proof from traffic.
Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.
Related tools
split_interface
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.
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 →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 →