Search This Blog

Saturday, August 27, 2011

Java Language Rules


We follow standard Java coding conventions. We add a few rules:
Don't Ignore Exceptions
Sometimes it is tempting to write code that completely ignores an exception like this:
void setServerPort(String value) {
    try {
        serverPort = Integer.parseInt(value);
    } catch (NumberFormatException e) { }
}
You must never do this. While you may think that your code will never encounter this error condition or that it is not important to handle it, ignoring exceptions like above creates mines in your code for someone else to trip over some day. You must handle every Exception in your code in some principled way. The specific handling varies depending on the case.
Acceptable alternatives (in order of preference) are:
  • Throw the exception up to the caller of your method.
·         void setServerPort(String value) throws NumberFormatException {
·             serverPort = Integer.parseInt(value);
·         }
  • Throw a new exception that's appropriate to your level of abstraction.
·         void setServerPort(String value) throws ConfigurationException {
·             try {
·                 serverPort = Integer.parseInt(value);
·             } catch (NumberFormatException e) {
·                 throw new ConfigurationException("Port " + value + " is not valid.");
·             }
·         }
  • Handle the error gracefully and substitute an appropriate value in the catch {} block.
·         /** Set port. If value is not a valid number, 80 is substituted. */
·          
·         void setServerPort(String value) {
·             try {
·                 serverPort = Integer.parseInt(value);
·             } catch (NumberFormatException e) {
·                 serverPort = 80;  // default port for server
·             }
·         }
  • Catch the Exception and throw a new RuntimeException. This is dangerous: only do it if you are positive that if this error occurs, the appropriate thing to do is crash.
·         /** Set port. If value is not a valid number, die. */
·          
·         void setServerPort(String value) {
·             try {
·                 serverPort = Integer.parseInt(value);
·             } catch (NumberFormatException e) {
·                 throw new RuntimeException("port " + value " is invalid, ", e);
·             }
·         }
Note that the original exception is passed to the constructor for RuntimeException. If your code must compile under Java 1.3, you will need to omit the exception that is the cause.
  • Last resort: if you are confident that actually ignoring the exception is appropriate then you may ignore it, but you must also comment why with a good reason:
·         /** If value is not a valid number, original port number is used. */
·         void setServerPort(String value) {
·             try {
·                 serverPort = Integer.parseInt(value);
·             } catch (NumberFormatException e) {
·                 // Method is documented to just ignore invalid user input.
·                 // serverPort will just be unchanged.
·             }
·         }
Don't Catch Generic Exception
Sometimes it is tempting to be lazy when catching exceptions and do something like this:
try {
    someComplicatedIOFunction();        // may throw IOException
    someComplicatedParsingFunction();   // may throw ParsingException
    someComplicatedSecurityFunction();  // may throw SecurityException
    // phew, made it all the way
} catch (Exception e) {                 // I'll just catch all exceptions
    handleError();                      // with one generic handler!
}
You should not do this. In almost all cases it is inappropriate to catch generic Exception or Throwable, preferably not Throwable, because it includes Error exceptions as well. It is very dangerous. It means that Exceptions you never expected (including RuntimeExceptions like ClassCastException) end up getting caught in application-level error handling. It obscures the failure handling properties of your code. It means if someone adds a new type of Exception in the code you're calling, the compiler won't help you realize you need to handle that error differently. And in most cases you shouldn't be handling different types of exception the same way, anyway.
There are rare exceptions to this rule: certain test code and top-level code where you want to catch all kinds of errors (to prevent them from showing up in a UI, or to keep a batch job running). In that case you may catch generic Exception (or Throwable) and handle the error appropriately. You should think very carefully before doing this, though, and put in comments explaining why it is safe in this place.
Alternatives to catching generic Exception:
  • Catch each exception separately as separate catch blocks after a single try. This can be awkward but is still preferable to catching all Exceptions. Beware repeating too much code in the catch blocks.
  • Refactor your code to have more fine-grained error handling, with multiple try blocks. Split up the IO from the parsing, handle errors separately in each case.
  • Rethrow the exception. Many times you don't need to catch the exception at this level anyway, just let the method throw it.
Remember: exceptions are your friend! When the compiler complains you're not catching an exception, don't scowl. Smile: the compiler just made it easier for you to catch runtime problems in your code.
Don't Use Finalizers
Finalizers are a way to have a chunk of code executed when an object is garbage collected.
Pros: can be handy for doing cleanup, particularly of external resources.
Cons: there are no guarantees as to when a finalizer will be called, or even that it will be called at all.
Decision: we don't use finalizers. In most cases, you can do what you need from a finalizer with good exception handling. If you absolutely need it, define a close() method (or the like) and document exactly when that method needs to be called. See InputStream for an example. In this case it is appropriate but not required to print a short log message from the finalizer, as long as it is not expected to flood the logs.
Fully Qualify Imports
When you want to use class Bar from package foo,there are two possible ways to import it:
  1. import foo.*;
Pros: Potentially reduces the number of import statements.
  1. import foo.Bar;
Pros: Makes it obvious what classes are actually used. Makes code more readable for maintainers.
Decision: Use the latter for importing all Android code. An explicit exception is made for java standard libraries (java.util.*, java.io.*, etc.) and unit test code (junit.framework.*)

No comments:

Post a Comment