
Making No Exceptions: Literally
Looking at a different approach to handle exceptions.
Don’t you hate it when articles start this way? There are two kinds of people. Well, there are two kinds. there are the ones who make their bed after they wake up, and there are the ones who leave it all in bundle.
Does it matter whether or not I fold the blankets, fluff the pillow, and leave them nicely stacked? Probably not. But I choose to think I’m making myself better, or at least better at facing the more difficult day’s challenges; Going to the gym, making some presentation or writing a blog post.
If I were to illustrate the start of my software development career, I’d say that I was handed a machete by my TL and that I were instructed to run head on into the jungle.
Did you ever exchange horror stories with other developers about the things you saw? You know, and I know, we saw things.
I learnt since then that there is lazy-good and lazy-bad. Good developers are lazy, they write code that lets them work less; or better said: more efficiently. But that laziness is great only to some extent, and passing that extent just leads to bad code. Yet, no developer just suddenly turns lazy-bad. It’s a spiral, we just give up to bad code and then create some more.
So I insist on making my bed.
One of the dirty code topics that bothers me most is what can be described as “exception driven development”. It’s not really a practice, it’s just a place you may end up in when you find yourself stuck in the middle of a jungle and having no idea how to get out. It is when you start patching up, trying-and-catching instead of trying to understand why you’re getting exceptions from the first place. It is a bad practice which leads to a scenario much like that tall person who tries to cover itself with a blanket that is too short, having a different limb pop out every time from a different end. One solution here may be to check whether the blanket is too short instead of kicking around, or getting a map of the jungle instead of a bigger machete.
My favorite approach to exception handling is using multiple return values. You have that in Go language and also in ES6 via destructuring.
Take a look at this Go code snippet. Here you see an example of the use of multiple return values to handle division by zero. Instead of throwing the code all to hell up the stack each time the input we receive is not to our liking, we handle things elegantly.
package mainimport "fmt"
import "errors"func divide(a int, b int) (error, int) {
if b == 0.0 {
return errors.New("Division by zero"), 0.0
} else {
return nil, (a/b);
}
}func main() {
err1, result1 := divide(8,4)
if err1 == nil {
fmt.Printf("Result == %d.\n", result1)
} else {
fmt.Println(err1)
} err2, result2 := divide(8,0)
if err2 == nil {
fmt.Printf("Result == %d.\n", result2)
} else {
fmt.Println(err2)
}
}
ES6 also provides this kind of capability via destructuring, and I could add another snippet of the same solution written in ES6, but I’d rather switch to talk about async/await.
With async/await you get to break the then/catch syntax you write for Promises, and achieve a neatly indented code with a more readable flow; well, unless you need to catch something.
function findAndPrintUser(id) {
users.findById(id)
.then((user) => console.log(user.name))
.catch((err) => console.error(err));
}async function findAndPrintUserAsync(id) {
try {
const user = await users.findById(id)
console.log(user.name);
} catch (err) {
console.error(err);
}
}
In the code snippet, after replacing the then with an await, we are left with using a try/catch block to allow us to handle the possibility of promise rejection. It kind of ruins the whole neat indentation we dreamt about. This is where destructuring assignment comes to the rescue.
The next snippet, which I think I originally saw on await-to-js, simply consists of the function that accepts a promise, does the then/catch and returns an Array which can be destructured and never again in your code would you write another then/catch expression.
function to(promise) {
return promise
.then(data => [null, data])
.catch(err => [err, null])
}async function findAndPrintUserAsync(id) {
const [err, user] = await to(users.findById(id))
if (err) {
console.error(err);
} else {
console.log(user.name);
}
}
To conclude, in my opinion, taking an approach where exceptions are never thrown and are instead handled elegantly pays off; As well as setting coding standards to your project, initially or not. And even though it’s a pain to take responsibility for your application’s health, you should keep following this approach, or any other healthy approach you choose.
After all, isn’t it great to come back home to a neatly folded bed?
✉️ Subscribe to CodeBurst’s once-weekly Email Blast, 🐦 Follow CodeBurst onTwitter, view 🗺️ The 2018 Web Developer Roadmap, and 🕸️ Learn Full Stack Web Development.