PRY - Please Repeat Yourself

by on

It's the canonical ruby metaprogramming example. You have this:

def large_image
image_base + '-large.png'

def medium_image
image_base + '-medium.png'

def small_image
image_base + '-small.png'

And you do some dynamic programming to define the methods with similar content:

%w(large medium small).each do |size|
class_eval %{
def #{size}_image
image_base + '-#{size}.png'

I have two problems with this pattern:

1) The dynamic code is far less readable and clear. It might even need a comment! The static code is very easy to read.

2) The dynamic code gives terrible stack traces
(eval):3:in `small_image': undefined local variable or method `image_base' for # (NameError)
from test.rb:28
Versus the static stack trace:
test.rb:15:in `small_image': undefined local variable or method `image_base' for # (NameError)
from test.rb:28
Here, test.rb:15 is the line inside "small_image" that calls "image_base".

The argument for metaprogramming is so that there is only one place to change the content. However this is easily accomplished with a helper. In fact I already had that helper in place, image_base. If you find yourself repeating the code *inside* the method, it's worth extracting into a helper.

Metaprogramming is for a dynamic system, not for enumerations. For example, Rails controllers use dynamic programming to allow you to define your own action methods. These methods are called dynamically by the router. This is a huge oversimplification, but the idea is that the Rails authors have no idea what method names could be used, so they can't type them all out. It's a fundamentally different problem.

If you find repetition in coding to be a difficult task, consider investing some time in learning a powerful editor with a regular expression matching and replace function.

So, please repeat yourself!
blog comments powered by Disqus