http://cleancoder.posterous.com/].
The five principles if used judiciously should result in code that is easier to maintain by being highly decoupled and allow the changing of specific implementation details without (or with less) friction.
Like every principle/guideline in software development the SOLID principles need to be understood but not used blindly. It is very easy to over architect a solution by being too dogmatic about the use of any guideline. You do however, need to be aware when a violation of SOLID principles occurs and make that decision based on its context and merits.
Robert Martin describes the Single Responsibility Principle (SRP) as: “A class should have only one reason to change”(1). I think the best way to get our heads around this concept is to view some code. So let’s consider the following example which is a business rules object that defines how jobs are handled in an issue tracking system.
class JobHandler(db, query_engine, email_sender): this.db = db this.query_engine = query_engine this.email_sender = email_sender def add_job(job): this.db.add(job) def delete_job(job): this.db.delete(job) def update_job(job): this.db.update(job) def email_user_about_job(job): this.email_sender.send(job.get_html_details(), job.user.email) def find_all_jobs_assigned_to(user): return this.query_engine.run("select all jobs assigned to: ", user) def find_all_completed_jobs(user): return this.query_engine.run("select all jobs with status: ", "completed")
So, what is the jobs handler doing?
Let’s critically review this code. What can we see?
I think it’s clear that the above object has 3 obvious responsibilities these are:
So perhaps a better design would be something like:
class JobRepository(db): this.db = db def add_job(job): this.db.add(job); def update_job(job): this.db.update(job); def delete_job(job): this.db.delete(job); class JobFinder(query_engine): this.query_engine = query_engine def find_all_jobs_assigned_to(user): return this.query_engine.run("select all jobs assigned to: ", user) def find_all_completed_jobs(user): return this.query_engine.run("select all jobs with status: ", "completed") class JobWorkFlow(email_sender): this.email_sender = email_sender def email_user_about_job(job): this.email_sender.send(job.get_html_details(), job.user.email)
So let’s critically analyse this code.
Conclusion? Well there really is no conclusion. It is important to realise that this is a trivial example whose responsibilities were obvious. Many times separating concerns is not as easy and decoupling these concerns may be very difficult.
In the example above I would comfortably say that the refactored code is better than the original code but this may not be the case with a real world example. Now when you see a class that as; low cohesion, too much responsibility, too many reasons to change, too many dependencies, etc. You can recognise this as a smell and violation of the SRP. You can then make the educated decision as to whether refactoring the code will result in better, cleaner more maintainable code.
On the other hand, refactoring is a hard process and the more you do it the easier it becomes, so do not be scared to take a little bit of time to refactor something like this. You will find that the case for not fixing SRP violations will become less compelling.