Coding Guidelines and Rules

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

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:

/*
 * © /Vorname und Name des Studenten/, Freie Universität Berlin /Jahr/
 */

Project set-up

Coding rules

Naming Conventions

Visibility

Interfaces and Classes

Getter and Setter

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

JavaDoc Tags

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

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

Dependency Injection

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.

   /* 
    * one comment that takes up at least
    * two lines
    */

Kinds of comments

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

There are six categories of comments:

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.

   /* 
    * TODO FIX: Our caller should be able to distinguish whether the
    * query failed or it is an IM client which sends back the message
    */

   /* 
    * move all chess pieces to start position
    */

   /* 
    * initialize a new chess game
    */

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.

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:

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:

Display.getDefault().syncExec(new Runnable() {
  public void run() {
    MessageDialog.openError(Display.getDefault().getActiveShell(),
      "Dialog Title", "Error message");
  }
});
YourPlugin.getDefault().getLog().log(
  new Status(IStatus.ERROR, "Plug-In ID goes here", IStatus.ERROR, message, e));

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

Dealing with InterruptedException

Little Things and Best Practices