It is easy to see code duplication and eliminate it during the refactoring step. However, it is difficult to see the data duplication in code that could be a sign of hidden abstraction. We must reflect about data duplication and consider expressing the hidden domain concept in a class during the refactoring step. You might also accomplish this by aiming for low semantic gap in the solution. The language we use to describe a problem affects the code quality.
I discuss this in depth in my upcoming TDD in Ruby book scheduled for release in March 2017 by Apress. In this book I review the solution by Anderson Dias for Conway's Game of Life found at: https://github.com/andersondias/conway-game-of-life-ruby. The cell object has a neighbors method that looks like this:
class Cell def neighbours neighbours =  neighbours.push(@world.cell_at(self.x - 1, self.y - 1)) neighbours.push(@world.cell_at(self.x - 1, self.y)) neighbours.push(@world.cell_at(self.x - 1, self.y + 1)) neighbours.push(@world.cell_at(self.x, self.y - 1)) neighbours.push(@world.cell_at(self.x, self.y + 1)) neighbours.push(@world.cell_at(self.x + 1, self.y - 1)) neighbours.push(@world.cell_at(self.x + 1, self.y)) neighbours.push(@world.cell_at(self.x + 1, self.y + 1)) neighbours end def live_neighbours self.neighbours.select do |n| n && n.live? end end end
In this case, you cannot replace the hard coded 1 with a constant and call it a day. This solution suffers from Primitive Obsession, since the location is accessed using integer values. The domain specific concept, Location is missing. The x and y stores data and is duplicated. What does x and y represent? We need to capture that abstraction and hide the data in x and y within the class. My solution uses Location object that looks like this:
class Location NORTHWEST = [-1, 1] NORTHEAST = [1, 1] SOUTHWEST = [-1, -1] SOUTHEAST = [1, -1] CENTER = [0, 0] NORTH = [0, 1] SOUTH = [0, -1] EAST = [1, 0] WEST = [-1, 0] OFFSETS = [NORTHWEST, NORTHEAST, SOUTHWEST, SOUTHEAST, NORTH, SOUTH, EAST, WEST] def self.add(first, second) [first+second, first+second] end end
The combination of a specific pair of x and y represents a specific location in the Moore's neighborhood. You can also see the Location class provides a closed operation, the add method.
Where it fits, define an operation whose return type is the same as the type of its arguments. Such an operation is closed under the set of instances of that type. A closed operation provides a high-level interface without introducing any dependency on other concepts. This pattern is most often applied to the operations of a Value Object.
-- Eric Evans, Domain Driven Design
The location class now provides us a place to allocate related responsibilities. I discuss these concepts in more depth in the book. Subscribe to my newsletter if you want to be notified about the discount coupon codes for the upcoming book.