You are here: Wiki>SE Web>RulesHome>CodeRules (11 Aug 2011, MeikeJohannsen)Edit

Coding Guidelines and Rules

This page describes the rules that we use when writing source-code.

Copyright assignment and license

We prefer the use of the modified BSD license for software created as part of a thesis but also accept software licensed under any other OSI certified Open Source license which is compatible with the GPL.

In addition to the license header, all source code files have to bear the following copyright header:

/*
 * (c) /Vorname und Name des Studenten/, Freie Universitšt Berlin /Jahr/
 */

  • All 3rd party code that has not been written by the student or a member of the AG SE needs to be kept in a separate source folder named ext-src
    • All such files need to be clearly marked by the source of the code, and the license this code has.

Project set-up

  • The package root for your projects needs to be de.fu_berlin.inf.#project# for instance de.fu_berlin.inf.wikidoc or de.fu_berlin.inf.ecg. This follows Suns suggestions of how to choose a package name.
  • The project needs to be hosted on a project hosting site like Berlios, SourceForge or Splineforge with CVS or SVN write access for the advisor.
  • The root folder of each project, needs to contain a file readme.txt which contains instructions for how to run the application and gives a short overview of the code-base.

Coding rules

  • All code and comments should be in English language.

Naming Conventions

  • This section is mainly based on the Code Conventions for the Java TM Programming Language, Chapter 9
  • Differences and additional conventions
    • Non-Listener interfaces should be preceded by an I
      • e.g: IProject, IPreferenceStore
    • Listener interfaces should use the name of their corresponding class and add Listener to it
      • e.g. MouseListener
    • Abstract base implementations for listener interfaces should use the name of their implemented interface and add Adapter instead of Listener to it
    • Listener interfaces and abstract base implementations should be placed in a sub package named events, relative to their corresponding class
      • e.g. if class Foo resides in package de.fu_berlin.inf.bar, then FooListener and FooAdapter have to reside in de.fu_berlin.inf.bar.events
  • All test case classes must end with Test. e.g HelloWorldTest
  • A test suite classes must contain the character sequence TestSuite
  • All plugin (Eclipse PDE JUnit test) test cases must end with PluginTest
  • Every test class that is used for White-Box testing must be declared in the same package , e.g foo.bar.HelloWorld -> foo.bar.HelloWorldTest
  • STF test cases must be put in any subpackage of de.fu_berlin.inf.dpp.stf.test , e.g de.fu_berlin.inf.dpp.stf.test.account.AccountPreferencePageTest

Visibility

  • By default all attributes and methods should be protected or public to indicate whether this attribute is part of the public signature (then public) or not (then protected).
  • If you choose to restrict visibility further (private) you need to explain the reason for this in the JavaDoc comment, because a private attribute should make anybody stop and think "Uh-Oh. I better don't mess with this.". If we use private for everything then we would not need protected at all.
    • Thus making something private should be a deliberate and conscious act and not something you do automatically (use protected instead) as it restricts the possibilities of others.
    • A good way to think about it is:
        • Public == Go use it
        • Protected == I did not want anybody to use this field, but there is no magic involved.
        • Private == I did use magic on this field (probably undocumented), beware!
    • Typical reasons can be:
      • An attribute is used by a method for optimization and caching and the meaning and timing effects of its current value is hard to describe to an external user.
      • This attribute is specific for this single class (such as a Logger or a SerialID, which subclasses need their own field of)
    • The following are NO valid reasons:
      • The method is not part of the public interface of the class, but rather used for internal computation (use protected, or define an interface)
      • You do not trust other programmers to do the right thing
      • You are embarrased at your own code quality and do not want others to use your method.

