Someone just told you your code isn’t DRY, and you have no idea what they’re talking about. You’re fresh out of college, and you’re starting to fear that your Computer Science degree left you woefully unprepared for the challenge of real-world software engineering.
“DRY means ‘Don’t Repeat Yourself.’ Look, it’s basically the same code in all of these methods,” your coworker tells you. Your coworker has been out of college for 9 months, so you take another look at your code. You’re writing an application to create widgets, and you’ve just finished a feature that made it possible to create a new type of widget.
class WidgetFactory
def foo_widget
Foo.new('widget')
end
def bar_widget
Bar.new('widget')
end
def baz_widget
Baz.new('widget')
end
def fuz_widget
Fuz.new(‘widget’)
end
end
On closer inspection, you agree with your coworker that there’s a definite pattern, but you can’t imagine what you could do about it. “We could use a little bit of metaprogramming to DRY this up,” your coworker says, and then blows your mind with the following refactoring:
module WidgetMethodGenerator
def widgets(*widget_types)
widget_types.each do |widget_type|
define_method "#{widget_type}_widget" do
widget_type.classify.constantize.new 'widget'
end
end
end
end
class WidgetFactory
extend WidgetMethodGenerator
widgets "foo", "bar", "baz", "fuz"
end
You had never before imagined that Ruby had such power. Suddenly you can start to fathom how all of those magical methods in Rails must be implemented. And your WidgetFactory code is so clean! It will be so easy to create the factory method for the next widget type that comes along! Your coworker is so pleased to have shown you the secret, magic power of metaprogramming. You’re off and away. Oh, the places you’ll go!
__________________________________________________________
Three years have passed. You’re still working on the same application. You’ve spent the last year refactoring away all of the meta-programming madness of your first year. You shake your head at your former self every time you bump into a needless define_method, method_missing, or instance_eval.
Your product owner asks you to implement a new feature in your application: force your users to confirm every widget creation with a captcha. For the past several months, your application has been plagued by widget spam bots.
You’re pair programming with a new college grad on implementing the confirmation modal when you write the following cucumber step definition:
When /^you create a new widget and confirm with a captcha$/ do
fill_in “Widget Description”, with: “foo bar”
click_button “Submit”
within(“#widget_confirmation_modal”) do
fill_in “#captcha”, with: “HERP DERP”
click_on “Confirm”
end
end
After you and your pair implement the new feature and get your acceptance test passing, you run your entire build – only to discover that a hundred other features have broken. It turns out there were a lot of tests that created a widget, and when you dig into them, you realize they all do it slightly differently. Some of them `click_button “Submit”`. Others `click_link_or_button “Submit”`. Still others `find(“#widget_submit”).click`. And on and on.
The knowledge of how to create a widget in the UI was smeared throughout your test suite. It dawns on you that DRY isn’t about the repetition of structure, it’s about the duplication of knowledge. In this case, the more code that knew about how to create a widget, the more difficult it became to change the way widgets are created.
Your pair learns this lesson with you. You hope it’s better than the lesson you learned when you were fresh out of college. Oh, the programs you’ll DRY!