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.
Checking for Success
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?"
Command Query Separation
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.
Conclusion
CQRS and SOLID are some important tested and trusted principles. Ultimately, stick to what works for your use case.