Interfaces and Classes

  • If you develop a listener interface with more than one method, you should in most cases provide an abstract base implementation which provides empty implementations, because in many cases implementors of your interface will not want to implement all methods. Also it helps to improve ability to change the program, as methods can be added to the interface more easily.
  • Do not implement more than one listener interface per class, especially if using a top level class, because it makes the code much less readable and makes you more likely to forget unregistering your listener.
    • Anonymous inner-classes assigned to fields are much better: Instead of
      public class A implements B, C, D {  
      ...
             
      you should write
      public class A implements D {  
      
        B b = new B(){
          ...
        };
      
        C c = new C(){
      
        };
        ...
             

Getter and Setter

  • Internal Local Entity Classes do not need (but may) use getters/setters
  • All other cases should use getters and setters
  • Internal collections may be returned from classes, but it MUST be clearly indicated whether the returned value MUST not be changed (then an immutable view should be returned) or whether a user of a class MAY change the collection (in which case the class must ensure that the behavior is well defined, for instance using an observable collection)

Field Initialization

Instance variables have to be initialized in their declaration. Only if this is not possible they may be initialized in the constructor.

Control Flow

  • Test whether you can return from a method instead of testing whether you should execute a block of code. Instead of
    	    public void ... {  
    		    ...
    		    if (condition){
    		        // long block of code
    		    }
    	    }
           
    you should write
    	    public void ... {
    	    
    	        ...
    	        if (!condition)
    	            return
    	            
    	        // long block of code
    	    }
           

  • Methods may assume that they are called with correct non-null input unless the method specifies that it allows incorrect or null input.
  • If a parameter may be null or is checked add a @param-JavaDoc that indicates this.
  • If a method checks for correct parameters it should throw an IllegalArgumentException in case of error.
    • It is recommended to perform checking in particular at important component boundaries. For instance in the Saros project we had a central entry point were events were buffered for sending over the network. Somehow a null event was inserted into the buffer queue and caused a NullPointerException later on. The programmer of the method which inserted the event into the buffer should have checked at this important boundary with many callers.
  • Use assert to check for complex preconditions, that cost a lot during runtime.
        /**
         * Get positions of slashes in the filename.
         * @param filename may be null
         * @return Null-based indices into the string pointing to the slash positions.
         */
        public int[] findAllSlashes(String filename) {
            if (filename == null)
                return new int[];
            ...
    

JavaDoc Tags

  • The following JavaDoc tags should be used for highlighting important aspects of a method or a class:
    • @ui or @swing or @swt - This method needs to be called from the UI thread, otherwise an exception might occur.
    • @nonBlocking - This method does return before finishing its activities. If there is at least one method is a class which is non-blocking it is highly recommended to put @blocking to all other methods to help users recognize that blocking behavior is important for this class
    • @blocking - This method is potentially long-running and blocks until it is done. This is the default for all method, which are unmarked.
    • @valueObject - The objects of this class are immutable. All methods are side effect free.
    • @nonReentrant - This method cannot be called twice at the same time.
    • @threadsafe - This method can safely be called twice at the same time.
    • @caching - If possible, this method uses a cache to calculate the return value.
  • Threading and Concurrency
    • Try to avoid instantiating the class Thread directly but rather use a ThreadFactory (in particular the NamedThreadFactory so that your threads are named) or even better an Executor.
    • Spend some time learning about the Java Concurrency library java.util.concurrent.

Progress & Cancelation 101

Whenever a method is long-running, i.e. there is a chance that it will take longer than 100ms or involves any external factors such as the user or input/output, the software engineer is responsible to provide a way to track progress of the operation and provide to the caller the possibility to cancel the operation.

If the software engineer does not provide such opportunity the user experience might be reduced. For instance in Saros, there used to be no possibility to cancel a file transfer from one user to the other but just to cancel in between the files. This behavior seems okay, but once we started to see archive files of 10 MB and more, which could only be canceled by disconnecting from the Jabber-Server, the undesireability of the behavior becomes pretty clear.

Fortunately enough there is a straight-forward and simple solution, which also improves the general threading behavior of the application: The class to use is called SubMonitor which implements the IProgressMonitor interface.

Now all methods which are long running, need to be changed to take a SubMonitor as a last parameter (this is our convention):

public Result computeSomething(List<Something> input, ..., SubMonitor progress){

Inside those methods, first, we need to initialize the ProgressMonitor with the name of the task and the number of steps this task will take (if the number of steps is unknown we set it to a large integer, 10000 is our convention):

progress.beginTask("Computing Something", input.size());

Now whenever we have made some progress towards this task, we can report this to the monitor:
  for (Something some : input){
    ... process input
    progress.worked(1)
  }

At the end of the task, we should report, that we are done with the task:

progress.done()

This basic contract of beginTask(), worked(), and done() is sufficient to achieve the basic uses of progress monitoring.

Nesting Progress

In many cases the situtation is a little bit more complicated, as the operation that is long-running is making calls to other long-running operations as well. To solve this problem, we need to create child progress monitors, which consume a given number of work steps from their parent:

public void computeInTwoSteps(SubMonitor progress){

  progress.beginTask("Compute in two steps", 2);

  computeFirstStep(progress.newChild(1));

  computeSecondStep(progress.newChild(1));

  progress.done();
}

This code will pass two =SubMonitor=s to the two methods, which then are free to use them just as the parent method did:

public void computeFirstStep(SubMonitor progress){

  progress.beginTask("Compute the first step", 140);
  ...
  progress.worked(5); // etc.
  ...
  progress.done();
}

Reporting information to the user

A progress monitor provides 3 ways to report information from a long running operation to the user

  • The amount of steps already worked as given by worked()
  • The name of the task, as set using beginTask(String) and setTaskName(String)
  • The name of the sub-task, as set using subTask(String)

This information is typically presented to the user as a Dialog with a message being equal to the taskname of the top level progress monitor, a progress bar showing the growing amount of work already done and a label for the current sub-task which switches everytime the sub-task is being set.

Since the taskName is fixed (by default), only the top level task name is shown to the user. The task name of the nested operations are never shown to the user. To report status messages from nested operations, the sub-task needs to be used:

public void computeInTwoSteps(SubMonitor progress){

  progress.beginTask("Compute in two steps", 2);

  progress.subTask("Two Step Computation: Step 1");
  computeFirstStep(progress.newChild(1));

  progress.subTask("Two Step Computation: Step 2");
  computeSecondStep(progress.newChild(1));

  progress.done();
}

Dealing with operations of unspecified length

To have a progress dialog on operations for which the amount of steps are unknown, the following solution is recommended:

while (!done()){
  ... do work

  progress.setWorkRemaining(1000);
  progress.worked(1);
}

This code will advance the progress bar 0,1% of the remaining total of the progress monitor and thus logarithmically approach 100% worked. The percentage 0,1% should be adjusted to achieve 50% progress on the expected number of work steps.

Cancellation

To achieve cancellation support in an operation, we should check frequently whether the user has requested that we stop our tasks:

  for (Something some : input){
    if (progress.isCanceled())
      return;
    ... process input
    progress.worked(1)
  }

The easiest way to response to a request for cancellation is to just return as shown above, but in most cases this is undesirable, because the caller will not know whether the operation finished or not. Instead, methods should rather throw a CancellationException so that a caller can recognize that the operation was canceled:

/**
 * @cancelable This long-running can be canceled via the given
 * progress monitor and will throw an CancellationException 
 * in this case.
 */ 
public BigInteger factorial(int n, SubMonitor progress){

  progress.beginTask("Computing Factorial of " + n, n);

  BigInteger result = BigInteger.ONE;

  for (int i = 1; i < n; i++){
    if (progress.isCanceled())
      throw new CancellationException();

    result = result.multiply(BigInteger.valueOf(i));
    progress.worked(1);
  }

  progress.done();
  return result;
}

Btw: It is an convention that we try to avoid InterruptedException for this, because it is a checked exception and thus cumbersome for the caller. To maintain this convention, a method MUST specify whether it is cancelable, by providing the demonstrated JavaDoc tag.

Software Design Rules

  • Distinguish clearly between the common building blocks of applications, namely:
    • Services - Providers of functionality that can be well encapsulated
    • Entities - Mutable classes that represent the business world. Group these into aggregates.
    • Values - Use the Value Object Pattern to ensure that users of the class can rely on the objects to be immutable and all methods to be side effect free. This helps A LOT when using a class.
  • Avoid implementing many interfaces in the same class whereever possible (more than one is necessary only in rare cases). Use nested or anonymous classes instead. It makes it much easier to understand and modify your code.

  • The Singleton Pattern is a inferior solution to define the architecture of an application compared to using Dependency Injection (see next section). To achieve the lazy semantics often also associated with the Singleton Pattern you should inject a dependency to a lazy initializer of the component that you actually need instead.

Dependency Injection

  • The use of a dependency injection framework such as Spring or PicoContainer is highly recommended because it improves the readability, changeability and modularization of the architecture of a software application.
  • Short introduction:
    • The goal of dependency injection is to solve the basic software engineering problem of how a object of a class A (usually a class representing a component) receives the objects of other classes B and C which it needs during the programm execution.
      • A typical example would be that the application core needs access to a preference object to work.
    • The solution that dependency injection provides is to let this problem be solved by a dependency injection container, which has a defined way to resolve such dependencies.
    • This involves three steps:
      1. Telling the dependency container which classes need to be managed by it
        • In PicoContainer:
          • container.addComponent(A.class)
      2. Changing the classes so that the container can identify what constitutes the classes dependencies.
        • See below
      3. Asking the container for object instances with all their dependencies being set.
        • In PicoContainer:
          • container.getComponent(A.class)
    • For the second step there are three possibilities:
      1. Constructor injection - The dependency container will use the constructor with the least amount of parameters to create an object if it needs one. This is the cleanest type of injection, since the dependencies are available to an instance of the class right from the start.
      2. Annotated field injection - The dependency container will look for fields with special annotation tags (in PicoContainer @Inject) and AFTER the constructor has been called will fill those fields with the dependencies. This type of injection, provides the code with the best readability (constructors with many parameters are difficult to read) but is more difficult to manage, because dependencies are not available in the constructor.
      3. Setter injection - Injection is done via setters. This is a possibility we do not use.
      • Recommendation: Since PicoContainer supports all three types at the same time, we should use Annotated field injection whereever possible and Constructor injection otherwise.

  • Dependency injection and Eclipse
    • Since Eclipse creates many objects itself such as Views and Actions, we cannot use constructor injection with such components.
    • Instead a reinjector needs to be used which can take an existing object and using annotated field injection will pass the missing dependencies.

Broadcast Listener

To avoid repeated code blocks in an Observable like

class Observable {

  List<Listener> listeners = new ...
  public void addListener(Listener listener){listeners.add(listener)}
  public void removeListener(Listener listener){...}

  public void someMethod() {
    ...
    // notify all listeners
    for (Listener l : listeners) {
      l.notify();
    }
  }

  public void someMethod2() {
    ...
    // notify all listeners again
    for (Listener l : listeners) {
      l.notify();
    }
  }

}

It is recommended to use a helper class BroadcastListener that provides a method to notify all its registered listeners. The BroadcastListener should be a singleton managed by PicoContainer.

public class BroadcastListener implements Listener {

  List<Listener> listeners = new ...
  public void add(Listener listener){listeners.add(listener)}
  public void remove(Listener listener){...}

  public void notify() {
    for (Listener l : listeners) {
      l.notify();
    }
  }

}

The code for an Observable becomes therfore much simpler. It only needs to know the BroadcastListener and can easily notify all registered listeners at once.

class Observable {

  BroadcastListener listener = new BroadcastListener();

  public void someMethod(){
    ...
    listener.notify();
  }

  public void someMethod2(){
    ...
    listener.notify();
  }
}

Common Templates

Registering a listener on a changing instance:

public void setSharedProject(SharedProject newSharedProject) {

     if (sharedProject == newSharedProject)
         return;

     // Unregister from old project
     if (sharedProject != null) {
         sharedProject.removeListener(this);
     }

     sharedProject = newSharedProject;

     // Register to new project
     if (sharedProject != null) {
         sharedProject.addListener(this);
     }
} 

Language & Documentation

All source documents are expected to be documented and written in English.

  • JavaDoc documentation and names should be meaningful and make the programming element understandable with minimum insight into the code. If your comments make the code more difficult to understand, remove them.

  • Don't use single line comments (starting with //) for multi line text, use instead
   /* 
    * one comment that takes up at least
    * two lines
    */

  • Comments should describe complex code in shorter text. Comments like "Create HashMap", "Set Value", "Loop here", or "else" should be removed.

Kinds of comments

This section is based on Steve McConnell's Code Complete, Chapter 32.

There are six categories of comments:

  • Repeat of the Code
    • If something is not complex and thus documentation repeats code, you should not document at all. For instance, don't document getters and setters (an exception would be to explain what the variable actually contains that you are getting).

A counter-example:

    /**
     * return a stack of String,
     * @param text
     * @return Stack
     */
    public Stack getFormatJavadocLine(String text) {
        StringTokenizer st = new StringTokenizer(text, "\n");
        Stack stack = new Stack();
        while (st.hasMoreTokens()) {
            stack.add(st.nextToken());
        }
        return stack;
    }

The documentation is absolutely useless as it just repeats the signature and even fails to explain the method name. Furthermore the method name is wrong for the task that is actually performed. The important question whether this method returns a stack of the individual lines in text from top to bottom or bottom to top remains unanswered.

  • Explanation of the Code
    • If the code needs to be explained, it is usually better to improve the code instead of adding comments.

  • Marker in the Code
    • Marker comments are notes for the developers that the work isn't done yet. They should not be left in the code. If you have to mark a section in the code, use „TODO“. This way all marker comments will be standardized and it is easier to locate them.
   /* 
    * TODO FIX: Our caller should be able to distinguish whether the
    * query failed or it is an IM client which sends back the message
    */

  • Summary of the Code
    • Comments that summarize the code in a few sentences can be valuable especially for readers who haven't written the code. They can scan these comments more quickly than the code.
   /* 
    * move all chess pieces to start position
    */

  • Description of the Code's Intent
    • Intent comments explain the purpose of a section of code. In contrast to the summary comments which operate at the level of the solution the intent comment operates at the level of the problem.
   /* 
    * initialize a new chess game
    */

  • Information that cannot possibly be expressed by the Code Itself
    • Some information needs to be written in comments since they cannot be expressed in code. This can be copyright notices, version numbers, notes about the code's design, optimization notes and so on.

Acceptable code comments are summary comments, intent comments and information that cannot be expressed in code. Markers are acceptable during development but should be removed before release. Try to avoid comments that repeat or explain the code.

What to comment

Generally you should document the code starting from the highest level of code hierarchy. This means that all packages need a documentation followed by interfaces and classes. All documentation should be in JavaDoc comments in order to automatically generate HTML source code documentation.

  • Each package should be documented in a package-info.java file located in the package folder.
    • The package description should help the reader to understand, what the package is about and what its intent is. It should also inform about terms to adhere when using or modifying this package. Important relations to other packages should be described here.

  • Each interface should be documented.
    • The comments in the interface should provide a short description of what you can use it for. For all exposed routines you should at a minimum document each parameter and each return value but hide implementation details.

  • Each class should be documented.
    • The description of the class should provide a short overview of what this class is about. Design decisions and limitations should be mentioned as well.

  • Methods should be documented, if they are complex enough and it will be helpful for other readers to summarize or explain the purpose of these methods.
  • Important objects and variables should also be briefly documented.

Before Committing: Formatting and cleaning-up

  1. We expect source code to be formatted and cleaned up using an automated tool prior to submission.
    • For Java use the Eclipse code formatter on the project (Source -> Format) which should loosely follow the Sun coding conventions.
    • For C/C++ use indent.
    • Make sure that
      • Identation is consistent
      • All Eclipse warnings are fixed
      • Imports are organized
    • The easiest way to achieve all this in Eclipse is to enable Save-Actions that perform exactly these.
  2. When committing use one or more of the following tags to make it easier for people to understand what your commit was about:
    • [NOP] This commit did not have any effect and only affects whitespace, removing unused methods, fixing documentation typos, etc.
    • [DOCU] Improves JavaDocs or comments
    • [TASK] Adds things that need to be done
    • [API] Affects the interface or dependencies of a component without creating any new functionality or fixing an existing bug
    • [INTERNAL] Only affects the details of the implementation without any effects to users of the component.
    • [REFACTOR] API or INTERNAL changes, which are done using automated tool support. So while REFACTOR changes usually result in large diffs, they are not very error prone. Caution: A refactoring should always be a separate patch, because refactorings are very hard to read.
    • [FIX] #Number: Bug description from SF Fixes a bug, if possible attach the SourceForge Bug #ID
    • [FEATURE] Improves the functionality of the software
    • [LOG] Improves Logging with Log4j
    • [UI] Improvements to the user interface
  3. Take care of the line delimiters. We only use Unix line delimiters. So if there are other delimiters you have to convert them (Eclipse -> File -> Convert Line Delimiters to... -> Unix). You can set the SVN properties for the EOL style automatically for every new file if you create a directory containing a file named config. The config file contains the following:
          
          [auto-props]
          *.java = svn:eol-style=LF
          *.txt = svn:eol-style=LF
          

Error Reporting, Exception Handling and Logging

We expect that all source code used in thesis to deal gracefully with errors. The goals to aim for should be:

  • Provide information to the user that something went wrong and how s/he can fix the problem (if). A typical example for this is a failed to log-in to a server, because the password is wrong which should lead to a error-dialog box. In the best of all cases this error message will offer the user to correct the problem or lead him to the place to correct it (for instance a preference dialog).
  • Provide information to the developer when some operation failed that should not have (unexpected) and where the problem occurred (stack-trace).
  • Provide information about the kind of events that happened internally (tracing/logging). It should be possible to disable these.

The first step to achieve this, is to make sure that you notice when things go wrong. Thus, all Runnables passed to Threads or Executors and all methods called from 3rd party software, such as Actions called from Eclipse, or Listeners from the network API need to be made secure as to catch all RuntimeException that might have slipped up.

Use the following method for this (you might want to pass up =RuntimeException=s up the stack as well):

/**
 * Return a new Runnable which runs the given runnable but catches all
 * RuntimeExceptions and logs them to the given logger.
 * 
 * Errors are logged and rethrown.
 * 
 * This method does NOT actually run the given runnable, but only wraps it.
 */
public static Runnable wrapSafe(final Logger log, final Runnable runnable) {
    return new Runnable() {
        public void run() {
            try {
                runnable.run();
            } catch (RuntimeException e) {
                log.error("Internal Error:", e);
            } catch (Error e) {
                log.error("Internal Fatal Error:", e);

                // Rethrow errors (such as an OutOfMemoryError)
                throw e;
            }
        }
    };
}

When developing in Eclipse the following code-snippets might help:

  • Error reporting to the user can be done using an ErrorDialog:
Display.getDefault().syncExec(new Runnable() {
  public void run() {
    MessageDialog.openError(Display.getDefault().getActiveShell(),
      "Dialog Title", "Error message");
  }
});
  • Error reporting for the developer can be done using the ErrorLog:
YourPlugin.getDefault().getLog().log(
  new Status(IStatus.ERROR, "Plug-In ID goes here", IStatus.ERROR, message, e));

  • Input from the user needs always to be checked and untainted on input.
    • Error messages need to be human readable.
    • Exceptions need to be handled correctly, i.e.
      • If the program could do something about the exception it should try (for instance repeat an failed operation).
      • Otherwise an unchecked exception should be thrown to prevent =throws=-clauses littering the code.
      • If you can't handle the exception then throw it back to the caller

Anti-example:

    public Javadoc updateJavadoc(String filename, String name,
            String newJavadocText, int isType) {
        Javadoc jd = null;
        try {
            ... Try to update Javadoc ...
        } catch (Exception e) { // No, no, no!
            e.printStackTrace(); 
        }
        System.out.println("The new javadoc-------\n" + jd); 
        return jd; 
    }
Better:

    public Javadoc updateJavadoc(String filename, String name,
            String newJavadocText, int isType) {
        Javadoc jd = null;
        try {
            ... Try to update Javadoc ...
        } catch (IOException e){ // Catch the checked exceptions that occur
            // Transform to runtime exception if you don't know what to do with it.
            throw new RuntimeException(e);
        } // let all runtime exceptions go up the stack
        System.out.println("The new javadoc-------\n" + jd); 
        return jd; 
    }

How to do it right:

    public Javadoc updateJavadoc(String filename, String name,
            String newJavadocText, int isType) throws IOException{
        Javadoc jd = null;
        try {
            ... Try to update Javadoc ...
        } catch (IOException e){ // Catch the checked exceptions that occur
            // bring the internal logic back to a stable state (if you can)
            throw e; // let the caller handle this exception
        } 
        System.out.println("The new javadoc-------\n" + jd); 
        return jd; 
    }

Logging with Log4J

  • We use one private static final logger per class. An editor template for Eclipse:
    private static final Logger log = Logger.getLogger(${enclosing_type}.class);
  • We use the following Log-Levels:
    • ERROR An error should be printed if something occurred which is the fault of the developer and which will cause unexpected behavior for the user.
    • WARN A warning should be printed if something occurred which is the fault of the developer but which while not expected to occur should be handled gracefully by the application.
    • INFO Any information that is of interest to the user may be printed using INFO.
    • DEBUG Any information which might be useful for the developer when figuring out what the application did may be printed as DEBUG. The amount of information printed should not cause the performance of the application to suffer (use TRACE for this).
    • TRACE Detailed information about the program execution should use the TRACE level. This information usually is so detailed that the application runs slower and thus should never be enabled in a production version.

Dealing with InterruptedException

  • When calling a blocking method, Java uses InterruptedException to signal that the waiting thread was told to stop waiting.
  • As a a caller to a blocking method it is your responsibility to deal with the possibility of being interrupted. This is why exception is checked in Java.
  • The contract of InterruptedException is the following:
    • If interrupted a method honoring the contract MUST either throw the InterruptedException or set the interrupt-flag.
  • Since the InterruptException-contract is assumed to be honored by all methods in Java, there are three ways of dealing with the InterruptedException:
    1. Rethrowing the InterruptedException to tell callers that this method might be interrupted: As we do not like checked exception this is an inferior solution to the problem.
    2. Resetting the interrupt flag
      • It is your responsibility to always reset the Interrupt flag in case you catch the Exception, because somebody who called you might depend on it. This will look like this and is the normal case for 90% of all cases:
        try {
          Thread.sleep(500);
        } catch(InterruptedException e){
          Thread.currentThread().interrupt(); // The code will continue after after the catch
        }
        
      • This is recommended even if you are sure that you will never be interrupted (because the program might change in the future)
      • Special case: Spinning / Busy Waiting
        • If you use Thread.sleep() inside a while() loop, then you cannot use the above pattern without leaving the while loop, because Thread.sleep() (all methods that honor the contract of InterruptedException) will return immediately without sleeping. Thus your while loop becomes a busy waiting loop.
        • If you really do not want to be interruptible, then you need to do the following:
          boolean interrupted = false;
          try {
            while (looping){
              // do stuff
              try {
                Thread.sleep(500);
              } catch(InterruptedException e){
                interrupted = true;
              }
            }
          } finally {
            if (interrupted)
              Thread.currentThread().interrupt(); // The code will continue after after the catch
          }
          
    3. Tell others that you violate the contract: Add to the signature of your method, that you do not support the contract of Thread.interrupt(). Be aware that by violating the InterruptedException-contract you put all your callers into violation as well (since if handle an InterruptedException incorrectly they cannot honor the contract anymore). Use the following to tell callers that you do not honor the contract:
      /**
       * @nonInterruptable This method does not support being interrupted
       */
      
  • BTW: There is no obligation in the contract for you to exit as quickly as possible.
  • For more read: http://www-128.ibm.com/developerworks/java/library/j-jtp05236.html

Little Things and Best Practices

  • To convert a primitive to a String use String.valueOf(...). Don't use "" + i.
  • To convert from a primitive use Integer.parseInt(...) and similiar.
  • Don't use StringBuffer unless you need threadsafety, use StringBuilder instead. * Similarly don't use Vector, use ArrayList
  • Use IO Utils when dealing with reading and writing files. Do not try to invent the wheel and read individual bytes out of streams.
    • Do use IOUtils.closeQuitely() for closing streams, as it handles all cases (null, exceptions) in one line
  • Whenever possible exit a method early and avoid nesting too deep:
    if (...){
        return null;
    } else {
        // do a lot of things...
            // nested very...
                // ...deeply
    }
    
    can easily become
    if (...){
        return null;
    }
    // do a lot of things...
        // nested only...
            // ...deeply now (you saved a level of nesting)
    
  • Never subclass Thread, always pass a Runnable into the constructor instead
  • Run your program using -ea and use asserts to let people understand what you were assuming in your code.
  • To convert from an IPath to a String use IPath.toPortableString() to convert from a String to an IPath use Path.fromPortableString() (one could also use toString() and new Path(String), but we prefer the from/toPortable version).
  • If equals is implemented in a class it is MANDATORY to implement hashCode as well, otherwise HashSets and other classes which rely on the contract of hashCode will not work. To do this correctly always use the Eclipse Generator (Source -> Generate hashCode and equals).
  • Always document the reason if x.compareTo(y) == 0, but x.equals(y) returns false
  • Use enum instead of a bunch of static final int fields. Then you have type safety for related constants and more readable debugging output is possible because you get meaningful names instead of "magic" numbers.
Topic revision: r50 - 11 Aug 2011, MeikeJohannsen
 
  • Printable version of this topic (p) Printable version of this topic (p)