Saturday, January 26, 2008
Write Only Ruby
For some reason, DRY and "type less" seem to have become equivalent to many in the the Ruby community. I believe that the essence of DRY is when an application change is localized to one area of the codebase. While DRY is important, it's no more important than maintainability and readability. Therefore, if logic is stored in one and only one location, but that location cannot be found or understood, you've traded maintainability and as a result have gained nothing.
But, I've grown tired of this debate. No one ever wins, instead people just get tired of listening so they stop interacting. If you want to define methods of a module in a loop that later gets included in 3 classes that have nothing else in common, you have the right to do that. In fact, I'm excited about helping you.
Here's how DRY your code could be.
That's some DRY code, perhaps even Extremely DRY (EDRY). Code isn't any good unless it runs.
It runs without problem. Dying for the implementation? No worries, here it is.
Of course, any EDRY code will be write only. It would be painful to try to actually read code written in this way, but that's already true of the 150 modules that each contain only one method definition. The difference is generally it's not immediately obvious when you first look at a codebase that has been DRYed up for the sake of keystroke savings. If the implementer instead chose to utilize EDRY, I'd know what I was getting into from the first file opened.
But, I've grown tired of this debate. No one ever wins, instead people just get tired of listening so they stop interacting. If you want to define methods of a module in a loop that later gets included in 3 classes that have nothing else in common, you have the right to do that. In fact, I'm excited about helping you.
Here's how DRY your code could be.
# what's the sense in writing the class name?
# you already wrote it once when you named the file
C do
# attr_accessor clearly has 12 too many characters
a :first_name, :last_name, :favorite_color
# include is short, but i is good enough
i Enumerable
# ctor defines a constructor
ctor {|*args| s.first_name, s.last_name = *args }
# d allows you to define any method by calling it from d.
# you can also chain the calls together to if you have several methods that are similar.
d.complete_info? {first_name && last_name && true }
d.white?.red?.blue?.black? {|color| self.favorite_color.to_s == color.to_s.chomp("?") }
endThat's some DRY code, perhaps even Extremely DRY (EDRY). Code isn't any good unless it runs.
Foo < Enumerable # => true
f = Foo.new("Mike", "Ward") # => #<Foo:0x1e6f4 @first_name="Mike", @last_name="Ward">
f.first_name # => "Mike"
f.last_name # => "Ward"
f.complete_info? # => true
f.red? # => false
f.favorite_color = :red # => :red
f.red? # => trueIt runs without problem. Dying for the implementation? No worries, here it is.
name = File.basename(eval("__FILE__", block.binding),".rb")
klass = eval "class ; end; ", binding, __FILE__, __LINE__
klass.class_eval(&block)
end
self
end
end
define_method :initialize, &block
end
include mod
end
DefineHelper.new(self)
end
attr_accessor(*args)
end
end
@klass = klass
end
@method_stack ||= []
end
method_stack << sym
if block_given?
method_stack.each do |meth|
@klass.class_eval do
define_method meth do
instance_exec meth, &block
end
end
end
end
self
end
end
# http://eigenclass.org/hiki.rb?instance_exec
mname = "__instance_exec__"
Object.class_eval{define_method(mname, &block) }
begin
ret = send(mname, *args)
ensure
Object.class_eval{undef_method(mname) } rescue nil
end
ret
end
endOf course, any EDRY code will be write only. It would be painful to try to actually read code written in this way, but that's already true of the 150 modules that each contain only one method definition. The difference is generally it's not immediately obvious when you first look at a codebase that has been DRYed up for the sake of keystroke savings. If the implementer instead chose to utilize EDRY, I'd know what I was getting into from the first file opened.


