[SUMMARY] Refactoring (#75)

James,

Great summary. Wish I had participated in the quiz!

I liked Dave Burt's 'Removed Unused Scope' example. Would you (or
someone else) care to give examples of 'Replace Similar Methods with
Metaprogrammed Definitions' and a 'Convert a Family of Methods to a
Single method_missing()'? I'm especially interested in the latter. I
know how method_missing works and I've seen its use in frameworks (like
Rails), but I've never actually written one myself.

I'm sure there are opportunities to use these Rubyesque type
refactorings in my code but I tend not to see them due to lack of
experience. One of the great things about the Refactoring book for me
was that it gave examples of things that I could easily relate to my own
application code. It sharpened my nose.

Thanks again for the quiz.

Steve

···

-----Original Message-----
From: Ruby Quiz [mailto:james@grayproductions.net]
Sent: Thursday, April 20, 2006 9:07 AM
To: ruby-talk ML
Subject: [SUMMARY] Refactoring (#75)

Hopefully this quiz caused some of us to read Martin Fowler's
Refactoring. I know it did exactly that for me, though it has been on
my want-to-read list for some time now. I'm half-way through it now and
I can only recommend everyone reading this pick up a copy immediately.

Each of this week's solutions came with its own write-up so there isn't
a lot left for me to dissect. Instead, I'll take this chance to talk a
little about the most interesting part of this exercise to me.

It's my opinion that a large majority of the refactoring patterns were
designed with the static languages in mind. I'm very intrigued by how a
language like Ruby changes the game. Let's me see if I can give some
examples.

First, I mentioned in my write-up that part of my refactoring called for
making a method abstract. After some thought, I decided that meant
deleting the method in Ruby. (See my solution for the reasoning.)

Here's another thought: many of the refactorings tell you to declare a
variable as final, to ensure that it isn't changing. What's the Ruby
equivalent to that?
I don't think we have one. We can #freeze the object, but that won't
help us if the variable is reassigned. I don't see a good answer there.

But there are two sides to every coin and we have some nice advantages.
For example, Extract Method talks a lot about how the local variables
can complicate extraction, especially if many of them are changed by the
extracted code. This is much less of an issue in Ruby though, since we
can have the extracted method take a block and use the variables in
there however we need. In fact, this means we can use Extract Method
quite a bit more often, since the code we pull just needs to be close to
similar and we can handle the differences in the block.

Dave Burt was also thinking along these lines and went all the way to
introduce some Ruby centric refactorings. Here's an example:

  Refactoring 2: Remove Unused Scope
  /You have a begin...end block that introduces a scope that is
unused./
  *Remove the unused scope.*
  
  Thus:
  
    def foo
      begin
        bar
      rescue
        baz
      end
    end
  
  Becomes:
  
    def foo
      bar
    rescue
      baz
    end

I would love to see many more of these. Perhaps we could come up with a
Replace Similar Methods with Metaprogrammed Definitions and a Convert a
Family of Methods to a Single method_missing() Call recipes. Food for
thought.

A big thank you to all who supported this deviation from our usual fun
and games. I think Pat hit on a super important topic here and we would
all do well to learn more about it.

Tomorrow, Matthew Moss is back with a quiz about txet smnraiclbg...

This is from memory, because I don't have easy access to the code at the moment.
I built a GUI application that automated various mainframe tasks for
users over TN3270E.
The particular mainframe in question had fairly tedious navigation
steps, and was broken out into separate 'screens', each of which had a
four-character name.

Long story short, I ended up making a method-missing handler for
screen navigation.

def method_missing(message, *args, &block)
  if message =~ /navigate_to_(\w{4})/
    x, y = find_prompt_coordinates
    screen.put(x,y,$1)
    screen.transmit
  else
    super
  end
end

Which meant I could then say:
host.navigate_to_abcd

I could just as easily have made "navigate_to" take a screen name as a
parameter, but I thought this was easier to read. Maybe it's not the
best example.

--Wilson.

···

On 4/20/06, Molitor, Stephen L <Stephen.L.Molitor@erac.com> wrote:

James,

Great summary. Wish I had participated in the quiz!

I liked Dave Burt's 'Removed Unused Scope' example. Would you (or
someone else) care to give examples of 'Replace Similar Methods with
Metaprogrammed Definitions' and a 'Convert a Family of Methods to a
Single method_missing()'? I'm especially interested in the latter. I
know how method_missing works and I've seen its use in frameworks (like
Rails), but I've never actually written one myself.

