I have been coding for many years. I read many books about clean coding and practising them myself. I found that the rule for clean code is Decoupling. Another the name is Isolation. You might know Big Ball of Mud. That is about architecture. That being said, there are a lot of connections in a ball.
Many classes are coupling together. Many connections prevent us from expanding system. If we put new class in this ball. It's hard to choose the connection for this class. That is open/close principle. But what happen if we refactor code. That means we break the connection. Classes related other classes, relying on other classes. We could not calculate the boundary of impact. We even get stressful when look at this ball.
Thus, Isolation is the solution. Isolation is a word but makes us think about other techniques such as Naming, Domain Driven Design, Dependency Injection, Single Responsibility etc. We can't isolate code if:
- We don't know the boundary of Domain.
- We get hard to find a proper name for new class or method.
- We don't know how to make loose coupling. Cause we can't make isolation completely. Things should work together to make an application.
- The intensity of Isolation. We can just make 10% isolation.
Let's take a look on this code. We have two models:
Creating Guru is a take-time process. It might take 5 minutes to get done. However, we don't use socket for realtime effects. If process is in progress so users just see empty result and message 'In progress' for example.
class GuruListBuilder attr_reader :domain initialize(domain) @domain = domain end def all gurus end private def gurus create_gurus if domain.guru_at.nil? Redis.current.set(redis_key) != 'inprogress' ? Guru.all :  end def create_gurus Redis.current.set(redis_key, 'inprogress') domain.update(guru_at: Time.current) CreateGuru.call Redis.current.set(redis_key, 'done') end def redis_key 'create-guru' end end
Guru_at attribute to prevent second request calling create_gurus method. We use redis to temporarily set status. Now looking at this code we see it's a bit messy. With each class at first glance we should have a concept in mind. That is a sentence to describe this class in Single Responsibility Pattern (SRP). Thus, GuruBuilder for building Guru list. But in this code, the second responsibility appears. That is 'Create Process State' - this is a short sentence we need to figure out (SRP).
We should isolate them and make loose connection. Because they can't work together if no connection. We don't use Dependency Injection because the second responsibility does not change regularly. No need to make it as a instance to inject to first SR. So we isolate them by private method.
def create_process_state Gurus::CreateProcessState.new(domain) end
But we also want to make an instance like singleton in this class.
def create_process_state @process_state ||= Gurus::CreateProcessState.new(domain) end
However, what we can do with this messy code?
def create_gurus Redis.current.set(redis_key, 'inprogress') domain.update(guru_at: Time.current) CreateGuru.call Redis.current.set(redis_key, 'done') end
Regarding to SP, what we want for this method is:
def create_gurus domain.update(guru_at: Time.current) CreateGuru.call end
So we think about template method for CreateProcessState class.
def call if block_given? set_in_progress yield delete_state end end
We can see this is SP with set in progress and delete state. Because yield is not the implementation. Yield is like a parameter, and we don't treat parameters like responsibility but delegation. If we delegate to other person to do thing, it means that thing is not our business. Now we know another Pattern "Delegation". We use a ton of patterns to reach Isolation goal.
Thank for reading.