Taking DRY Too Far
Taking DRY too far is easy. A line of code appearing identically in two places in a class or codebase does not necessarily mean the duplication needs to be removed. Here is an example:
An over-zealous developer may look at this snippet and see the first line in each action is duplicated. He or she might do this:
Here is a good example of a code that actually violates DRY.
Think before deciding code is violating DRY. Ask yourself if the code is really hurting maintainability. As with most principles, DRY has a gray area - just be careful not to take it too far.
Related: Shane Harvie blogged about abusing DRY in a post on Moist Tests yesterday.
class PeopleController < ApplicationController def list @people = Person.find(:all) end def show @person = Person.find(params[:id]) end def destroy @person = Person.find(params[:id]) @person.destroy redirect_to people_url end end
An over-zealous developer may look at this snippet and see the first line in each action is duplicated. He or she might do this:
class PeopleController < ApplicationController def list @people = Person.find(:all) end def show @person = find_person end def destroy @person = find_person @person.destroy redirect_to people_url end protected def find_person Person.find(params[:id]) end endAdding the find_person method is wrong. This is not what the DRY principle is about. Also, the show and destroy actions still have the same duplication, just in a different way. This change could also hurt maintainability. The show and destroy actions may want to find the person in a slightly different way. That may sound extremely hypothetical, but a scenario is if the show action should only display certain records, while any record can be deleted. Perhaps to remove the calls to find a developer could do this:
class PeopleController < ApplicationController before_filter :find_person, :only => %w[show destroy] def list @people = Person.find(:all) end def show end def destory @person.destory redirect_to people_url end protected def find_person @person = Person.find(params[:id]) end endEven though that duplicated line of code is gone, we're now abusing filtering (if you disagree, I'll save details on that for another post). Finding the person to use in the action should not be done in a filter that runs before the action - it belongs in the action. The first code snippet in this post is the best - no before_filter, no private method.
Here is a good example of a code that actually violates DRY.
class SimpleCalculator def initialize(verbose = true) @verbose = verbose end def add(first_number, second_number) result = first_number + second_number puts "#{first_number} + #{second_number} is #{result}" if @verbose result end def substract(first_number, second_number) result = first_number - second_number puts "#{first_number} - #{second_number} is #{result}" if @verbose result end endNotice how both of the "puts" lines end with "if @verbose". This violates DRY; it hurts maintainability. Every time we add a "puts" in this class, we need to tack on "if @verbose". Here is a much better solution:
class SimpleCalculator def initialize(verbose = true) @verbose = verbose end def add(first_number, second_number) result = first_number + second_number say "#{first_number} + #{second_number} is #{result}" result end def substract(first_number, second_number) result = first_number - second_number say "#{first_number} - #{second_number} is #{result}" result end protected def say(message) puts message if @verbose end end
Think before deciding code is violating DRY. Ask yourself if the code is really hurting maintainability. As with most principles, DRY has a gray area - just be careful not to take it too far.
Related: Shane Harvie blogged about abusing DRY in a post on Moist Tests yesterday.
Posted on 2007-07-27 | permalink | del.icio.us
Blog Archive
