In Progress
Unit 1, Lesson 1
In Progress

# Coincidental Duplication Redux

“DRYing up code is just a matter of comparing logic and extracting sections with duplication.”

Or is it?

In this episode we’ll revisit the concept of “coincidental duplication”. You’ll learn how to apply the Don’t Repeat Yourself principle when you have two independent pieces knowledge which are superficially similar.

Video transcript & code

The other day I did an episode on coincidental duplication. To be perfectly honest, I wasn't completely happy with the example I used. Then Katrina Owen sent me an example which I thought was such a perfect demonstration of coincidental duplication and over-DRYing code that I decided to revisit the topic.

Let's say we have some code for calculating the dates on which certain holidays fall in Norway. There are methods for calculating Mother's Day and Father's Day for a given year, as well as for determining when the next Mother or Father's day will occur. Finally, there are some helper methods that the other methods use.

You may note that the code for determining `#next_fathersday` and `#next_mothersday` is substantially similar. This is the kind of repetitive pattern we usually look for when refactoring.

``````require 'date'

# in Norway
module Calendar
module Holiday

FEBRUARY = 2
NOVEMBER = 11

def mothersday(year)
second_sunday_in(FEBRUARY, year)
end

def fathersday(year)
second_sunday_in(NOVEMBER, year)
end

def next_mothersday(today = Date.today)
this_years = mothersday(today.year)
if this_years >= today
this_years
else
mothersday(today.year + 1)
end
end

def next_fathersday(today = Date.today)
this_years = fathersday(today.year)
if this_years >= today
this_years
else
fathersday(today.year + 1)
end
end

def second_sunday_in(month, year)
# Day number 8 always falls within
# the second week of the month
second_week = Date.new(year, month, 8)
second_week + days_until_sunday(second_week)
end

def days_until_sunday(date)
(7 - date.wday) % 7
end

end
end
``````

Now let's take a look at a refactored version of this code, in which the logic for calculating Father's Day and Mother's Day has been combined into a single method called `#date_for_parentsday`. The `#date_for_mothersday` and `#date_for_fathersday` methods are both implemented in terms of this combined method.

``````require 'active_support/all'

# in Norway
module Calendar
module Holiday

def date_for_mothersday(today = Time.now.utc)
date_for_parentsday(2, today)
end

def date_for_fathersday(today = Time.now.utc)
date_for_parentsday(11, today)
end

def date_for_parentsday(month, today)
# Day number 8 will guarantee the second Sunday of the month
close_date = Time.utc(today.year, month, 8)
possible_parentsday = next_sunday_after(close_date)
if possible_parentsday >= today.beginning_of_day
actual_parentsday = possible_parentsday
else
actual_parentsday = next_sunday_after(close_date + 1.year)
end
return actual_parentsday
end

def next_sunday_after(time)
return time + ((7 - time.wday) % 7) * 1.day
end

end
end
``````

Is this code better? Well, it's certainly shorter. But that's about all I can say for it. First off, nobody says "Parent's Day". It's not a recognizable domain concept, so the code is already suffering from reduced readability as a result of unfamiliar, over-genericized terminology.

I don't know about you, but just looking at this code, I found the old version more readable. And I think the fundamental reason is this: Mother's Day and Father's Day are two separate concepts, each with their own rules for when they fall in the calendar. The fact that the logic for applying those rules may be substantially similar is a coincidence: it does not reflect a shared concept that both Mother's Day and Father's Day depend on.

As such, I feel that the original code is more intention revealing. And because the duplication is coincidental, the original code is no less DRY.

The moral here is this: DRYing up code isn't just a matter of mechanically comparing logic and extracting any sections with duplication. The Don't Repeat Yourself principle is about having a single home for each discrete piece of knowledge. If you have two pieces of independent knowledge, they should have two different homes - even if those homes look superficially similar.

That's it for today. Happy hacking!