If you are like most developers, this is your guy ☝
He's supposed to make your code easy to change, easy to understand and easy to reuse.
But all he does is to confuse everyone and touch everything around him. To make things worse, he's not alone.
Let's see how we can tame these fellas by recognizing single responsibilities.
Following below is a loose summary of chapter 2 from one of the greatest books on practical object-oriented design (don't miss out, whatever programming language background you have, these are pure fundamentals explained awesomely).
Cohesion and Single Responsibility Principle
Highly cohesive classes are said to be easy to change and reuse. They do just one small thing, i.e. they have a Single Responsibility.
Interrogate your classes
You will like this technique, especially if you are a rubber duck programmer.
Really, try talking to them. It's said to nurture your creative energy and problem-solving.
Listen closely. If you hear alarming signal words like "and" sprinkled with "or", you have your hint on where to look next.
If you don't get an answer yet, shoot that guy and move on to check your code for more hints.
Start even smaller
Classes should have a Single Responsibility, yes. But to make sense out of your classes you'll need to take a look at the methods, those are actually the folks that take action. Classes are more obvious in nature, that's why everyone's talking about them. But what really makes your design are messages that you pass around your classes and objects by using methods.
Often methods do more than one thing, so here can be a good place to start debunking your class. If you gave up on interrogating your class (🔫), then you can move on to the methods.
When you reform your class to consist of small cohesive methods, oftentimes you end up getting to understand which single responsibility your class should have and what's redundant or in the wrong place.
Hide data structures
Using raw data structures in your classes can lead to headaches when changing code.
When you create classes that have a single responsibility, every tiny bit of behavior lives in one place. This should also be true when you work with data. If you see yourself doing a lot of accessing Arrays in many places then most probably it's time to hide the structure of your data object because you are not following the DRY principle.
Take this as an example:
class Videogame
attr_reader :data
def initialize(analytics_data)
@data = analytics_data
end
def game_price
calculate(data[0][0])
end
def game_popularity
calculate(data[0][0])
end
end
Here, the analytics_data
has the relevant data stored in a variable for us hidden somewhere in a 2-dimensional array. You can wrap your data structures inside a method that will serve as a single point of truth of how to extract that data.
In our game example you could do something like...
class Videogame
attr_reader :metrics
def initialize(analytics_data)
@metrics = deconstruct(analytics_data)
end
def game_price
calculate(metrics)
end
def game_popularity
calculate(metrics)
end
def deconstruct
data[0][0]
end
end
So now, if the definition of how to retrieve the metrics ever changes (it probably has already changed 😉), you will only have to change the deconstruct
method and not the 100 definitions of data[0][0]
that are all over the code.
Practice
Now you can start practicing the curiosity described above to handle questions like...
- what to put in an individual class?
- what extract from a class?
- what other classes to have along with it?
But don't get too bugged down by The Fear of Not Designing while coding.
With SRP in mind, these questions become more intuitive with time.
In the next part, we'll look at how to tackle dependent classes and dependencies in general.