Looking over my Raku code, I've realized that I pretty much never use CATCH blocks to actually catch/handle error. Instead, I handle errors with try blocks and testing for undefined values; the only thing I use CATCH blocks for is to log errors differently. I don't seem to be alone in this habit – looking at the CATCH blocks in the Raku docs, pretty much none of them handle the error in any sense beyond printing a message. (The same is true of most of the CATCH blocks in Rakudo.).
Nevertheless, I'd like to better understand how to use CATCH blocks. Let me work through a few example functions, all of which are based on the following basic idea:
sub might-die($n) { $n %% 2 ?? 'lives' !! die 418 }
Now, as I've said, I'd normally use this function with something like
say try { might-die(3) } // 'default';
But I'd like to avoid that here and use CATCH blocks inside the function. My first instinct is to write
sub might-die1($n) {
$n %% 2 ?? 'lives' !! die 418
CATCH { default { 'default' }}
}
But this not only doesn't work, it also (very helpfully!) doesn't even compile. Apparently, the CATCH block is not removed from the control flow (as I would have thought). Thus, that block, rather than the ternary expression, is the last statement in the function. Ok, fair enough. How about this:
sub might-die2($n) {
ln1: CATCH { default { 'default' }}
ln2: $n %% 2 ?? 'lives' !! die 418
}
(those line numbers are Lables. Yes, it's valid Raku and, yes, they're useless here. But SO doesn't give line numbers, and I wanted some.)
This at least compiles, but it doesn't do what I mean.
say might-die2(3); # OUTPUT: «Nil»
To DWIM, I can change this to
sub might-die3($n) {
ln1: CATCH { default { return 'default' }}
ln2: $n %% 2 ?? 'lives' !! die 418
}
say might-die3(3); # OUTPUT: «'default'»
What these two reveal is that the result of the CATCH block is not, as I'd hopped, being inserted into control flow where the exception occurred. Instead, the exception is causing control flow to jump to the CATCH block for the enclosing scope. It's as though we'd written (in an alternate universe where Raku has a GOTO operator [EDIT: or maybe not that alternate of a universe, since we apparently have a NYI goto method. Learn something new every day…]
sub might-die4($n) {
ln0: GOTO ln2;
ln1: return 'default';
ln2: $n %% 2 ?? 'lives' !! GOTO ln1;
}
I realize that some critics of exceptions say that they can reduce to GOTO statements, but this seems to be carrying things a bit far.
I could (mostly) avoid emulating GOTO with the .resume method, but I can't do it the way I'd like to. Specifically, I can't write:
sub might-die5($n) {
ln1: CATCH { default { .resume('default') }}
ln2: $n %% 2 ?? 'lives' !! die 418
}
Because .resume doesn't take an argument. I can write
sub might-die6($n) {
ln1: CATCH { default { .resume }}
ln2: $n %% 2 ?? 'lives' !! do { die 418; 'default' }
}
say might-die6 3; # OUTPUT: «'default'»
This works, at least in this particular example. But I can't help feeling that it's more of a hack than an actual solution and that it wouldn't generalize well. Indeed, I can't help feeling that I'm missing some larger insight behind error handling in Raku that would make all of this fit together better. (Maybe because I've spent too much time programming in languages that handle errors without exceptions?) I would appreciate any insight into how to write the above code in idiomatic Raku. Is one of the approaches above basically correct? Is there a different approach I haven't considered? And is there a larger insight about error handling that I'm missing in all of this?
"Larger insight about error handling"
Is one of the approaches [in my question] basically correct?
Yes. In the general case, use features like try and if, not CATCH.
Is there a different approach I haven't considered?
Here's a brand new one: catch. I invented the first version of it a few weeks ago, and now your question has prompted me to reimagine it. I'm pretty happy with how it's now settled; I'd appreciate readers' feedback about it.
is there a larger insight about error handling that I'm missing in all of this?
I'll discuss some of my thoughts at the end of this answer.
But let's now go through your points in the order you wrote them.
KISS
I pretty much never use CATCH blocks to actually catch/handle error.
Me neither.
Instead, I handle errors with try blocks and testing for undefined values
That's more like it.
Logging errors with a catchall CATCH
the only thing I use CATCH blocks for is to log errors differently.
Right. A judiciously located catchall. This is a use case for which I'd say CATCH is a good fit.
The doc
looking at the CATCH blocks in the Raku docs, pretty much none of them handle the error in any sense beyond printing a message.
If the doc is misleading about:
The limits of the capabilities and applicability of CATCH / CONTROL blocks; and/or
The alternatives; and/or
What's idiomatic (which imo is not use of CATCH for code where try is more appropriate (and now my new catch function too?)).
then that would be unfortunate.
CATCH blocks in the Rakudo compiler source
(The same is true of most of the CATCH blocks in Rakudo.).
At a guess those will be judiciously placed catchalls. Placing one just before the callstack runs out, to specify default exception handling (as either a warning plus .resume, or a die or similar), seems reasonable to me. Is that what they all are?
Why are phasers statements?
sub might-die1($n) {
$n %% 2 ?? 'lives' !! die 418
CATCH { default { 'default' }}
}
this not only doesn't work, it also (very helpfully!) doesn't even compile.
.oO ( Well that's because you forgot a semi-colon at the end of the first statement )
(I would have thought ... the CATCH block [would have been] removed from the control flow)
Join the club. Others have expressed related sentiments in filed bugs, and SO Q's and A's. I used to think the current situation was wrong in the same way you express. I think I could now easily be persuaded by either side of the argument -- but jnthn's view would be decisive for me.
Quoting the doc:
A phaser block is just a trait of the closure containing it, and is automatically called at the appropriate moment.
That suggests that a phaser is not a statement, at least not in an ordinary sense and would, one might presume, be removed from ordinary control flow.
But returning to the doc:
Phasers [may] have a runtime value, and if evaluated [in a] surrounding expression, they simply save their result for use in the expression ... when the rest of the expression is evaluated.
That suggests that they can have a value in an ordinary control flow sense.
Perhaps the rationale for not removing phasers from holding their place in ordinary control flow, and instead evaluating to Nil if they don't otherwise return a value, is something like:
Phasers like INIT do return values. The compiler could insist that one assigns their result to a variable and then explicitly returns that variable. But that would be very un Raku-ish.
Raku philosophy is that, in general, the dev tells the compiler what to do or not do, not the other way around. A phaser is a statement. If you put a statement at the end, then you want it to be the value returned by its enclosing block. (Even if it's Nil.)
Still, overall, I'm with you in the following sense:
It seems natural to think that ordinary control flow does not include phasers that do not return a value. Why should it?
It seems IWBNI the compiler at least warned if it saw a non-value-returning phaser used as the last statement of a block that contains other value-returning statements.
Why don't CATCH blocks return/inject a value?
Ok, fair enough. How about this:
sub might-die2($n) {
ln1: CATCH { default { 'default' }}
ln2: $n %% 2 ?? 'lives' !! die 418
}
say might-die2(3); # OUTPUT: «Nil»
As discussed above, many phasers, including the exception handling ones, are statements that do not return values.
I think one could reasonably have expected that:
CATCH phasers would return a value. But they don't. I vaguely recall jnthn already explaining why here on SO; I'll leave hunting that down as an exercise for readers. Or, conversely:
The compiler would warn that a phaser that did not return a value was placed somewhere a returned value was probably intended.
It's as though we'd written ... a GOTO operator
Raku(do) isn't just doing an unstructured jump.
(Otherwise .resume wouldn't work.)
this seems to be carrying things a bit far
I agree, you are carrying things a bit too far. :P
.resume
Resumable exceptions certainly aren't something I've found myself reaching for in Raku. I don't think I've used them in "userspace" code at all yet.
(from jnthn's answer to When would I want to resume a Raku exception?.)
.resume doesn't take an argument
Right. It just resumes execution at the statement after the one that led to an exception being thrown. .resume does not alter the result of the failed statement.
Even if a CATCH block tries to intervene, it won't be able to do so in a simple, self-contained fashion, by setting the value of a variable whose assignment has thrown an exception, and then .resumeing. cf Should this Raku CATCH block be able to change variables in the lexical scope?.
(I tried several CATCH related approaches before concluding that just using try was the way to go for the body of the catch function I linked at the start. If you haven't already looked at the catch code, I recommend you do.)
Further tidbits about CATCH blocks
They're a bit fraught for a couple reasons. One is what seems to be deliberate limits of their intended capability and applicability. Another is bugs. Consider, for example:
My answer to SO CATCH and throw in custom exception
Rakudo issue: Missing return value from do when calling .resume and CATCH is the last statement in a block
Rakudo issue: return-ing out of a block and LEAVE phaser (“identity”‽)
Larger insight about error handling
is there a larger insight about error handling that I'm missing in all of this?
Perhaps. I think you already know most of it well, but:
KISS #1 You've handled errors without exceptions in other PLs. It worked. You've done it in Raku. It works. Use exceptions only when you need or want to use them. For most code, you won't.
KISS #2 Ignoring some native type use cases, almost all results can be expressed as valid or not valid, without leading to the semi-predicate problem, using simple combinations of the following Raku Truth value that provide ergonomic ways to discern between non-error values and errors:
Conditionals: if, while, try, //, et al
Predicates: .so, .defined, .DEFINITE, et al
Values/types: Nil, Failures, zero length composite data structures, :D vs :U type constraints, et al
Sticking with error exceptions, some points I think worth considering:
One of the use cases for Raku error exceptions is to cover the same ground as exceptions in, say, Haskell. These are scenarios in which handling them as values isn't the right solution (or, in Raku, might not be).
Other PLs support exceptions. One of Raku's superpowers is being able to interoperate with all other PLs. Ergo it supports exceptions if for no other reason than to enable correct interoperation.
Raku includes the notion of a Failure, a delayed exception. The idea is you can get the best of both worlds. Handled with due care, a Failure is just an error value. Handled carelessly, it blows up like a regular exception.
More generally, all of Raku's features are designed to work together to provide convenient but high quality error handling that supports all of the following coding scenarios:
Fast coding. Prototyping, exploratory code, one-offs, etc.
Control of robustness. Gradually narrowing or broadening error handling.
Diverse options. What errors should be signalled? When? By which code? What if consuming code wants to signal that producing code should be more strict? Or more relaxed? What if it's the other way around -- producing code wants to signal that consuming code should be more careful or can relax? What can be done if producing and consuming code have conflicting philosophies? What if producing code cannot be altered (eg it's a library, or written in another language)?
Interoperation between languages / codebases. The only way that can work well is if Raku provides both high levels of control and diverse options.
Convenient refactoring between these scenarios.
All of these factors, and more, underlie Raku's approach to error handling.
CATCH is a really old feature of the language.
It used to only exist inside of a try block.
(Which is not very Rakuish.)
It is also a very rarely used part of Raku.
Which means that not a lot of people have come up with “pain points” of the feature.
So then very rarely has anyone done any work to make it more Rakuish.
Both of those combined make it so that CATCH is a rather featureless part of the language.
If you look at the test file for the feature, you will note that most of it was written in 2009 when the test suite was still a part of the Pugs project.
(And most of the rest are tests for bugs that have been found over the years.)
There is a very good reason that few people have tried to add new behaviours to CATCH, there are plenty of other features that are much nicer to work with.
If you want to replace a result in the event of an exception
sub may-die () {
if Bool.pick {
return 'normal'
} else {
die
}
}
my $result;
{
CATCH { default { $result = 'replacement' }}
$result = may-die();
}
It is much easier to just use try without CATCH, along with defined‑or // to get something that works very similarly.
my $result = try { may-die } // 'replacement';
It is even easier if you are dealing with soft failures instead of hard exceptions, because you can just use defined‑or by itself.
sub may-fail () {
if Bool.pick {
return 'normal'
} else {
fail
}
}
my $result = may-fail() // 'replacement';
In fact the only way to use CATCH with a soft failure is to combine it with try
my $result;
try {
CATCH { default { $result = 'replacement' }}
$result = may-fail();
}
If your soft failure is the base of all failure objects Nil, you can either use // or is default
my $result = may-return-nil // 'replacement';
my $result is default<replacement> = may-return-nil;
But Nil won't just work with CATCH no matter how much you try.
Really the only time I would normally use CATCH is when I want to handle several different errors in different ways.
{
CATCH {
when X::Something { … }
when X::This { … }
when X::That { … }
default { … }
}
# some code that may throw X::This
…
# some code that may throw X::NotSpecified (default)
…
# some code that may throw X::Something
…
# some code that may throw X::This or X::That
…
# some code that may fail instead of throw
# (sunk so that it will throw immediately)
sink may-fail;
}
Or if I wanted to show how you could write this [terrible] Visual Basic line
On Error Resume Next
In Raku
CATCH { default { .resume } }
That of course doesn't really answer your question in the slightest.
You say that you expected CATCH to be removed from the control flow.
The whole point of CATCH is to insert itself into the exceptional control flow.
Actually that's not accurate. It doesn't so much insert itself into the control flow as ending the control flow while doing some processing before moving on to the caller/outside block. Presumably because the data of the current block is in an erroneous state and should no longer be trusted.
That still doesn't explain why your code fails to compile.
You expected CATCH to have its own special syntax rule when it comes to the semicolon ending a statement.
If it worked the way you expected it would fail one of the important [syntax] rules in Raku, “there should be as few special cases as possible”. Its syntax is not special in any way unlike what you seem to expect.
CATCH is just one of many phasers with one important extra bit of functionality, it stops exception propagation down the call stack.
What you seem to be asking for it to instead alter the result of an expression that may throw.
That doesn't seem like a good idea.
$a + may-die() + $b
You want to be able to replace the exception from may-die with a value.
$a + 42 + $b
Basically you are asking for the ability to add action‑at‑a‑distance as a feature.
There is also a problem, what if you actually wanted $a + may‑die to be replaced instead.
42 + $b
There is no way in your idea for you to specify that.
Even worse, there is a way that could accidently happen. What if may‑die started returning a failure instead of exception. Then it would only cause an exception when you tried to use it, for example by adding it to $a.
If some code throws an exception, the block is in an unrecoverable state and it needs to halt execution. This far, no farther.
If an expression throws an exception, the result of executing the statement it is in, is suspect.
Other statements may rely on that broken statement, so then the whole block is also suspect.
I do not think it would be that good of an idea if it instead allowed the code to continue but with a different result for the current expression. Especially if that value can be far removed from the expression somewhere else inside of the block. (action‑at‑a‑distance)
If you could come up with some code that would be vastly improved with .resume(value), then maybe it could be added.
(I personally think that leave(value) would be more useful in such a circumstance.)
I will grant that .resume(value) seems like it may be useful for control exceptions.
(Caught with CONTROL instead of CATCH.)
Related
I understand, Assertions are used in tests to check if the programmer's pre- and post- conditions are true and if the assertions fail, there is/are bug/bugs that need to debugged and fixed.
An assertion is a software construct where the developer states ("asserts") a condition that he believes will always be true. If the condition evaluates to false in some languages an exception is thrown, in others a message is printed, and in others the program ceases to operate.
"A message is printed" highlighted in the above definition caught my attention.
My question is, can we extend the use of assert statements in the actual code itself and catch AssertionErrors to log the messages and not just tests?
Basically to avoid too much verbose with if-else statements(I hate if-else btw, personally).
For example, all of below
String response = aMethod(sendSomething);
if (response!=null && response!="") {
//blah blah
} else {
log.error("Response is null");
throw new NoDataFoundException();
}
could be replaced with the below that provides more detailed messages.
try{
assertThat(aMethod(sendSomething), notNullValue());
} catch (AssertionError e) {
log.error(e.getMessage());
}
You don't want to catch an assertion failure(*). By definition, the program is operating outside its design perimeter. The only safe thing to do is get out.
(*) OK, you can catch it if the point of catching it is to log whatever you anticipating you'll need to diagnose the problem, and then get out quickly.
In your example, it appears that after testing for a response and logging the failure, you'll carry on inline. In general, that would be a bad idea, but perhaps this was just a quick illustration on your part and should not be taken too literally.
As to what I take to be your overall point: yes, if you're checking pre/postconditions at all, leave the checks in shipping code.
My code would look like:
response = aMethod(sendSomething);
if (response == null || response.isEmpty())
throw new InternalError("....");
and there would be something catching the error at some outermost layer, a point of exception handling being that you can transfer control far away rather than having to deal with everything at the point it occurs -- especially if "it can't happen".
First of all, I know the standard answer will be that exceptions are never to be used for flow control. While I perfectly agree with this, I've been thinking a long time about something I sometimes did, which I'll describe with the following pseudo-code:
try
string keyboardInput = read()
int number = int.parse(keyboardInput)
//the conversion succeeds
if(number >= 1000)
//That's not what I asked for. The message to display to the user
//is already in the catch-block below.
throw new NumberFormatException() //well, there IS something wrong with the number...
catch(NumberFormatException ex) //the user entered text
print("Please enter a valid number below 1000.")
First of all, take this example in a very abstract way. This does not necessarily have to happen. The situation simply is:
A user input needs to be constrained and can go wrong in 2 ways,
either
by a thrown exception the language defines, or by a check. Both errors
are reported by the user in the same way, because they do not need to know
the technical difference of what caused it.
I have thought of several ways to solve it. To begin with, it would be better to throw a custom made exception. The problem I then face is, if I catch it locally, what to do with the other exception? In se, the custom exception would be cause for a second catch-block, in which the message would be copied into just as well. My solution:
//number is wrong
throw new MyException()
catch(NumberFormatException ex)
throw new MyException()
catch(MyException ex) {
print("Please enter...")
The meaning of the exceptions' names is everything here. This application of custom-made exceptions is widely accepted, but essentially I didn't do anything different from the first way: I forced to go into a catch-block, albeit by throwing a custom exception rather than a standard-library one.
The same way applied to throwing the exception on to the calling method (thus not having a catch block for the custom exception) seems to make more sense. My method can go wrong in what is technically two ways, but essentially one way: wrong user input. Therefore, one would write a UserInputException and make the method throw this. New problem: what if this is the main method of an application?
I'm not currently struggling with a specific application to implement this kind of behaviour, my question is purely theoretical and non-language specific.
What is the best way to approach this?
I would consider the first exception to be low-level, and I would handle it (by translation in this case) at the point of call. I find that this leads to code that is easier to maintain and refactor later, as you have less types of exceptions to handle.
try
string keyboardInput = read()
try
int number = int.parse(keyboardInput)
catch(NumberFormatException ex)
throw MyException("Input value was not a number")
//the conversion succeeds
if(number >= 1000)
throw MyException("Input value was out of range")
catch(MyException ex) //the user entered text
print( ex.ToString() )
print("Please enter a valid number below 1000.")
I think you have essentially a few ways to go about it with minimal code duplication in mind:
Use a boolean variable/store the exception: If there was an error anywhere in the the general logic of the specific task you are performing, you exit on the first sign of error and handle that in a separate error handling branch.
Advantages: only one place to handle the error; you can use any custom exception/error condition you like.
Disadvantages: the logic of what you are trying to achieve might be hard to discover.
Create a general function that you can use to inform the user about the error (pre-calculating/storing all information that describes the general error, e.g. the message to display the user), so you can just make one function call when an error condition happens.
Advantages: the logic of your intent might be clearer for readers of the code; you can use anu custom exception/error conditon you like.
Disadvantages: the error will have to be handled in separate places (although with the pre-computed/stored values, there is not much copy-paste, however complex the informing the user part).
If the intent is clear, I don't think throwing exceptions from within your try block explicitly is a bad idea. If you do not want to throw one of the system provided exceptions, you can always create your own that derives from one of them, so you only need a minimal number (preferably one) of catch blocks.
Advantages: only one place to handle error condition -- if there is essentially only one type of exception thrown in try-block.
Disadvantages: if more than one type of exception is thrown, you need nested try-catch blocks (to propagate the exceptions to the most outward one) or a very general (e.g. Exception) catch block to avoid having to duplicate error reporting.
The way I see it is this:
Assuming there's no other way to parse your int that doesn't throw an exception, your code as it is now, is correct and elegant.
The only issue would be if your code was in some kind of loop, in which case you might worry about the overhead of throwing and catching unnecessary exceptions. In that case, you will have to compromise some of your code's beauty in favor of only handling exceptions whenever necessary.
error=false;
try {
string keyboardInput = read();
int number = int.parse(keyboardInput);
//the conversion succeeds
if(number >= 1000) {
//That's not what I asked for. The message to display to the user
//is already in the catch-block below.
error=true;
} catch(NumberFormatException ex) { //the user entered text
error=true;
}
if (error)
print("Please enter a valid number below 1000.");
Also you can think about why you're trying to aggregate two errors into one.
Instead you could inform the user as to what error they did, which might be more helpful in some cases:
try {
string keyboardInput = read();
int number = int.parse(keyboardInput);
//the conversion succeeds
if(number >= 1000) {
//That's not what I asked for. The message to display to the user
//is already in the catch-block below.
print("Please enter a number below 1000.");
} catch(NumberFormatException ex) { //the user entered text
print("Please enter a valid number.");
}
You do not need any exceptions in this particular example.
int number;
if (int.TryParse(keyboardInput, out number) && number < 1000) // success
else // error
However, the situation you describe is common in business software, and throwing an exception to reach a uniform handler is quite common.
One such pattern is XML validation followed by XSLT. In some systems, invalid XML is handled through catching validation exceptions. In these systems, it is pretty natural to reuse the existing exception handling in XSLT (which can naturally detect some classes of data errors that a particular validation language cannot):
<xsl:if test="#required = 'yes' and #prohibited = 'yes'>
<xsl:message terminate='yes'>Error message</xsl:message>
</xsl:if>
It is important to see that if such conditions are extremely rare (expected to occur only during early integration testing, and disappear as defects in other modules get fixed), most of the typical concerns around not using exceptions for flow control do not really apply.
What about approaching this validation problem by writing several validator classes that take in an input and return errors, or no errors. As far as your struggle with exceptions: put that logic into each validator and deal with it there on a case by case basis.
after that you figure out the correct validators to use for your input, collect their errors and handle them.
the benefits of this are:
Validators do one thing, validate a single case
Its up to the validation function to decide how to handle the errors. Do you break on first validation error or do you collect them all and then deal with them?
You can write your code is such a way that the main validation function can validate different types of input using the same code, just picking the correct validators using your favorite technique.
and disadvantages:
You will end up writing more code (but if you are using java, this should be put into the 'benefits' bucket)
here is some example pseudo-code:
validate(input):
validators = Validator.for(input.type)
errors = []
for validator in validators:
errors.push(validator.validate(input))
if errors:
throw PoopException
and some validators:
MaxValidator extends IntValidator:
validate(input):
errors = []
errors.push(super.validate(input))
if input > 1000:
errors.push("bleee!!!! to big!")
return errors
IntValidator:
validate(input):
try:
int.parse(input)
catch NumberFormatException:
return ['not an int']
return []
of course you would need to do some trickery to make the parent validator possibly return you a valid version of the input, in this case string "123" converted to an int so the max validator can handle it, but this can be easily accomplished by making the validators statefull or some other magic.
I can't see this answer anywhere in here, so I'll just post it as another point of view.
As we all know, you can actually break the rules if you know them well enough, so you can use throwing an Exception for flow control if you know it's the best solution for your situation. From what I've seen, it happens usually with some dumb frameworks...
That said, before Java 7 (which brought us the mighty multicatch construct), this was my approach to avoid code repetition:
try {
someOffendingMethod();
} catch (Exception e) {
if (e instanceof NumberFormatException || e instanceof MyException) {
System.out.println("Please enter a valid number.");
}
}
It's a valid technique in C#, too.
I have used try-catch/except-finally variants in many languages for years, today someone asked me what is the point of finally and I couldn't answer.
Basically why would you put a statement in finally instead of just putting it after the whole try-catch block? Or in other words is there a difference between the following blocks of code:
try{ //a}
catch {//b}
finally {//c}
try{//a}
catch{//b}
//c
EDIT:
PEOPLE, I know what finally does, I have been using it for ages, but my question is in the above example putting //c in finally seems redundant, doesn't it?
The purpose of a finally block is to ensure that code gets run in three circumstances which would not very cleanly be handled using "catch" blocks alone:
If code within the try block exits via fallthrough or return
If code within a catch block either rethrows the caught exception, or--accidentally or intentionally--ends up throwing a new one.
If the code within the try block encounters an exception for which the try has no catch.
One could copy the finally code before every return or throw, and wrap catch blocks within their own try/catch to allow for the possibility of an accidental exception occurring, but it's far easier to forgo all that and simply use a finally block.
BTW, one thing I wish language designers would include would be an exception argument to the finally block, to deal with the case where one needs to clean up after an exception but still wants it to percolate up the call stack (e.g. one could wrap the code for a constructor in such a construct, and Dispose the object under construction if the constructor was going to exit with an exception).
Finally block is executed even if an exception thrown in the try block. Therefore, for instance if you opened a stream before, you may want to close that stream either an exception is thrown or not. Finally block is useful for such an issue.
finally is a syntactic sugar to allow DRY principle in try-catch pattern. Exception is usually thrown if the library code has not enough information to handle some state and wants the client code to solve it. If you don't have library-client code separation, you can handle everything by if instead of try.
Let's see a standard situation without finally:
void myFunction() {
var r = allocateResources();
r.doSomething();
if(somethingBadHappens) {
freeResources(r);
throw new Exception(CODE42);
}
r.doSomethingMore();
freeResources(r);
}
In the snippet above, you repeat freeResources(): this can be multiple statements which you need to repeat. This smells and finally block is the solution for clean code:
void myFunction() {
var r = allocateResources();
try {
r.doSomething();
if(somethingBadHappens) throw new Exception(CODE42);
r.doSomethingMore();
}
finally {
freeResources(r);
}
happyFunction();
}
Let's realize three levels of abstraction:
A1 is the library code providing allocateResources() function
A2 is our code providing myFunction, consuming A1
A3 is some client code consuming myFunction in try-catch block:
function A3code() {
try {
myFunction();
doSomething();
}
catch(Exception e) {
// no hanging resources here
Console.WriteLine(e);
}
}
Now let's see what can happen:
if allocateResources() throws in A1, we don't know how to handle it in A2 (A2 code can be run in Console-free environment), so we delagate the situation to A3 without adding any further code. If Exception is thrown here, the finally block is not executed, since finally is bound to try which was not entered.
if somethingBadHappens in try block, the stack unwinds to A3 where the situation is handled BUT before it finally block is executed, so we don't need to repeat it if no exceptions happen.
before finally we can add catch block and try to resolve some potential exceptions from A1 which may appear in calling r.doSomething methods. Usually we want to handle exceptions as soon as possible to make the client code (A3) more comfortable for client coders.
happyFunction() is executed only if nothing throws in myFunction() (inside or outside of try block).
As #supercat points out, the finally block is executed also if try block exits via return. I suggest you avoid this bad habit and have only one return in every function (maybe some early exists at the very beginning of functions). The reasons for single-return functions are:
The code is more readable: you look at the end and see WHAT the function returns. In multiple returns you must find all return occurences, inspect all the ifs, think about when the ifs are satisfied and only then you know what the function returns.
The code can be optimized by compilers, see copy elision.
The reason for multiple returns is avoiding many nested ifs, but there are other techniques to solve it. Exceptions are exception in this rule.
Learn by example
let v = 0;
function f() {
try {
v = 1;
return 2;
} finally {
v = 3;
return 4;
}
v = 5;
return 6;
}
const r = f();
console.log(r, v);
following prints "3, 4"
Finally make sure your code is executed even if you get an exception.
The finally block is useful for cleaning up any resources allocated in the try block as well as running any code that must execute even if there is an exception
http://msdn.microsoft.com/en-us/library/zwc8s4fz(v=vs.80).aspx
Is it always a bad programming technique to leave a catch block empty when using a try-catch block?
In cases where I am expecting an exception, for example, I am reading 10 values from a file...and converting each value into a String. There is a possibility that one of the 10 values can be a null, but I don't want to stop my execution at that point and rather continue (obvious use of try catch)
A lame example of what I am trying:
String _text = textReader.ReadLine(); //Assuming this RETURNS a NULL value
try {
String _check = _text.ToString();
//Do something with _check, but it should not be NULL
}
catch (Exception)
{ //Do Nothing }
At this point, when I catch an exception:
1. I don't want to log this. Since I am expecting a buggy value.
2. I don't want to re-throw the exception up the call-stack.
3. I want to simply continue with my execution
Under these cases, is it acceptable to leave the catch empty? Or is this a complete NO-NO and there is a better way to handle this?
I presume this can be a community wiki since it also deals with programming techniques.
- Ivar
I assume that you mean
_text.ToString()
and you're concerned about the case when _text may be null?
I don't like your use of exceptions in this case. Put yourself in the mind of someone needing to maintain this code. They see:
catch (Exception) { }
Can they really deduce that all this is doing is catching the Null case? They have to consider what other Exceptions might be thrown. At the very least this raises uncertainty in the maintainer's mind.
Why could you not code:
if ( _text != null ) {
String _check = _nullValue.ToString();
}
This says exactly what you mean.
But taking this further, what does getting a value of NULL mean? You're reading a file that may have 10 values in it. I'm guessing that maybe blank line gives a null for you?
What do intend if you get:
1
2
<blank line>
4
...
10
So that's 9 good values and a blank line. What will you do if instead you get 10 good values and a blank line? 11 good values? 11 good values and a blank line?
My point is that silently ignoring oddities in user input is often a bad idea. It's quite likely that some of the cases above are actually typos. By warning in some of these cases you may be very helpful to the user. This implies that probably most odditities in input need at least some kind of count, if not an actual immediate error log.
You might, for example, emit a message at the end
Your file contained 13 lines, two lines were blank. We processed 10 valid values and ignored one.
At the very leasyt for debugging purposes you might have trace statements in the error paths.
Summary: completely ignoring Exceptions is rarely the right thing to do.
If it's expected it's not really exceptional. Is this a good use of exceptions? It may be more appropriate to test for a null value before trying to do stuff.
if(_text != null)
// do something
The best argument I liked was that the code will become highly un-maintainable. And my aim was to make my code as easy to understand as possible.
With the suggestions above, I have an army of ternary operators in place (my personal favorite over big if-else blocks).
And the code definitely looks better! and I think unless the try-catch is well documented, I don't myself see a good reason for having empty catch statements anymore.
Thanks Guys!!
-Ivar
I'm wondering if there has been any research (both casual and robust) on the maintainability of projects that use the "guard statement" paradigm vs. the "single function exit point" paradigm?
Guard statement example (in C#):
string GetSomeString()
{
if(necessaryConditionFails) { return null; }
if(!FunctionWithBoolReturn(someAttribute)) { return null; }
//all necessary conditions have been met
//do regular processing...
return finalStringValue;
}
single function exit point example (in C#):
string GetSomeString()
{
string valueToReturn = null;
if(necessaryConditionPasses && FunctionWithBoolReturn(someAttribute))
{
//all necessary conditions have been met
//do regular processing...
valueToReturn = finalStringValue;
}
return valueToReturn;
}
I know the merits and failings of both have been debated endlessly on SO, but I'm looking for actual research into how maintainable each paradigm is*. This may be unknown, but I figured if the information is out there, someone on SO would know where it was. My web searches have not been sucessful so far.
**I'm also aware that many programmers (including me) use both principles throughout their code, depending on the situation. I'm just hoping to discover which one has a proven track record of greater maintainability to use as the preferred paradigm.*
Forcing to have single exit points have some problems of their own.
The first one is that it may lead to complex constructions.
Image a function in which you have to open a file, read a line, convert the line to a number and return that number or zero if something goes wrong.
With a single exit point, you end up using lots of nested if's (if file exists open it, if open succeeds read the line, if read succeeds convert the value to an integer), which makes your code unreadable.
Some of it can be solved by having a label at the end of the function and using goto's (we had to use that in the past since we also used the single exit point and preferred readability) but it's not ideal.
Second, if you are using exceptions you are forced to catch everything if you want your single exit point again.
So, personally, I prefer putting lots of checks (and asserts) in the beginning and during the execution of the function, and exit the function at the first sign of trouble.