The Method of Abstraction I: Single Responsibility Principle (SRP)
Part of my series of easy primers on methods to eliminate code smell: needlessly lengthy or complex code.
Code smell: You have methods that are performing multiple separate tasks.
In my last post, I dove into a very theoretical and abstract topic (the Golden Ratio). Today, I’m writing about a more down-to-earth abstraction: abstracting methods (a.k.a. functions).
One of the most important things you can do to make your code more elegant doesn’t come from a method provided by Ruby, but from the methods you provide for yourself — namely, how you organize them.
Two of the fundamental principles of elegant code are the Single Responsibility Principle (SRP) and DRY (Don’t Repeat Yourself). They are as simple as the code they aim to inspire:
1) Whenever you notice yourself writing the same code more than twice, you should separate it into its own method. (DRY)
2) Every unit of code should serve a single, discrete purpose. (SRP)
It wasn’t until I came to Flatiron School that I heard of these principles and the concept of breaking up methods into smaller pieces. When I did, it was a revelation; I had to completely refactor how I thought about and approached programming.
In Ruby, the ideal goal is methods that are 5 lines long or less. My eyebrows would have shot up if you had told me this when I was first starting to learn coding. How can the complexity and breadth of web apps be reduced to five-line methods?
With good organization, which is particularly important for complex programs. Shorter methods mean more versatile, readable code. They mean code that takes less time to read, understand, debug and adapt to changing purposes. This is why the two principles above — which naturally lead to shorter, cleaner code — are so important.
In Ruby, methods should be approximately 5 lines or less.
the single responsibility principle (SRP)
Normally, the SRP is mentioned in reference to classes: the blueprints for each individual piece of the diorama that is an object-oriented program. (In an online shopping site, for example, the classes would include a user, an item, a cash register, and so on.) But the principle also applies to methods, and, for the sake of keeping the example small, that’s what I’ll focus on in this post.
This principle states that each building block of a program — be it a class or a method — should serve one discrete purpose. For the hashketball lab, Flatiron’s curriculum team had already pre-defined our methods for us, and, unsurprisingly, they were already structured to fall well in line with the SRP. So I wrote a method for the sake of demonstrating something that does not follow the Single Responsibility Principle:
To the untrained eye, this method to access player stats doesn’t look bad at all. And yet it has fallbacks — the greatest being that it’s not very flexible to change. If the structure of the players hash were modified, it may require rewriting the entire method. We’d also need to change this method if a single hash key were renamed, or if we wanted to print out the player stats in a different format.
The purpose for the Single Responsibility Principle is flexibility, and the core idea behind it is that “a class/method should only have one reason to change.” In other words, each method should only fulfill a single task, and should only require adaptation if we decide to tweak that single task. This method, on the other hand, performs at least two: getting player information, and printing it out.
Instead, we want to separate these two tasks into separate methods:
In this example, each of our two methods does a single, discrete thing. And notice the use of #tap, one of the handy methods I covered in another post from this series? If you missed the blog, you can read it here.
In this refactor, we have one method that get player stats by name and returns them as a hash, and a second method that focuses on printing them out.
The purpose for the SRP is flexibility: when each method only performs a single task, it is less likely to be dependent on small changes in our program.
Incidentally, combining these functions into a single method is also more repetitive in the long run. No doubt getting player stats is a common need in whatever program this method would be a part of; by putting that logic within a method that’s ultimately designed to do something totally different (print out information), we’ll then need to repeat the same fact-finding logic in every other method that needs to get player data.
This leads nicely into our next principle, DRY. In Part II, we’ll move on to the hashketball lab itself, which defined discrete methods, but left their content up to the students to figure out.