This is another blog post in a series about using pen and paper for brainstorming, planning, and designing.
The Single Responsibility Principle is the most important principle of software design. It states that classes should be cohesive, or, in other words, should have only one reason to change. Though it may sound easy in theory, obeying SRP in practice can be difficult, even for experienced developers.
How many times have you tried to refactor out a responsibility from a god class? How many times have you succeeded? It’s hard. Primarily, because of the limitations of the brain. We cannot keep more than 3-4 things in our working memory. This means that understanding the ways in which 30 methods interact with each other is extremely difficult. And that’s when feature sketches can come in handy.
Take a look at this class:
class Account attr_reader :number, :balance, :name, :client, :status def initialize number, balance, name, client, created_at, status @number = number @balance = balance @name = name @client = client @status = status end def withdraw amount, description transaction = TransactionFactory.build(@number, -amount, description) TransactionRepository.save(transaction) @balance -= amount AccountRepository.save(self) end def deposit amount, description transaction = TransactionFactory.build(@number, amount, description) TransactionRepository.save(transaction) @balance += amount AccountRepository.save(self) end def transfer_to destination_account, amount, description withdraw amount, description destination_account.deposit amount, description end def transactions TransactionRepository.find_all_by_number(@number) end def can_withdraw? amount @status == :open && @balance >= amount end def close if @status != :close @status = :close AccountRepository.save(self) end end def reopen if @status != :open @status = :open AccountRepository.save(self) end end end
Though the Account class is only 50 lines long, and it’s public API consists of only 12 methods, it still takes some time to see what this class does and how it can be refactored.
Let’s see how feature sketches can help us with that.
Step 1. Draw Instance Variables
Start with drawing a circle for each instance variable of the class.
Step 2. Draw Methods
Next, do the same for each method of the class. To save some space I grouped all the attribute accessors inside the same circle. Then, draw a line from each method to the circles of the variables and methods it accesses or modifies.
Step 3. Identify Clusters
After you’re done, you should be able to see if there is any clustering in your class.
We identified two clusters: the first one is changing the status of an account, and the other one is responsible for working with transactions.
Step 4. Refactor
The last step is to move one of the clusters into a separate class. In our case, the Account class is an information holder and a service provider at the same time. The responsibility for building, finding and saving transactions can be easily refactored out into a service or a DCI context.
Feature sketches can really add to your understanding of the code. In this example, we managed to reduce the number of things to keep in our working memory from twelve down to three by identifying clusters of methods. Which considering the limitations of the brain can have tremendous value.
I’ve read about Feature Sketches in the book “Working Effectively with Legacy Code” by Michael Feathers. The book is full of great insights, and I highly recommend reading it.