Single Responsibility
Single responsibility is a concept that has been around for a while.
Wikipedia defines it as:
…a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.
This helps your codebase be easier to maintain, easier to debug, and easier to learn.
Do one thing, and do it well
Whether you’re building something for yourself, you’re the only one on the team, or you’re part of a giant development team - there’s a certain need for having an “orderly” way of “doing things”.
In my intro post, I started off the introduction to this topic by saying:
script.js is so 2014
Think about having all of your current JavaScript code in one file. It used to happen! It still does happen. It’s daunting for anyone coming into a project. Sure, you can help supplement the knowledge you’ve dumped into it by annotating with comments or naming your functions properly, but it’s still a daunting thing to look at.
Imagine coming into a project for the first time. You’ve been tasked with updating this function on a website:
$('.price').on('click', function() {
$('.item').css('color': 'green');
$.ajax(
});
The above creates an event handler via jQuery (don’t get me started on jQuery), but doesn’t really infer why it exists.
The switch to component-based architecture in the front-end world and really just the transformation of JavaScript from being simply a DOM manipulation tool to a complex infrastructure language (thanks Node) has brought about a lot of change to the ways we write it.
In an Angular 2+ world with TypeScript (when written according to John Papa’s Angular Style Guide), the above code would be written in at least three different files, like so:
import { TransportService } from "../../services/transport.service";
@Component()
export class PriceComponent extends Component implements OnInit {
@Input() price: number;
constructor(private _transportService: TransportService) {}
ngOnInit() {
this.addEventListener('click', (e: MouseEvent) => {
this._transportService.send('stuff');
})
}
}
@Injectable()
export class TransportService {
public send(item: string) {
}
}
There would probably be another file to act as a state class across modules (another Angular Service) to change the CSS as in the first example as well, but I think you get the idea of what I’m going for here.
Writing more code isn’t necessarily a bad thing
I find myself being verbose in my writing of JavaScript / TypeScript nowadays, and I don’t view that as a bad thing.
Verbosity in code isn’t inherently inefficient. It isn’t going to slow down my application, at least in a way that’d really matter. Tree shaking and whatnot exist now! Let things be singletons. Let things do exactly what they should do and not a thing more. There was a time when we didn’t compile our JavaScript code, and it made sense to not be verbose. But now, now that we can compile our code means we can be verbose too. The front-end world gains access to a lot of things back-end / compiled languages have enjoyed for a while with these new tools.
I’m of the opinion that it doesn’t go against the idea of pragmatism to be a bit verbose. Writing a bit more meaningful code than would be necessary for the now makes my job of maintaining / adding / scaling the thing I’m doing now easier for me (or someone else!) in the future.
Of course the jQuery code listed first above would work for the purpose we’d want. It did back then! It would do so still. But there’s a way, and then there’s a better way (for all those involved).
My codebase isn’t built with this in mind. Do I have to scratch it all?
Codebases are always changing. We’re always adding to them, deleting from them, and manipulating in between. Start on the path toward single responsibility.
I’ve encountered this in the past. I came into a codebase that had several different frameworks within it. One was used in one part of the app, another in another part. No real rhyme or reason for any of it. I made it my duty to go through and bring everything under the same umbrella.
Developing with empathy means that you keep the following in mind:
- the person before you may not have had the time to think about the bigger picture.
- the person before you may not have had the technical know-how to always think about the most common denominator.
- you should leave the codebase in a better state than it was before you arrived.
- the people you work with now are likely to need your help in keeping the codebase healthy.
- you can’t solve everything.
- the person after you could very well be in the same position you are in right now if you don’t fix what you can.
I made my aspirations for the codebase part of the expected work and not just “outside” the realm of the project work.
This meant bringing items that did similar or even the same thing into one hunk of code by introducing reusable components.
I’ve already employed this strategy. I’m good to go.
Is it? This isn’t a “we built it with this in mind” kind of topic. Quite the contrary - what you wrote on Friday, was it built with this principle in mind? This is an always on principle, and without it always on, you can get lost from it pretty quickly.
At a previous job, my team and I for the most part maintained this principle. This was a codebase I led for a while and I still let some of this slip. It came back to haunt us! When asked about x
being a reusable component, I had to admit that it wasn’t, that it was built specifically for a particular purpose because we didn’t have enough time to generalize it and merge it into our existing patterns. It’s not a failure - but it shows that it requires constant communication with project leaders, designers, and everyone else on the team to maintain a codebase in the most optimal way.
This doesn’t just apply to JavaScript / TypeScript
Single responsibility makes its way into CSS too. OOCSS, BEM, and all the other “ways” of doing CSS make some part of this part of their schema too. I’ll expand on this in another post.
tl;dr
Writing a codebase with single responsibility in mind is hard work. It takes a lot of thinking and planning to be comfortable breaking complex ideas up into small bits that all do “their own thing” and can be reused. However, it allows developers and teams alike to work quicker, increase speed and efficiency, and helps onboard new developers and instills good heuristics on what should be “new” and what can be reused.