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.

# 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("?") }
end

That'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? # => true

It runs without problem. Dying for the implementation? No worries, here it is.

class Object
def C(base_class=Object, &block)
name = File.basename(eval("__FILE__", block.binding),".rb")
klass = eval "class #{name.capitalize}; end; #{name.capitalize}", binding, __FILE__, __LINE__
klass.class_eval(&block)
end

def s
self
end
end

class Class
def ctor(&block)
define_method :initialize, &block
end

def i(mod)
include mod
end

def d
DefineHelper.new(self)
end

def a(*args)
attr_accessor(*args)
end
end

class DefineHelper
def initialize(klass)
@klass = klass
end

def method_stack
@method_stack ||= []
end

def method_missing(sym, *args, &block)
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

module Kernel

def instance_exec(*args, &block)
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
Object.class_eval{ define_method(mname, &block) }
begin
ret = send(mname, *args)
ensure
Object.class_eval{ undef_method(mname) } rescue nil
end
ret
end

end

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.

7 comments:

  1. I don't like 'C', 'a', 'i', etc.
    We code Ruby, not C !

    However 'class' without name it's a great idea... but it must be a convention, not a restriction !

    In an other hand, writing one time the name of the class is not really a problem, it can't be compared to a database definition...

    ReplyDelete
  2. Fun post, with some good points, but short names are not a part of DRY, so the "C", "i", etc., are a strawman.

    ReplyDelete
  3. You know, as someone who is now writing APL for a living... I can really appreciate this post.

    But I've got to tell you: Nothing will stop people from being more terse than is beneficial.

    ReplyDelete
  4. I agree with Jeem. This post totally missed the point.

    ReplyDelete
  5. As Jeem pointed out the post was supposed to be fun. I couldn't agree more that DRY is not about short names or typing less. If anything, it was meant to point out that typing less is a silly way to see DRY.

    ReplyDelete
  6. Whenever I see code in a post of yours, it invariably gets pushed around by the Google ad, like so:

    bad formatting

    And you probably want it to look more like so:

    good formatting

    Just so you know.

    ReplyDelete
  7. Anonymous3:26 AM

    I'd actually argue that you're not taking it far enough. What's the point of comments? Good code should be self-documenting and intuitive to anyone within 5 seconds of looking at it.

    It really frustrates me to see great programmers get bogged down by comments and documentation efforts. If we wanted to be technical writers, we'd have majored in English, not CS!

    Also, your implementation is too long - more semi-colons, please!


    ...


    ;)

    ReplyDelete

Note: Only a member of this blog may post a comment.