I'm sure there are opportunities to use these Rubyesque type
refactorings in my code but I tend not to see them due to lack of
experience. One of the great things about the Refactoring book for me
was that it gave examples of things that I could easily relate to my own
application code. It sharpened my nose.

Thanks again for the quiz.

Steve

-----Original Message-----
From: Ruby Quiz [mailto:james@grayproductions.net]
Sent: Thursday, April 20, 2006 9:07 AM
To: ruby-talk ML
Subject: [SUMMARY] Refactoring (#75)

Hopefully this quiz caused some of us to read Martin Fowler's
Refactoring. I know it did exactly that for me, though it has been on
my want-to-read list for some time now. I'm half-way through it now and
I can only recommend everyone reading this pick up a copy immediately.

Each of this week's solutions came with its own write-up so there isn't
a lot left for me to dissect. Instead, I'll take this chance to talk a
little about the most interesting part of this exercise to me.

It's my opinion that a large majority of the refactoring patterns were
designed with the static languages in mind. I'm very intrigued by how a
language like Ruby changes the game. Let's me see if I can give some
examples.

First, I mentioned in my write-up that part of my refactoring called for
making a method abstract. After some thought, I decided that meant
deleting the method in Ruby. (See my solution for the reasoning.)

Here's another thought: many of the refactorings tell you to declare a
variable as final, to ensure that it isn't changing. What's the Ruby
equivalent to that?
I don't think we have one. We can #freeze the object, but that won't
help us if the variable is reassigned. I don't see a good answer there.

But there are two sides to every coin and we have some nice advantages.
For example, Extract Method talks a lot about how the local variables
can complicate extraction, especially if many of them are changed by the
extracted code. This is much less of an issue in Ruby though, since we
can have the extracted method take a block and use the variables in
there however we need. In fact, this means we can use Extract Method
quite a bit more often, since the code we pull just needs to be close to
similar and we can handle the differences in the block.

Dave Burt was also thinking along these lines and went all the way to
introduce some Ruby centric refactorings. Here's an example:

        Refactoring 2: Remove Unused Scope
        /You have a begin...end block that introduces a scope that is
unused./
        *Remove the unused scope.*

        Thus:

          def foo
            begin
              bar
            rescue
              baz
            end
          end

        Becomes:

          def foo
            bar
          rescue
            baz
          end

I would love to see many more of these. Perhaps we could come up with a
Replace Similar Methods with Metaprogrammed Definitions and a Convert a
Family of Methods to a Single method_missing() Call recipes. Food for
thought.

A big thank you to all who supported this deviation from our usual fun
and games. I think Pat hit on a super important topic here and we would
all do well to learn more about it.

Tomorrow, Matthew Moss is back with a quiz about txet smnraiclbg...

James,

Great summary. Wish I had participated in the quiz!

I liked Dave Burt's 'Removed Unused Scope' example. Would you (or
someone else) care to give examples of 'Replace Similar Methods with
Metaprogrammed Definitions'

I'm not sure what James had in mind, but I think this works well as an
example of post refactoring 'Replace Similar Methods with
Metaprogrammed Definitions':

def add_test(name, clean_or_dirty, number_of_errors, definition)
    eval <<"END_OF_TEST"
      def test_#{name}
        assert_class_is_#{clean_or_dirty} Class.new {#{definition}}
        assert_number_of_errors #{number_of_errors}
      end
END_OF_TEST
end

def dont_add_test(*args)
  # not gonna do it
end

class CheckRTest < Test::Unit::TestCase
  def setup
    @checkr = CheckR.new
  end

  def test_initialize
    assert_kind_of CheckR, @checkr
  end

  def test_is_class_clean_exists
    assert_respond_to @checkr, "is_class_clean?"
  end

  def test_errors_exists
    assert_respond_to @checkr, "errors"
  end

  add_test :clean_if,
           :clean, 0,
           "def foo; true if true; end"

  add_test :clean_if_2,
           :clean, 0,
           "def foo;if true;a=1;puts a;end;end"

  add_test :clean_while,
           :clean, 0,
           "def foo;while a==2;break;end;end"

  add_test :clean_assignment_in_if_body,
           :clean, 0,
           "def foo;if a==2;a=3;a;end;end"

  add_test :clean_return_value,
           :clean, 0,
           "def foo;1;end"

