Re: Refactoring (#75)


#1

James,

Great summary. Wish I had participated in the quiz!

I liked Dave B.'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


#2

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.


#3

On 4/20/06, Molitor, Stephen L removed_email_address@domain.invalid wrote:

James,

Great summary. Wish I had participated in the quiz!

I liked Dave B.'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

designed with the static languages in mind. I’m very intrigued by how a
I don’t think we have one. We can #freeze the object, but that won’t

      def foo
        bar

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 M. is back with a quiz about txet smnraiclbg…


thanks,
-pate


#4

On 4/20/06, Molitor, Stephen L removed_email_address@domain.invalid 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()’?

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


#5

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

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’:

[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 G. II


#6

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…


#7

On 4/20/06, Logan C. removed_email_address@domain.invalid 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.

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


#8

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

Method name resolution might be hell, though…
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.