Exception-Safe Java

Warning

Topic is very controversial

Why Exceptions?

We need a mechanism
to separate the code
that generates an error with the code
that handles it

It's usually the case that where an error happened is in a different layer of where the error can be handled

A Tale of Two Exceptions

Unchecked Exceptions

  • If you don't handle them, it's not a compile error
  • There aren't part of the method's signature
  • They will crash your program with a noisy stack trace
  • This is what all languages have

Checked Exceptions

  • It's a compile error if you do nothing about them
  • You need to handle them or propagate them
  • Part of a method's signature
  • Java is the only language with them

Unchecked Exceptions
can be completely ignored

C++'s STL had a bug
for 6 years for a code
that threw an exception
but it wasn't handled

How should you use
them, according to
the language creators?

Errors

  • Programmer's Errors
  • Application's Errors

Programmer's Errors

  • The code needs to be fixed,
    e.g. remember to null check a reference
  • You don't need to "handle" them,
    you want them to stop the application loudly
    so you don't forget that your code needs to be fixed
  • It's important than a developer sees
    the stack trace for ease of fixing the code
  • Use unchecked exceptions

Application's Errors

  • Move the "error" to a method where an
    action can be taken, probably in a different layer
  • Caught and do some kind of retrying / Move to presentation layer and make a message with it
  • We never want to show an stack trace to an user
  • Use checked exceptions

Criticisms of Checked Exceptions

#1 They can also be ignored

try {
  // code
} catch (AcmeException e) {

}

There's no language
feature that
can't be misused

#2 They break encapsulation

It's true if you don't
follow best practices

Best Practices

Don't caught too soon!

#1 source of bugs!

When should you caught?

  • If you are going to retry, probably
    as soon as the exception is thrown
  • If you are going to transform it into a
    message, caught on the presentation layer
  • If you are going to log, where you have
    all the information that you need,
    don't log at different classes

Avoid using them
for control flow

Use your IDE judiciously

If your IDE is giving
you a compiler error,
avoid the option of "surround with try/catch"

try {
  // code
  try {
    // code
  } catch (...) {
    // code
  }
} catch (...) {
  // code
}
try {
  // code
} catch (...) {
  try {
    // code
  } catch (...) {
    // code
  }
}
// code
try {
  // code
} catch (...) {
  // code
}

// code
try {
  // code
} catch (...) {
  // code
}

Understanding the information flow is hard,
so it's easy for bugs to hide

More subtle...

String foo() {
  String s = "";
  try {
    // code
    s = ...;
    // code
  } catch(ABCException e) {
    // handling of Error(?)
  }
  return s;
}

Are you sure you
won't return null?

Avoid control-flow

  • try should be the first expression of a method
  • there should never statements after
    the last catch or finally (if there's one)
  • Use more than one return statement
public String getFoo(...) {
  try {
    // code
    return ...;
  } catch (ABCException e) {
    // error-handling
    return "";
  }
}

If you need some parameters to log?

public get(Foo foo, Bar bar) {
  Baz baz = foo.get(bar);
  return get(baz);
}

private get(Baz baz) {
  try {
    // code
    return ...;
  } catch (ABCException e) {
    LOGGER.warn(baz);
    return ...;
  }
}

Avoid long methods

more so, if you
need error-handling

Single Abstraction Level

  • Split into smaller methods
    to better handle the errors
  • Separate the initialization of
    data with the code that uses it

Don't caught Exception

Remember that unchecked exceptions inherit from it

try {
  // code
} catch (Exception e) {
  // DON'T
}

Caught only what you know it would be thrown

try {
  // code
  foo.throwsABCException();
  // code
} catch (ABCException e) {
  // code
}
try {
  // code
  foo.throwsBazException();
  // code
} catch (ABCException e) {
  // Compiler Error!!
}

Don't thrown Exception

Someone will need to caught it

try {
  // code
} catch(ABCException | FooException e) {
  // code
}

Create your own Exceptions

public class InsufficientFundsException
    extends Exception {
   
  private double amount;

  public InsufficientFundsException(double amount) {
    this.amount;
  }

  public double getAmount() {
    return this.amount;
  }
}
try {
   // code
} catch (ServiceException e) {
   throw new InsufficientFundsException(amount);
}

Assertions

Don't handle!

try {
  // code
} catch(RepositoryException e) {
  throw new AssertionError(e);
}

Exception-Safe Java

By Carlos Obregón

Exception-Safe Java

  • 1,082