[thirty more test definitions deleted]

  private
  def assert_class_is_clean(klass)
    assert_equal true, check_class(klass)
  end

  def assert_class_is_dirty(klass)
    assert_equal false, check_class(klass)
  end

  def assert_number_of_errors(n)
    assert_equal n, @checkr.errors.length
  end

  def check_class(klass)
    @checkr.is_class_clean?(klass)
  end

···

On 4/20/06, Molitor, Stephen L <Stephen.L.Molitor@erac.com> wrote:

and a 'Convert a Family of Methods to a
Single method_missing()'? I'm especially interested in the latter. I
know how method_missing works and I've seen its use in frameworks (like
Rails), but I've never actually written one myself.

I'm sure there are opportunities to use these Rubyesque type
refactorings in my code but I tend not to see them due to lack of
experience. One of the great things about the Refactoring book for me
was that it gave examples of things that I could easily relate to my own
application code. It sharpened my nose.

Thanks again for the quiz.

Steve

-----Original Message-----
From: Ruby Quiz [mailto:james@grayproductions.net]
Sent: Thursday, April 20, 2006 9:07 AM
To: ruby-talk ML
Subject: [SUMMARY] Refactoring (#75)

Hopefully this quiz caused some of us to read Martin Fowler's
Refactoring. I know it did exactly that for me, though it has been on
my want-to-read list for some time now. I'm half-way through it now and
I can only recommend everyone reading this pick up a copy immediately.

Each of this week's solutions came with its own write-up so there isn't
a lot left for me to dissect. Instead, I'll take this chance to talk a
little about the most interesting part of this exercise to me.

It's my opinion that a large majority of the refactoring patterns were
designed with the static languages in mind. I'm very intrigued by how a
language like Ruby changes the game. Let's me see if I can give some
examples.

First, I mentioned in my write-up that part of my refactoring called for
making a method abstract. After some thought, I decided that meant
deleting the method in Ruby. (See my solution for the reasoning.)

Here's another thought: many of the refactorings tell you to declare a
variable as final, to ensure that it isn't changing. What's the Ruby
equivalent to that?
I don't think we have one. We can #freeze the object, but that won't
help us if the variable is reassigned. I don't see a good answer there.

But there are two sides to every coin and we have some nice advantages.
For example, Extract Method talks a lot about how the local variables
can complicate extraction, especially if many of them are changed by the
extracted code. This is much less of an issue in Ruby though, since we
can have the extracted method take a block and use the variables in
there however we need. In fact, this means we can use Extract Method
quite a bit more often, since the code we pull just needs to be close to
similar and we can handle the differences in the block.

Dave Burt was also thinking along these lines and went all the way to
introduce some Ruby centric refactorings. Here's an example:

        Refactoring 2: Remove Unused Scope
        /You have a begin...end block that introduces a scope that is
unused./
        *Remove the unused scope.*

        Thus:

          def foo
            begin
              bar
            rescue
              baz
            end
          end

        Becomes:

          def foo
            bar
          rescue
            baz
          end

I would love to see many more of these. Perhaps we could come up with a
Replace Similar Methods with Metaprogrammed Definitions and a Convert a
Family of Methods to a Single method_missing() Call recipes. Food for
thought.

A big thank you to all who supported this deviation from our usual fun
and games. I think Pat hit on a super important topic here and we would
all do well to learn more about it.

Tomorrow, Matthew Moss is back with a quiz about txet smnraiclbg...

--
thanks,
-pate
-------------------------

I'll provide an example:

Let's say you have a class for managing a list of music. Each item in
the list is a class which contains a lot of meta-data about the music
files (artist, title, album, track number, duration, genre, release
date, etc.) Part of managing this list of music is sorting it by
various criteria, for display in a media application for example.

There are a lot of different ways to sort based on the various kinds
of meta-data, and it would be a pain to have to code a special method
to sort on each type of meta-data:

list.sort_by_artist, list.sort_by_title, etc.

Of course in this case it might not be too difficult to just do a
custom sort when needed:

list.sort_by {|song| song.artist}

But why not make things really handy by adding a method missing?

# Full test code
class Song < Struct.new('Song', :title, :artist, :album)
  def to_s
    "#{title} by #{artist} (#{album})"
  end
end

class MusicList
  attr_accessor :list

  def initialize
    @list =
  end

  def method_missing(name, *args)
    if name.to_s =~ /sort_by_(.*)/
      puts "Sorting by #$1:"
      @list.sort_by {|song| song.send($1.to_sym)}
    end
  end
end

if $0 == __FILE__
  music = MusicList.new
  music.list << Song.new('Here I Am', 'Al Green', 'Greatest Hits')
  music.list << Song.new('Ain\'t No Sunsine', 'Al Green', 'Greatest Hits')
  music.list << Song.new('Until It Sleeps', 'Metallica', 'Load')
  music.list << Song.new('Bleeding Me', 'Metallica', 'Load')
  music.list << Song.new('King Nothing', 'Metallica', 'Load')
  music.list << Song.new('Shiver', 'Maroon 5', 'Songs About Jane')
  music.list << Song.new('This Love', 'Maroon 5', 'Songs About Jane')
  puts music.sort_by_title, '----'
  puts music.sort_by_artist, '----'
  puts music.sort_by_album, '----'
end
# End test code

Now things like list.sort_by_artist and list.sort_by_title will call
method missing, and any new meta-data can be sorted by without
changing the list code.

Ryan

···

On 4/20/06, Molitor, Stephen L <Stephen.L.Molitor@erac.com> wrote:

Would you (or
someone else) care to give examples of 'Replace Similar Methods with
Metaprogrammed Definitions' and a 'Convert a Family of Methods to a
Single method_missing()'?

Rather than mucking around with method_missing, let's just extend the
language so method names are regular expressions:

class MusicList
  def /sort_by_(.+)/ (*margs) # "margs" == matches as args
     @list.sort_by { |song| song.send(margs[0])
  end
end

Method name resolution might be hell, though....

[snip example]

Right, or even more trivially:

def a=( value )
   @a = value
end

def b=( value )
   @b = value
end

def c=( value )
   @c = value
end

To:

attr_writer :a, :b, :c

James Edward Gray II

···

On Apr 20, 2006, at 11:04 AM, pat eyler wrote:

On 4/20/06, Molitor, Stephen L <Stephen.L.Molitor@erac.com> wrote:

James,

Great summary. Wish I had participated in the quiz!

I liked Dave Burt's 'Removed Unused Scope' example. Would you (or
someone else) care to give examples of 'Replace Similar Methods with
Metaprogrammed Definitions'

I'm not sure what James had in mind, but I think this works well as an
example of post refactoring 'Replace Similar Methods with
Metaprogrammed Definitions':

I would rather have something like
class MusicList
   sortable_attr_accessor :song, :duration # does everything
                                          # attr_accessor used to do, but
                                          # also defines #sort_by_song,
            # etc.
end

or
class MusicList
    can_sort_by :song, :duration
end

I guess what I'm saying is I'd rather have explicitly defined generated methods than one method that alters it's behavior based on its name. Of course these methods would not prevent you from sorting by anythiong else or even still having the method_missing in there, I just think it looks cleaner, in this case.

···

On Apr 20, 2006, at 12:33 PM, Matthew Moss wrote:

Rather than mucking around with method_missing, let's just extend the
language so method names are regular expressions:

class MusicList
  def /sort_by_(.+)/ (*margs) # "margs" == matches as args
     @list.sort_by { |song| song.send(margs[0])
  end
end

Method name resolution might be hell, though....

That's what I get for trying to make up an example :wink:

Keep in mind this was just an example and may not be the best example.
Maybe the ActiveRecord find methods which use method_missing are a
better example. Of course I'm sure they could be implemented some
other way as well.

Ryan

···

On 4/20/06, Logan Capaldo <logancapaldo@gmail.com> wrote:

I guess what I'm saying is I'd rather have explicitly defined
generated methods than one method that alters it's behavior based on
its name. Of course these methods would not prevent you from sorting
by anythiong else or even still having the method_missing in there, I
just think it looks cleaner, in this case.