Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Colum Ferry
Colum Ferry

Posted on

     

Let's Clean Up: Ugly Try-Catches!

Let's Clean Up: Ugly Try-Catches!

We've all been there. We have all usedawait onasync methods, forgetting to wrap them intry-catch just to be told off about anUnhandled Error 😱

But it's not just theseaysnc methods that might throw. Perhaps we use a third-party library that throws, or we design our codebase in such a way that we throw errors intentionally to bubble error handling up a few layers.

So we proceed by wrapping our calls to the throwable methods in ourtry-catch blocks.

Perfect! 😍

🤔 Or is it?

Working in a code base where you can expect methods tothrow can lead to situations where your logic is wrapped intry-catch blocks. It also leads to other code design problems.

Take a look at the examples below:

try{constmyVar=myThrowableMethod();}catch(err){console.log(err);}// Wait, there's an error here:console.log(myVar);// myVar doesn't exist outside of the try scope.
Enter fullscreen modeExit fullscreen mode

As we can see above, themyVar variable doesn't exist outside of thetry block. But we need that value to continue our logic!!

So now we need to do something a little different.
How about:

try{constmyVar=myThrowableMethod();constnewVar=manipulateMyVar(myVar);constresponse=sendApiRequest(newVar);returnrespponse;}catch(err){console.log(err);}
Enter fullscreen modeExit fullscreen mode

🤮!

No. Let's not do this.

Now all our logic wrapped inside thetry block. That's ugly.
Also, if any of the subsequent method calls throw, are we sure we want to handle them the same way!? Possibly, but most likely not!

Ok, let's try something else...

letmyVar;try{myVar=myThrowableMethod();returnrespponse;}catch(err){console.log(err);}constnewVar=manipulateMyVar(myVar);constresponse=sendApiRequest(newVar);
Enter fullscreen modeExit fullscreen mode

This is a little better, but still not perfect. It's still ugly asmyVar has to be declared then initialised almost immediately after just in a different scope. It also presents an issue whenmyThrowableMethod does throw, as execution will continue and try to usemyVar!

🐛 Alert!

I could keep going, giving more situations where thesetry-catch blocks can present code design, readability, and maintainability problems.

Instead, I'll present you with a solution!

The Solution 🚀

I wrote a small library to tackle this issue head on :

Let's welcomeno-try to the scene. 🎉

What isno-try? 😱

no-try is a tiny library that takes thetry-catch out of your code, improves the readability and maintainability of your code whilst helping to improve code design.

It exposes two functions.noTry andnoTryAsync with the latter resolving and returning the result of Promises.

Don't believe me? Let's look at it in more detail.

To install it, simply runnpm i --save no-try

Then add it to your file:

In TypeScript;

import{noTry}from"no-try";
Enter fullscreen modeExit fullscreen mode

In JS:

constnoTry=require("no-try").noTry;
Enter fullscreen modeExit fullscreen mode

Now, let's refactor our example above to useno-try.

const{result,error}=noTry(()=>myThrowableMethod());if(error){// Handle errorreturn;}constnewVar=manipulateMyVar(result);constresponse=sendApiRequest(newVar);
Enter fullscreen modeExit fullscreen mode

🎉🎉🎉

Isn't that cleaner!?

If you have a standard error handling function, you can supply that tonoTry and it will invoke it for you if an error occurs!

functionmyCustomErrHandler(error){// Do something to handle error}const{result,error}=noTry(()=>myThrowableMethod(),myCustomErrHandler);if(error){return;}constnewVar=manipulateMyVar(result);constresponse=sendApiRequest(newVar);
Enter fullscreen modeExit fullscreen mode

And that's it!

We've removed thetry-catch blocks from our code, preventing issues relating to block-scoped variables, whilst also allowing our code to be much more readable without sacrificing the flexibility of handling the error how we want.

You can read more onno-try over onGitHub.

Now go clean your code!


If you have any questions, feel free to ask below or reach out to me on Twitter:@FerryColum.

Top comments(13)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
metruzanca profile image
Samuele Zanca
  • Location
    Milan, Italy
  • Work
    Full-Stack Developer
  • Joined

Isn't that cleaner!?

Imho, no. That's adding a useless dependency to a problem that's easily solved by addingreturn statement in the catch block. Could probably benefit from using more functional programming to refactor that bit but that depends purely on what other stuff is going on before this block.

CollapseExpand
 
coly010 profile image
Colum Ferry
Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

Agreed. You can add the return to the catch to break execution in the method, but you're still left with what I feel is less readable code. I guess it comes down to a matter of preference! :)

CollapseExpand
 
metruzanca profile image
Samuele Zanca
  • Location
    Milan, Italy
  • Work
    Full-Stack Developer
  • Joined

A lot of coding is down to preference. :)

CollapseExpand
 
swarupkm profile image
Swarup Kumar Mahapatra
  • Joined

Well sometimes less Magic and more control is better

CollapseExpand
 
coly010 profile image
Colum Ferry
Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

Theno-try lib should still provide you with all the control you need :)
It just removes the need to write the try catch blocks manually. It still uses them under the hood.

