This is the second in a series of (hopefully) amusing posts into Robert Martin’s SOLID principles. It’s written as a dialog between Socrates and a student named Loej in the spirit of the dialectic method popularized by Plato. In this post, the principle explored is the “Single Responsibility Principle”. I hope you find this enjoyable.
Socrates: Welcome again Loej. Last time we agreed that software development is, as Steve McConnell puts it, a wicked problem.
Loej: Right. We have to solve software problems again and again to get them right. This means change is an inherent part of the software development process.
Socrates: And we agree that when we write code, we must anticipate change in order to manage complexity.
Loej: Right and managing complexity is the fundamental concern of software engineering.
Socrates: Very good. When we talked before you said that in your experience, even a simple change can be difficult to make and may cascade into unrelated changes.
Loej: Right. I remember when our company went international we had to add a shipping country for just about everything. Products, customers, you name it. I mean dragging the new fields to our forms was easy enough. Master are you OK?
Socrates: (Shivers) Continue…
Loej: But then we had to change the database code in the Customer form, the Product form, and the Supplier form. And we had to change our validation controls on all the forms.
Loej: We introduced a defect. But at first we thought it was a bug with our printing object.
Socrates: Your printing object? How’s that?
Loej: We had an object which printed everything out. It did things like PrintW2, PrintCustomerInvoice, PrintAddressLabel, stuff like that. All of a sudden invoices and address labels stopped printing!
Socrates: Did you track it down?
Loej: Yes after a few long days. It turned out that we were writing a null to the customer table and when we retrieved the customer from the database in the Printer object, we got back a null as well. This null was being appended to the address string and this caused the printer to not print!
Loej: Yeah bad memory.
Socrates: No pun intended?
Loej: Hmm? Oh, no.
Socrates: I see. Let’s start at the beginning.
Loej: Sounds good.
Socrates: You had one user directed change, to internationalize shipping, but this change required you to change some really big classes.
Socrates: And in doing so you introduced a defect.
Loej: Right again.
Socrates: And the defect was originally thought to be in the Printer object, which was also a large class which did all things printer related.
Socrates: If the printer class had been simpler, would it have been easier to debug? Easier to test?
Loej: Well, that code would just need to be somewhere else.
Socrates: Right, but would you have had to look so hard to find the defect?
Loej: You mean if we had a class whose job was to just print customer invoices? And a class just to print address labels?
Loej: But wouldn’t this violate the don’t repeat yourself principle, you know, DRY? I mean wouldn’t we need a bunch of code repeated in each of those little classes? And doesn’t a bunch of little classes add complexity which we were trying to mitigate in the first place?
Socrates: Good questions. Let me address your first concern, DRY. Why do you think it would be violated?
Loej: Well, we had methods in the printer object that formatted strings and checked that columns were correct and all that stuff. All of these things relied on shared methods in the printer object. We’d have to duplicate that in every new object.
Socrates: Would you really?
Loej: Oh, you’re suggesting we could keep those things in the printer class?
Socrates: Or a couple new classes.
Loej: OK I get it.
Socrates: Right. So let’s not worry about DRY. Now about your second concern, bunches of little classes.
Loej: Right, we would have like 20 classes instead of one.
Socrates: And this adds complexity?
Loej: Totally. I mean who can keep track of 20 things in their head.
Socrates: Would you rather keep track of 20 classes or 20 methods of one class? Isn’t this really the same thing?
Loej: Well, there are a lot more files around.
Socrates: Could you use folders to simplify that? And don’t multiple small files work better with your revision control system anyway? And don’t modern IDEs take care of this?
Loej: Points taken. But automatic completion would become a mess…
Socrates: What about using namespaces to handle that?
Loej: Ok. So there’s not much difference in terms of complexity between twenty methods and twenty classes in modern software development with modern IDEs.
Socrates: And actually using namespaces and folders you can simplify things more with separate classes than you can a single monolithic object. Let’s move on then. We agreed earlier it’s easier to find defects if they are more focused.
Socrates: Would it be easier to not inadvertently introduce defects if objects were smaller and more focused?
Loej: I suppose so.
Socrates: So would it make sense to divide up your validation logic, display logic, data logic, and business logic into separate classes?
Loej: Well it would have made it possible to do some testing.
Socrates: Taking this idea further, what if each class had a single responsibility. If each class just did one thing?
Loej: That would make it easier to test. But how do I know when I’m done dividing things up? How do I know when I’ve got the right level of granularity? I mean, our printer object only handled printing. And our forms only did one thing. Like the supplier form handled suppliers. I mean at some level of abstraction, everything only does one thing.
Socrates: Well what do you mean when you say the printer object only handled printing?
Loej: In the case of the printer object it meant that it only handled printing. It printed invoices and it…
Socrates: Hold up!
Socrates: You said the word “and”. I think that’s a good sign you are breaking this rule when describing a class.
Loej: You mean that if I use the word “and”, I have more than one responsibility?
Loej: OK I’ll buy that. But how do I know if I have the right responsibility?
Socrates: That’s going to depend on your domain model and a second rule of thumb.
Loej: What’s that rule?
Socrates: According to Robert Martin who formulated these SOLID principles, each class should have one and only one reason to change.
Loej: That sounds arbitrary and academic.
Socrates: Well, but what would it mean if we had two reasons to change?
Loej: I suppose it meant that the class did more than one thing. It had two responsibilities.
Socrates: Right. So either we planned for the class to have two responsibilities which we have decided is not a good idea, or we didn’t understand the class well enough in the first place because we didn’t see that there were two responsibilities – two reasons to change.
Loej: I guess it makes sense. Still, I can think of some cases this wouldn’t be good practice or good design.
Socrates: Sure. SOLID should help us think about good software design but they are principles of good design and not laws. Your domain model requirements should sometimes take precedence.
Loej: So what you are saying is to keep this idea in mind as I code. This will lend my design to looser coupling, easier testing, and smaller more focused objects.
Socrates: Very good. Now go and meditate deeply on the letter S until we speak again.
Loej: Yes master!