<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DogBiscuit</title>
    <description>... mmm, crunchy!</description>
    <link>http://dogbiscuit.org/mdub/weblog</link>
    <language>en-us</language>
    <generator>EvenYetAnotherWeblog</generator>
    <item>
      <title>Refactoring "support" for Ruby?</title>
      <guid>http://dogbiscuit.org/mdub/weblog/Tech/Programming/Ruby/RubyMethodRenamed</guid>
      <pubDate>Sun, 10 Apr 2005 23:00:00 +1000</pubDate>
      <description><![CDATA[<p>
These days, there a number of pretty damn good IDEs for Java, with features
like intelligent code-completion (aka "intellisense") and automated
refactorings.  I was a late-starter with IDEs, myself, but even just over
the past year I've become annoyingly dependent on some of those IDE
features.
</p>
<p>
Such features depend quite heavily on gleaning data-type information from
the code, which is fine for languages like Java and C#.  But in
dynamically-typed languages like <a href='http://ruby-lang.org'>Ruby</a>, we don't have
that type info, so things like method-name completion and automated
renaming become impossible.  (Or so I thought).
</p>
<h3>
Stealing a trick from SmallTalk
</h3>
<p>
It's been puzzling me that there isn't better refactoring support for Ruby,
given that the whole concept of <a href='http://www.refactoring.com'>refactoring</a>
grew out of the SmallTalk community, in the first place.  Or more
accurately, I've been confused about how automated refactoring could be
possible in a dynamic language like SmallTalk.  
</p>
<p>
Then, recently, I stumbled across a paper describing "<a href='http://st-www.cs.uiuc.edu/~droberts/tapos/TAPOS.htm'>A Refactoring Tool
for Smalltalk</a>", which
contains the following explanation:
</p>
<blockquote>
The Refactoring Browser uses <u>method wrappers</u> to collect runtime
information. These wrappers are activated when the wrapped method is
called and when it returns. The wrapper can execute an arbitrary block of
Smalltalk code. To perform the rename method refactoring dynamically, the
Refactoring Browser renames the initial method and then puts a method
wrapper on the original method. As the program runs, the wrapper detects
sites that call the original method. Whenever a call to the old method is
detected, the method wrapper suspends execution of the program, goes up
the call stack to the sender and changes the source code to refer to the
new, renamed method. Therefore, <u>as the program is exercised</u>, it
converges towards a correctly refactored program.
</blockquote>
<p>
Ah-ha!  Cunning.
</p>
<h3>
The Ruby version
</h3>
<p>
As it turns out, we can do much the same thing in Ruby ... leaving aside
the "go up the call stack and change the source code" part.
</p>
<p>
Here's the supporting code:
</p>
<pre>
def method_renamed(h)
  old_name = h.keys[0].to_sym
  new_name = h.values[0].to_sym
  define_method(old_name) { |*args|
    file, line = caller[1].split(':')
    warning = &quot;##{old_name} renamed to ##{new_name}&quot;
    $stderr.puts &quot;#{file}:#{line}: #{warning}&quot;
    send(new_name, *args)
  }
end
</pre>
<p>
Okay, here's a method I want to rename:
</p>
<pre>
class LinkPanel

  def render
    # ... 
  end

end
</pre>
<p>
When I rename it, I also record the change using <tt>method_renamed</tt>:
</p>
<pre>
class LinkPanel

  method_renamed :render =&gt; :to_html

  def to_html
    # ... 
  end

end
</pre>
<p>
Now, I run my tests, and calls to the renamed method result in warnings:
</p>
<pre>
/home/mikew/eyaw/sidebar.rb:229: #render renamed to #to_html
</pre>
<p>
With a single key-chord in my <a href='http://www.gnu.org/software/emacs/emacs.html'>Ruby
IDE</a>, I can jump directly to
the source-code in question, and fix up the call.  I imagine that an
ever-so-slightly-more intelligent IDE could complete the refactoring,
applying the rename to the call-site automatically!  Later on, when I'm
confident that everything has been cleaned up, I'll go back and remove that
<tt>method_renamed</tt> alias.
</p>
<p>
There's more to refactoring than just renaming stuff, of course.  I think
the "dynamic analysis" trick would be useful to support other refactorings,
too ... though I haven't tried it yet.
</p>
<p>
Proviso: this approach relies on actually running the code, preferably from
tests.  As the original paper says:
</p>
<blockquote>
.. the refactoring is only as good as your test suite. If there are
pieces of code that are not executed, they will never be analyzed, and
the refactoring will not be completed ...
</blockquote>
]]></description>
    </item>
  </channel>
</rss>
