Many developers have this tendency to aggressively try-catch errors in their program. I see it even encouraged by some developers in the name of resiliency and fault-tolerance, but in my experience, it can make systems hard to debug as it can insidiously hide real issues. I recommend that most people should just let the errors propagate to some root error handler or let their program crash if recovery isn't important or if the surrounding environment will auto recover for you.
The root error handler allows you to have a centralized place to log the error. You can also stop the error from propagating at this point if you don't want to completely crash your program or if there is a way to recover from the error.
The only reason to try-catch a piece of code is if you are trying to catch a specific error that could potentially be thrown. This could be a network error that may occur when you're making a request, or a parse error when you are parsing user input. You never want to be try-catching something that you don't understand or are not aware of since it will obfuscate the deeper issue at hand if those types of errors occur. Your users will suffer for it as the error may manifest itself indirectly in ways that are hard to understand and debug.
When try-catching a piece of code, you have some decisions to make. You can represent the caught error as data or state that is part of your program. This is a common technique in strongly typed functional programming languages where errors are often just represented as data and returned as such. For example, Scala has a Try type and Haskell has an Either type which can be used to represent both success and failure. You can also catch the error and even re-throw a new error. This can be useful in cases where you want to log something more informative to the root error handler, such as more context on how a function was called or a friendlier error message.
Error handling doesn't have to be that complicated. Just let the errors flow and cherry-pick a couple of errors that need to be handled explicitly.
The root error handler allows you to have a centralized place to log the error. You can also stop the error from propagating at this point if you don't want to completely crash your program or if there is a way to recover from the error.
The only reason to try-catch a piece of code is if you are trying to catch a specific error that could potentially be thrown. This could be a network error that may occur when you're making a request, or a parse error when you are parsing user input. You never want to be try-catching something that you don't understand or are not aware of since it will obfuscate the deeper issue at hand if those types of errors occur. Your users will suffer for it as the error may manifest itself indirectly in ways that are hard to understand and debug.
When try-catching a piece of code, you have some decisions to make. You can represent the caught error as data or state that is part of your program. This is a common technique in strongly typed functional programming languages where errors are often just represented as data and returned as such. For example, Scala has a Try type and Haskell has an Either type which can be used to represent both success and failure. You can also catch the error and even re-throw a new error. This can be useful in cases where you want to log something more informative to the root error handler, such as more context on how a function was called or a friendlier error message.
Error handling doesn't have to be that complicated. Just let the errors flow and cherry-pick a couple of errors that need to be handled explicitly.