There are times in code when you need to return the results of an operation, but aren’t using a transport mechanism like HTTP that gives you a data structure with built-in statuses. In these situations, a lightweight OpResult class can be really helpful.
The original problem: returning when an operation fails
Imagine you’re writing a method to send a document to a printer. Your two teammates—let’s call them Karen and Ivan—will need it, so the method needs to be independent of any UI and reusable in different places. You don’t want to throw an exception just because the printer is turned off, so your first quick implementation looks like this:
The next day Karen pings you that her printer will throw an error if one of the color ink cartridges needs to be replaced, but she can’t tell the user which one because we’re only returning a success/failure flag. Easy fix, right?
Later in the week, you hear from Ivan. He wasn’t as vigilant as Karen in preventing users from submitting print requests without all the needed information. He asks if your method can return all the information the user needs to fix while printing a file. Normally you’d tell him he needs to write better code, but he’s the company owner’s son and you like being employed. A quick adjustment later, you’ve solved the problem:
The problem grows: returning more than just errors
A few days later we get another request from Karen. She’s handling financial reports and has a new requirement from a green initiative to track how many sheets of paper are getting printed. Uh-oh. We’re already returning something—errors. Your brain immediately conjures up a new class to combine the error results and requested data:
But this isn’t your first rodeo. You know what’ll happen if you introduce this pattern into the code base–lots of similar Result classes with slightly different implementations. You mumble to yourself “there’s got to be a better way” as you go fix a fresh coffee and take a slow walk in the nearby park to clear your mind. After several sips and a couple of hundred steps of aimless wandering, the answer bubbles up from your subconscious.
Handling multiple needs with a single reusable approach
By using a generic for the result, this same class can be used for any situation where we need to gracefully return either a success result of some kind or a list of errors. Upon a little reflection, you add another property to make it easier for the calling code to check for success.
Lastly, you realize that you can simplify the object construction and ensure it’s always in a valid state by using factory methods to set the properties instead of making the developer set everything themselves. Factory methods with different names will also make it easier to see what kind of result is being created, as you can see by comparing the code lines commented below with “return success” and “return failure” with the previous snippet using a constructor.
Now, let’s take a look at the complete solution:
OpResult isn’t a silver bullet and shouldn’t be used everywhere. When you don’t have another mechanism (such as HTTP responses) available for easily returning operation errors and results together, though, a lightweight OpResult class helps you effortlessly and cleanly deal with errors and results without creating many similar, slightly different Result classes.