SOLID Principles: S is for Single Responsibility

Imagine you were writing the code modelling a car.  One you can drive, from the 1960s before they got too complex.  What does it look like?

class Car
{
    public void Accelerate();
    public void Brake();
}

Now, ignoring the fact that I should have started with an interface (and you can’t steer), there’s something very dangerous in this code.  It’s the assumption that a car is an object.  It’s not, it’s a phenomenally complex interlinking of components.  The driver may see it as one object, but you can be assured the manufacturer does not.

So what’s actually going on when you accelerate a car?  Well, for a start, you press down the accelerator pedal.  That’s the actual entry point to the system.  The Accelerate method is, at best, a simplification on top of that.  This opens a valve that lets petrol into the engine.  What happens then is extremely dependent upon the state of the engine.  For a start, if it’s not running, not very much is going to happen.  Indeed, your car will need some mechanism to prevent you from flooding the engine when it’s off.

We’re already creating an extremely complex object and we haven’t even started building code for the engine pistons yet.  It’s going to become a complete mess really quickly.  Unless, that is, we actually have objects that represent the physical components of the car.

The single responsibility principle is just a formalization of what we’re talking about here.  It says that a class should only do one thing and only that.  Valves should be represented by Valve objects, not by the Engine class or (shudder) implicit in the workings of the acceleration pedal.  Valve objects can then be reused for any similar valve anywhere in the car, or indeed in a power station model if it uses the same sort of valves.

Going back to how I learned to do object oriented programming, the Turbo Pascal User Guide had an example that featured a Line object.  It had properties for start point, end point and a method to draw it on the screen.

WHAT?

I can’t believe I didn’t notice how insane this was the first time I read it.  Let’s assume we’re using constructor injection.  So, to create a line object, I have to pass it a rendering context?  How about if I was trying to use it for visibility detection in a 2D maze?  I’m never going to draw the line, in fact the whole point would be that I’m not drawing the line.  And how about anti-aliasing?  Are you really going to put the logic for anti-aliasing into your line class?  It’s going to get pretty big if you do.

To put it more formally, rendering to the screen is a completely separate responsibility to that of dealing with the geometric properties.  This must have been a pretty common anti-pattern back in the day, because it’s the first thing Uncle Bob mentions in Chapter 9 of Agile Software Development.

Single Responsibilty:  It’s a Matter of Perspective

Let’s talk about the car example again.  Let’s say that we’re not trying to physically model the car this time.  Instead, we’re writing eBay Motors.  What do we care about then?  Well, mostly, model, age and colour.  Writing a car class with those properties makes complete sense this time.  There’s a well defined responsibility here: capture enough information to allow people to search for the sort of car they want.  It’s not a full model of the car, but it serves its purpose.

This is part of the point of SRP, but also its greatest problem: what exactly is a responsibility is a matter of perspective.  From the perspective of physical modelling, one approach is appropriate.  From the perspective of selling the heap of junk, quite another, simpler approach is appropriate.  Equally, a sale looks very different to different departments within a firm.  For sale people, it’s a source of commission.  For dispatch, it’s logistics.  For finance, it’s a series of payments.  These are different, linked, responsibilities and should be separated.

cookie-monster

You’ll recognize this as an interface segregation concern, but naively applying ISP will get you into even more trouble than it’ll get you out of.  A car is a different object and a different responsibility to the designer, manufacturer and salesman.  Trying to think in terms of unified models is right up there with sacrificing chickens to the cookie monster as unproductive techniques in software development go.  It gets even worse when you’ve got multiple people working on the same project who have different perspectives on the same responsibility.  In your car model, should you be modelling wear and tear?  Air flow?  Or, to get completely ridiculous, how about radioactive decay?  Do we need a Hadron class for our car model?

Actually, what it comes down to is that applying SRP is one of the two hard things in computer science: naming.  If you can describe what a class does, you’ve got a responsibility.  If you need to use the words “and” or “also”, you haven’t.  If the description is vague, you haven’t.   Even so, different people will name the same piece of code differently.  Equally, one man’s readable code is another man’s tangle of wires.

Uncle Bob will tell you to extract till you drop.  I think, ultimately, you have to find a point at which you yourself are comfortable and productive.  As you get used to the principles and start to know them in your bones, you’ll find yourself heading closer and closer to that point.  Just don’t start modelling the weak nuclear force.

Published by

Julian Birch

Full time dad, does a bit of coding on the side.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s