2007-08-26

Better exception handling

I read Karsten Wagner's Blog: Better exception handling today.

The post in essence touches three key elements of exception handling and design.

Exceptions are an equally important part of API design
Equally important as classes, method, and arguments. What exceptions are thrown where and why, and whether they should be checked, has to be carefully thought of. One should also be careful when the class hierarchy of exceptions, if one should be made at all.

For instance it isn't always the case that all exceptions should be checked (or unchecked for that matter). Be very specific on which cases which could benefit from a checked exception and thus explicit error handling on the client.

The post uses classes from java.io as an example of how not to do it. java.sql and java.remote could also be mentioned with their checked exceptions causing boilerplate code time and time again. An essential problem of their checked IOException, SQLException and RemoteException is that the exception has no way of informing the executing code whether the failure is transient or permanent, and what the recovery or restart actions could be.

For SQLException in JDK 6 this is not longer the cases, as there are now several subclasses of SQLException providing this information. See SQLNonTransientException, SQLRecoverableException, SQLTransientException, and SQLWarning for details.

Nulls considered harmful
I have written about this topic before, as others have. With the ability to declare whether a method accepts or returns nulls, static analysis tools, or even the compiler, could do checks both at compile and execution time giving better error reporting on null references. Static analysis could produce compiler errors when an argument declared not to accept nulls are served a value known to be null.

At run time checks could be added like the one added for arrays indexing, for instance in form of assertions:
assert someVarName != null : "Invalid null dereference of declared nonnull variable 'someVarName'";
These assertions could potentially be disabled by the -da command line option to the JVM.

Unchecked exceptions are better than their reputation
They are far better than many other alternatives, like SIGSEGV and core dumps, and are actually quite robust and helpful in diagnosing an error.

For most cases, a RuntimeException means you have error situations you cannot or will not handle. The default result in Java is that the stack trace is written to the console, and the executing thread stops. Whether or not the execution is restartable depends on your application. If it's a background thread responsible for some kind of periodic check, the thread will need to be rescheduled. If it's a thread responsible for serving some external request, the client could just retry the request.

Aspects to the assistance
One possible way of working around some of these issues is to put AspectJ to work.

Save state to add a restartable checkpoint
Use this to add a checkpoint before attempting an operation known to be erratic and cause inconsistent state:


@Pointcut("call(* dangerousOperation(..))")
public void dangerousOperation() {}

@Before("dangerousOperation() && this(myObject)")
public void saveState(MyClass myObject) {
  myObject.saveState();
}

@AfterThrowing(pointcut="dangerousOperation() && this(myObject)", throwing="e")
public void restoreState(MyClass myObject, SomeDangerousCheckedException e) {
  myObject.recoverState();
  log.warning("Recovered " + myObject + " from " + e);
}

Handling a certain checked exception in a common way
When using an API throwing checked exceptions you have to add boilerplate try / catch blocks around each and every call to this API, something that could be very tedious.

One solution is to use a wrapper API which catches these exceptions and provides error handling and / or unchecked exceptions. Spring JDBC does this for JDBC access.

But you can also use an aspect for this, like this one from ibm.com/developerworks/ :
public class SqlAccess {

  private Connection conn;
  private Statement stmt;

  public void doUpdate(){
    conn = DriverManager.getConnection("url for testing purposes");
    stmt = conn.createStatement();
    stmt.execute("UPDATE ACCOUNTS SET BALANCE = 0");
  }

  public static void main(String[] args)throws Exception{
    new SqlAccess().doUpdate();
  }
}

private static aspect exceptionHandling{
  declare soft : SQLException : within(SqlAccess);

  pointcut methodCall(SqlAccess accessor) : this(accessor) && call(* * SqlAccess.*(..));

  after(SqlAccess accessor) : methodCall (accessor){
    System.out.println("Closing connections.");
    if(accessor.stmt != null){
      accessor.stmt.close();
    }
    if(accessor.conn != null){
      accessor.conn.close();
    }
  }
}

2007-08-24

How do you achieve 100% test coverage?

When doing Test-Driven Development, one critical metric is test coverage. And it is often quite difficult to achieve 100% test coverage on a given method using its "normal" unit tests.

This can often lead to very strange looking test code written specifically to test all possible branches and every corner case. Sometimes this is useful, but more often than not its testing for testings sake.

But if your test suite is good enough and tests all the designed behavior, uncovered code is a good indication of unused code. Try to delete it. If it still compiles and all tests pass, the code was dead weight code you can safely remove.

This is specifically often true for getters and setters for each and every attribute on a particular class. Put YAGNI (You Ain't Gonna Need It) to use and remove all setters and getters not in active use by your code. If you need one later, you could always add it. This makes the code much more compact and easier to read.

But what if the getters and setters are part of an API? Well, then the API should be an interface, shouldn't it?

2007-08-17

When is a feature done?

Far too often a new feature is considered done at an incomplete state.

This is my suggested checklist for when to consider a feature DONE:

  1. It's checked in to source control
  2. It compiles
  3. It passes its unit tests (no, not having unit tests does not count)
  4. It doesn't break any other unit tests
  5. It increases the overall test coverage for the module
  6. The continuous integration server passes the build
  7. It's integration tested with other features and / or modules it pertains to
  8. It's documented in the release notes and / or product documentation
  9. The feature does what it's supposed to do (as defined by the specifications for the feature)
If you answer no to any of these questions, you're not done. Furthermore, if you answer N/A to any of them you should ask yourself whether the feature should be implemented at all.

The last point is perhaps the most difficult to assess, unless you have executable specifications in form of FIT tests or something similar. Furthermore, if you're using Scrum, the feature must be demonstrated on the end-of-iteration demo to be considered done.

2007-08-06

Nyttig søkemotor for javadoc

jdksearch.com er en Google Custom Search som raskt og effektivt gir deg muligheten for å søke i javadoc på java.sun.com.

Du kan også filtrere resultatene på JDK versjon slik at du raskt og enkelt kan finne den relevante dokumentasjonen.

Anbefales!

2007-08-05

Oppdatert XmlEditor

Jeg har oppdatert XPath XML-editoren til også å støtte sletting av elementer. Dette er for eksempel nyttig dersom du ønsker å flytte versjonsnummeret til dependencyManagement i topp-POM for prosjektet.

For å angi at du vil slette en node bruker du følgende syntaks: /xpath:=&

Hvorfor &? Fordi det er ugyldig i XML og dermed ikke har noen reell mening.

Du finner siste versjon her : xmleditor.tar.bz2