wiki:ProgrammingGuidelines

Version 31 (modified by welberge, 8 years ago) (diff)

--

^ElckerlycDocumentation?

Programming guidelines and toolkit selection

We ask contributors to Elckerlyc to adhere to certain programming guidelines. During the development of Elckerlyc we have selected certain 'standard' libraries for specific tasks (for e.g. logging, unit testing, collections). Contributions to Elckerlyc 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

Exception handling guidelines

  1. Use checked exceptions for exceptions that the client can take useful actions upon. Use RunTimeException and its subclasses otherwise.
  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).
  3. A chain of exceptions can be used to translate a low-level exception into a higher level one. 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. 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.

Hmi Logging Setup

All logging within HMI 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 Hmi 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>

^ElckerlycDocumentation?