Single Responsibility Functions

I'm a software developer and computer scientist who loves building things.
Search for a command to run...

I'm a software developer and computer scientist who loves building things.
No comments yet. Be the first to comment.
The Interface Segregation Principle is the I in SOLID design principles. It states that types should not implement methods unrelated to their operations. In essence, interfaces should be designed so that the implementing types are not forced to imple...

The project management iron triangle applied to software development
Errors in golang are vastly different from errors in JavaScript. In this post, we will implement error handling for the events project we started building in the first post . That means, handling errors within the module itself as well as errors fr...

If you already know how to use a programming language, when learning a new language it is only natural to attempt to draw comparisons. This post is the first in a series, we'll attempt to learn Golang by re-implementing Events APIs in Go. What are th...

This post is inspired by this wonderful post by Evelyn Stender. This post does not explain what a generator is or basic usage, Evelyn's post does a good job of that. Instead, in this post, we will demonstrate how generators can be useful for streamin...

Many people understand the Single Responsibility Principle, but very few pay attention to how it affects whether function (and methods) should return something. In this blog post, I'm looking at a case for taking that principle to the "extreme" and looking at how functions could be written to only ever have a single reason to change.
A common thing return statements can help do is verify that operations were successful by returning boolean values, see this example:
if (UpdateUser(user)) {
if (SendEmail(user.email)) {
// log operation
} else {
// retry sending email
}
} else {
// log failure
}
Returning false only says the functions failed but it doesn't say why., this is what exceptions are for. If the functions UpdateUser and SendEmail were rewritten to throw exceptions, we eliminate the need for the conditionals, make the whole code easier to read and handle errors better.
try {
const createdUser = UpdateUser(userData);
SendMail(user.email):
} catch (e) {
// log error
}
This implementation is easier to read, allows for better error handling (Exceptions should contain error messages) Writing the functions this way brings up the question, "What then (if anything) should the UpdateUser and SendMail functions return?"
Taking the Single Responsible Principle to the extreme in this case, let's employ CQRS. CQRS states that a method or function should either be a Command that changes state or a Query that returns values, but never both. In this light, the UpdateUser function should only ever update a user with no side-effects and return nothing, same thing with the SendMail function.
try {
UpdateUser(userData);
const user = GetUser(userId);
SendMail(user.email):
} catch (e) {
// log error
}
This makes everything a whole lot clearer and more consistent. It's easy to know exactly what a function does (knowing that it doesn't do anything else). Satisfying the Principle of Least Astonishment as there are no side-effects and functions/methods do not return needless information.
CQRS and SOLID are some important tested and trusted principles. Ultimately, stick to what works for your use case.