Code structure

Classes and Interfaces

  • Take your time, look at the environment of your code and think. When it comes to the establishment of classes and interfaces, there is a lot of mistakes to make that work against the designed architecture and make solving problems afterwards very expensive.
  • Components must implement an interface if they access external resources. Implementing an interface to combine things to reusable units is always a good idea, but before doing so make sure that there is no such similar implementation in place already and how the newly created one would fit into the architecture.
  • 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(){
          ... 
        };
      
        ...

Control flow

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

    Furthermore, there is no need to put the code after the return statement into an explicit "else"-branch. You can easily save one level of block-nesting.

Checking parameters

  • 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.

    /**
     * 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[];
      ...
    }
  • 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, 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.