CollapseExpand
 
andrewharpin profile image
Andrew Harpin
Security and Safety Critical Embedded Developer
  • Location
    Uk
  • Work
    Cyber Security team lead at ZF
  • Joined

Good code should read like a document, really good code can be understood by a non coder.

There are sometimes benefits from abstracting complex functionality, but it is a balance of readability, functionality and performance.

Code is first and foremost a document describing the behaviour of your action.

Looking at it from a different perspective, if a Dev was to come along and they had never seen this code or your no-try mechanism.

They have to read your code, then they have to go and learn your no-try and maybe they'll understand it, maybe they won't.

However there is a high probability they've seen a try-catch, so they don't need to learn this functionality and so immediately understand what you are trying to achieve!

This is an over abstraction which makes you feel better as a particular function has been reduced, but as a piece of code actually over complicates the readability of the code as it deviates from normal coding practice.

Try catch is easily explained to a non-coder, your function less so, especially as the name doesn't reflect the functionality of it's operation.

This is why function naming is critical in abstraction.

CollapseExpand
 
coly010 profile image
Colum Ferry
Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

I understand what you're saying, and it makes sense. There's a cost to learning what the method does, but the same can be said for any dependency you add to your codebase.

Take for example someone adding lodash, are using both_.clone and_.cloneDeep but someone else comes along to read it. If they don't know about lodash, or the difference between those functions, they will have to go and look them up to.

Perhaps simply calling the functiontryCatch might have explained more what the function is doing, BUT, for any dev that wants it to be called this, then they can alias the import.

import { noTry as tryCatch } from 'no-try';

Or

const tryCatch = require('no-try').noTry;

CollapseExpand
 
kmaschta profile image
Kmaschta
  • Location
    Nancy, FRANCE
  • Work
    Software Developer at marmelab
  • Joined

Ugly isn't really a professional adjective to use.
A chunk of code can be simple or complex, fast or slow, stateful or stateless, among others measurable metrics.

Saying a code is "clean" or "ugly" is really subjective, it's an opinion. For example, a try/catch isn't intrinsically ulgy nor bad by itself.

Every instruction in a codebase is a technical choice made in a certain context that must be understood. A hackathon blockchain explorer will have a different codebase than a battle-tested and historical ERP.

It's pretty reductive for our field to use such adjectives.

I don't wanna be rude, but the JavaScript ecosystem doesn't need a new (26 lines of code) package while receiving a "go clean your code" with it.
I know there is a lot of new developers on this platform, and this is awesome! But this is not the kind of message we need to send them.

CollapseExpand
 
coly010 profile image
Colum Ferry
Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

Agree: it is subjective.
Disagree: JS ecosystem doesn't need a new package (whatever the reasoning behind it is).

Without developers trying to think of new ways to make their own and others lives maybe just that little bit easier, then we would never innovate. We wouldn't get new technologies.

If a dev, at any level, has an idea for how to make their life easier, we should be at least encouraging them to investigate it, not telling them to sit on it just because it may not be needed, (which is also subjective).

Is native XHR API bad or ugly? Subjective, but we still have Fetch, Axios, Angular Http etc.

So yes, it's subjective. If you don't mind try catches, still use them, if you don't like how they look, or have been burned with bugs caused or relating to their usage, then there is an alternative.

CollapseExpand
 
broflylow profile image
Haritonich
  • Joined
• Edited on• Edited

There is already another solution that was created years ago, he took approach to handle errors same way as in GO

github.com/scopsy/await-to-js

Anyway good job mate !

CollapseExpand
 
coly010 profile image
Colum Ferry
Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

Thanks!
Awesome link, there is something similar to the above called await-of that also handles promises, I wanted a method that also worked for standard methods that throw errors.

CollapseExpand
 
deta19 profile image
mihai
webdeveloper since 2014
  • Location
    romania
  • Education
    bachelor
  • Work
    webdev at not important
  • Joined
• Edited on• Edited

Question, shouldn't try catch be used at the beginning of writing your code and after that you clean it up, because you know now that the code is stable?

CollapseExpand
 
coly010 profile image
Colum Ferry
Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

I'm not sure I fully understand what you are saying.

It sounds like you're saying you wrap the full app startup in a try catch and then come along later and remove it?

Try catches should be wrapped around any method you expect can throw an error at the layer you wish to handle it.

Perhaps you have three layers:

UI
Business
Data Layer

Your data layer may throw and error but you want to catch it at the UI level so you can show an error to the user.
Or perhaps, your business layer throws an error, but you want to catch it immediately so you can call a fallback method etc.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Full Stack JS Developer and JS Enthusiast
  • Location
    Northern Ireland
  • Work
    Senior Software Engineer
  • Joined

More fromColum Ferry

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp