wiki:ProgrammingGuidelines

Java programming guidelines and toolkit selection

We ask contributors to Asap to adhere to certain programming guidelines. During the development of Asap we have selected certain 'standard' libraries for specific tasks (for e.g. logging, unit testing, collections). Contributions to Asap should preferable not introduce dependencies on new libraries that provide very similar functionality as these libraries.

Library overview

Functionalitylibrary
Logging SLF4J
Generic utilities (e.g. extensions to/utilities for java.collection) guava
Thread safety annotations jcip
Unit testing JUnit
UI Testing fest
Mocking mockito,  powermock-mockito if needed (e.g. to mock final classes)
Creating custom assertions in unit tests hamcrest

Programming style guidelines

The most important programming style rules are:

  • Javadoc should be provided for every public class
  • Do not use tabs
  • Use @Override when overriding a function of a super class
  • Use the standard Java naming-scheme for classes, methods and variables

We make use of  checkstyle to check programming style. Asap specific checkstyle configurations can be found in the HmiShared/ant/checkstyle directory; a light-weight version of the required programming style is found in HmiShared/ant/checkstyle/hmi-verylite.xml. If you use Asap's build system, you can check the style of your project using:

ant checkstyle

or using the checkstyle eclipse plugin.

Exception handling guidelines

  1. Use checked exceptions for exceptions that the client can take useful actions upon. Use RunTimeException and its subclasses otherwise ([1], item 58, 59).
  2. Store information in the exceptions so that the client can use this information in recovering from them (for example: the AudioUnitPlayException contains the failing AudioUnit) ([1], item 63).
  3. A chain of exceptions can be used to translate a low-level exception into a higher level one ([1] item 61). When this technique is used, always include the original exception in the higher level exception (using initCause), so that it stack trace can be used in debugging.
  4. Don't ignore exceptions ([1], item 65). Either catch them and act upon them or throw them. If an exception can't happen, but has to be caught anyways, throw an AssertionError. If the occurrence of an exception doesn't influence the progress of the client (for example, a file not properly closing after reading all information from it), at least log it.

Dealing with Data

To allow easy jar and webstart deployment, all data required to run your project should be in its resource directory and loaded through hmi.util.Resources/hmi.util.ResourcePool. See UsingResources for more details on this.

Asap Logging Setup

All logging within Asap code is done through the  Simple Logging Facade for Java. This facade requires the slf4j-api.jar in the classpath. The output of SLF4J can be redirected to the logger of the clients choice, by adding the appropriate jars in the classpath. To allow this, the Asap framework code itself should not put any of these logging jars in its classpath. For the EnvironmentDemos,  logback (from the developer of  log4j which is no longer actively updated) is set up as the output logger.

Logging levels

SLF4J defines the following levels of priority ordered from low to high:

  • trace: finer-grained than debug, discouraged, could be used for as for extra filtering/redirection as an 'extra' debug level.
  • debug: fine-grained informational events that are most useful to debug an application.
  • info: informational messages that highlight the progress of the application at coarse-grained level.
  • warn: designates potentially harmful situations. The application can continue running after these.
  • error: error events that will presumably abort the application.

The level of a log message is selected by the function of the Logger that is used for logging (e.g. logger.debug("message"), logger.error("error")).

Parameterized logging

SLF4J's parameterized logging is a printf-like logging style:

LOGGER.debug("Hello {}", name);

This avoids the unnecessary performance overhead of string concatenation

LOGGER.debug("Hello "+name);

or code bloat

if (logger.isDebugEnabled()) 
{
      LOGGER.debug("Hello " + name);
}

if the log statement is not executed. If three or more parameters are needed, the log statement has to be called with an Object array. For example:

LOGGER.debug("Hello {} {} and {}", new Object[]{name1,name2,name3});

The logger.isDebugEnabled() version is still recommended if some expensive operation is needed to create the log String:

if (logger.isDebugEnabled()) {
     
      LOGGER.debug("Expensive result: {}", getExpensiveResult());
}

Logging exceptions

SLF4J can log the Exception trace using:

logger.error("Exception message", exception)

Hierarchical logging

Like most loggers SLF4J allows one to set the log level, log destination and log format based on the name of the logger. The naming scheme allows setting up level, destination and format for a group of loggers. For example, one could redirect the logging of loggers hmi.elckerlyc.PegBoard, hmi.elckerlyc.animationengine.GazeMU and hmi.elckerlyc.animationengine.AnimationPlanPlanner by redirecting hmi.elckerlyc, or one could redirect only hmi.elckerlyc.animationengine.GazeMU and hmi.elckerlyc.animationengine.AnimationPlanPlanner by redirecting hmi.elckerlyc.animationengine.

Example:
Send all output to with level >= INFO to the test.html file. In addition, log all output from hmi.elckerlyc with level >= DEBUG to the stdout:

<configuration debug="true">
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="ch.qos.logback.classic.html.HTMLLayout">
          <pattern>%relative%thread%mdc%level%logger%msg</pattern>
      </layout>
    </encoder>
    <file>test.html</file>
  </appender>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="FILE" />
  </root>
  <logger name="hmi.elckerlyc" level="DEBUG">
    <appender-ref ref="STDOUT" />
  </logger>
</configuration>

Logback configuration and tools

The Logback conguration xml is selected by using the run time argument

-Dlogback.configurationFile=logconfig.xml

Several example congurations are set up in the HmiResourcerepository/LogbackConfigs directory. See also above for an example. The  Lilith Logback event viewer can be used to search and filter log events written in Logback's XML format. It can also capture Logback output in real-time using a socket connection.

Redirecting input from other loggers

Input from other loggers (Jakarta Commons Logging, Log4j, java.util.logging) can be redirected to SLF4J (see  http://www.slf4j.org/legacy.html). This is done by placing the appropriate jars in the classpath. Redirecting java.util.logging requires a call to

SLF4JBridgeHandler.install();

This installs SLF4J as an additional logging handler. To get rid of the existing java.util.logging handlers use:

Logger root = Logger.getLogger("");
for (Handler h:root.getHandlers())
{
  root.removeHandler(h);
}

There is a serious performance impact of redirecting java.util.logging over SLF4J, a 60 fold increase is reported for disabled logging statements and a 20% overhead for enabled log statements. So, only use this redirection if the third party software you want to redirect logs from software which has few log statements at performance critical places (this holds for odejava).

Unit and integration testing

Asap uses the  JUnit framework for its automatic testing.

Testing guidelines

  1. Test cases should be simple. Reduce complexity in setup with generic setup functions and/or @Before. Reduce checking complexity and clarity with custom asserts. Avoid using conditional branches in tests cases.
  2. Unit tests should be stand alone. If possible, don't use files, the network, databases and don't share data between test cases. Mockups/stubs/null objects can help here.

Test class naming scheme

Tests classes end with Test, integration test classes end with IntegrationTest, Abstract test classes start with Abstract (and end with Test). This differentiation between integration tests and unit tests allows us to use a quick health check of the code, running only the unit tests.

Test assertions

Use assertions, that, when they fail communicate the failure as clearly as possible. If your test case is full of System.out.println's (or log messages), this is an indication of the assertions not being clear enough. For example:

assertTrue(expected==actual)

Does not communicate the values of expected and actual if an error occurs. One solution is to use the string description of the assertTrue:

assertTrue(expected + "<>" + actual,expected==actual)

But, ofcourse we're lazy and we don't want to write error messages with each assert. Custom asserts in JUnit or other libraries can help us. For example:

assertEquals(expected,actual)

will give you information on actual and expected values if the assert fails. You can write your own asserts for custom data types. Some HMI specic asserts (for example to assert Quat4f or Vec3f equality) are stored in HmiTestUtil.

Custom assertions using hamcrest

 hamcrest is a utility library providing custom assertions with good human-readable error messages. Some examples (added here, because the documentation that comes with hamcrest sucks).

Assert that there are 3 items in someList, prints the content of the list if this is not the case:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
...
assertThat(someCollection,hasSize(3));

Asserts that x > 4 :

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.number.OrderingComparison.greaterThan;
...
assertThat(x,greaterThan(4));

Asserts that x instanceof X:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
...
assertThat(x, instanceOf(X.class));

Asserts that someList has items item1, item2, item3 in any order; someList may contain other items.

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItems;
...
assertThat(someList,hasItems(item1,item2,item3));

Asserts that someList has items item1, item2, item3 exactly in that order; someList may not contain any other items.

import static org.hamcrest.MatcherAssert.assertThat;
import org.hamcrest.collection.*;
...
assertThat(someList,IsIterableContainingInOrder.contains("item1","item2","item3"));

Asserts that someList has items item1, item2, item3 in any order; someList may not contain any other items.

import static org.hamcrest.MatcherAssert.assertThat;
import org.hamcrest.collection.*;
...
assertThat(someList,IsIterableContainingInAnyOrder.containsInAnyOrder("item1","item2","item3"));

Asserts that someCollection has size 1 (does not show list items if this is not the case)

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
...
assertThat(someCollection,hasSize(1));

Asserts that someCollection is an empty list of String (and shows the collection elements if this is not the case):

import static org.hamcrest.MatcherAssert.assertThat;
import org.hamcrest.Matchers;
...
assertThat(someCollection,Matchers.<String>empty());

Assert that someList has an item of class X.

import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItemInArray;
...
assertThat(someList.toArray(), hasItemInArray(instanceOf(X.class)));        

Mocking

TODO: some simple examples

Advanced: argument verification with an ArgumentCaptor?:

ArgumentCaptor<BMLBlockPeg> argument = ArgumentCaptor.forClass(BMLBlockPeg.class);
verify(mockScheduler,times(1)).addBMLBlockPeg(argument.capture());
assertEquals("bml1",argument.getValue().getId());
assertEquals(0,argument.getValue().getValue(),0.001);

Parameterized testing

Parameterized test classes allow the reuse of test case logic with different input parameter values. Each test case in a parameterized test is executed multiple times, with a different (pre-specified) set of input parameter values. The default parameterized test functionality in JUnit does not communicate the values of the parameters when the parameterized test fails. The HmiTestUtils package provides LabelledParameterized test runner, which fixes this by allowing you to add a label to each parameter instance.

Example:

@RunWith(LabelledParameterized.class)
public class SchedulerParameterizedIntegrationTest
{
    @Parameters
    public static Collection<Object[]> configs() throws Exception
    {
    	Collection<Object[]> objs = new ArrayList<Object[]>();
    	
        for(int i=0;i<..;i++)
        {       
          Object obj[] = new Object[2];
       	  SpeechEngineFactory sp = ...
      	  obj[0] = "SpeechPlanner = " + sp.getType();//label
          obj[1] = sp;                               //test parameter 1
          objs.add(obj);	
        }
        return objs;
    }
    private SpeechEngineFactory speechEngineFactory; 

    public SchedulerParameterizedIntegrationTest(String label, SpeechEngineFactory sv)
    {
       speechEngineFactory = sv;
    }

    @Test
    public void test()
    {
       assertSomething(speechEngineFactory);
    }
    ...
}

Creating testable code: Dependency Injection

class Foo
{
  private final Bar bar;
  public Foo()
  {
    bar = new Bar();
  }
}

Is less testable than this:

class Foo
{
  private final Bar bar;
  public Foo(Bar b)
  {
    bar = b;
  }
}

Because in the first case you cannot easily mock bar, which makes it hard to test Foo in isolation. The second form is called dependency injection (you inject the dependency to bar into the constructor). When using dependency injection, a convenience no-argument constructor can still be added:

class Foo
{
  private final Bar bar;
  public Foo(Bar b)
  {
    bar = b;
  }
  public Foo()
  {
    this(new Bar());
  }
}

BiBTeX

Bibtex references stored on BibTex and BibTexElckerlycPublications

References

  1. ^Bloch, Joshua, Effective Java (2nd Edition), Prentice Hall, 2008, book, 0321356683,

User contributions