Go to primary content
Oracle® Retail POS Suite Implementation Guide, Volume 2 – Extension Solutions
Release 14.1
E54476-02
  Go To Table Of Contents
Contents
Go To Index
Index

Previous
Previous
 
Next
Next
 

3 General Development Standards

The following standards have been adopted by Oracle Retail product and service development teams. These standards are intended to reduce bugs and increase the quality of the code. The chapter covers basic standards, architectural issues, and common frameworks. These guidelines apply to all Oracle Retail applications.


Note:

See the Oracle Retail POS Suite Security Guide for more information about specific security features and implementation guidelines for the POS Suite products.

Basics

The guidelines in this section cover common coding issues and standards.

Java Recommendations

The following are guidelines for what to avoid when writing Java code.

  • Do use polymorphism.

  • Do have only one return statement per function or method; make it the last statement.

  • Do use constants instead of literal values when possible.

  • Do import only the classes necessary instead of using wildcards.

  • Do define constants at the top of the class instead of inside a method.

  • Do keep methods small, so that they can be viewed on a single screen without scrolling.

  • Do not have an empty catch block. This destroys an exception from further down the line that might include information necessary for debugging.

  • Do not concatenate strings. Oracle Retail products tend to be string-intensive and string concatenation is an expensive operation. Use StringBuilder instead.

  • Do not use function calls inside looping conditionals (for example, while (i <=name.len())). This calls the function with each iteration of the loop and can affect performance.

  • Do not use a static array of strings.

  • Do not use public attributes.

  • Do not use a switch to make a call based on the object type.

Avoiding Common Java Bugs

The following fatal Java bugs are not found at compile time and are not easily found at runtime. These bugs can be avoided by following the recommendations in the following table.

Table 3-1 lists some fatal Java bugs and their preventative measures.

Table 3-1 Common Java Bugs

Bug Preventative Measure

null pointer exception

Check for null before using an object returned by another method.

boundary checking

Check the validity of values returned by other methods before using them.

array index out of bounds

When using a value as a subscript to access an array element directly, first verify that the value is within the bounds of the array.

incorrect cast

When casting an object, use instanceof to ensure that the object is of that type before attempting the cast.


Formatting

Follow these formatting standards to ensure consistency with existing code.

  • Indenting/braces—Indent all code blocks with four spaces (not tabs). Put the opening brace on its own line following the control statement and in the same column. Statements within the block are indented. Closing brace is on its own line and in same column as the opening brace. Follow control statements (if, while, and so on) with a code block with braces, even when the code block is only one line long.

  • Line wrapping—If line breaks are in a parameter list, line up the beginning of the second line with the first parameter on the first line. Lines should not exceed 120 characters.

  • Spacing—Include a space on both sides of binary operators. Do not use a space with unary operators. Do not use spaces around parenthesis. Include a blank line before a code block.

  • Deprecation—Whenever you deprecate a method or class from an existing release is deprecated, mark it as deprecated, noting the release in which it was deprecated, and what methods or classes should be used in place of the deprecated items; these records facilitate later code cleanup.

  • Header—The file header should include the tag for revision and log history.

    Example 3-1 Header Sample

    /* ===========================================================================
    *    Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. 
     * ===========================================================================
     * $Header:$
     * ===========================================================================
     * NOTES
     * <other useful comments, qualifications, etc.>
     *
     * MODIFIED    (MM/DD/YY)
     *    username  01/04/10 - update header date
     *
     * ===========================================================================
    package oracle.retail.stores.samples;
     
    // Import only what is used and organize from lowest layer to highest.
    import oracle.retail.stores.common.utility.Util;
     
    //----------------------------------------------------------------------------
    /**
         This class is a sample class. Its purpose is to illustrate proper 
         formatting.
         @version $Revision$
    **/
    //----------------------------------------------------------------------------
    public class Sample extends AbstractSample
    implements SampleIfc
    {                                       
        // revision number supplied by configuration management tool
        public static String revisionNumber = "$Revision$";
        // This is a sample data member.
        // Use protected access since someone may need to extend your code.
        // Initializing the data is encouraged.
        protected String sampleData = "";
     
        //---------------------------------------------------------------------
        /**
            Constructs Sample object.
            Include the name of the parameter and its type in the javadoc.
            @param initialData String used to initialize the Sample.
        **/
        //---------------------------------------------------------------------
        public Sample(String initialData)
        {                                   
            sampleData = initialData;      
            // Declare variables outside the loop  
            int length = sampleData.length();
            BigDecimal[] numberList = new BigDecimal[length];
            
            // Precede code blocks with blank line and pertinent comment
            for (int i = 0; i < length; i++)
            {
                // Sample wrapping line.
                numberList[i] = someInheritedMethodWithALongName(Util.I_BIG_DECIMAL_ONE,
                                                                     sampleData,
                                                                     length - i);
            }
        } 
    }
    

Javadoc

  • Make code comments conform to Javadoc standards.

  • Include a comment for every code block.

  • Document every method's parameters and return codes, and include a brief statement as to the method's purpose.

Naming Conventions

Names should not use abbreviations except when they are widely accepted within the domain (such as the customer abbreviation, which is used extensively to distinguish customized code from product code).

Table 3-2 lists some additional naming conventions.

Table 3-2 Naming Conventions

Element Description Example

Package Names

Package names are entirely lower case and should conform to the documented packaging standards.

oracle.retail.stores.packagename

com.mbs.packagname

Class Names

Mixed case, starting with a capital letter.

Exception classes end in Exception; interface classes end in Ifc; unit tests append Test to the name of the tested class.

DatabaseException

DatabaseExceptionTest

FoundationScreenIfc

File Names

File names are the same as the name of the class.

DatabaseException.java

Method Names

Method names are mixed case, starting with a lowercase letter. Method names are an action verb, where possible. Boolean-valued methods should read like a question, with the verb first. Accessor functions use the prefixes get or set.

isEmpty()

hasChildren()

getAttempt()

setName()

Attribute Names

Attribute names are mixed case, starting with a lowercase letter.

lineItemCount

Constants

Constants (static final variables) are named using all uppercase letters and underscores.

final static int NORMAL_SIZE = 400

EJBs—entity

Use these conventions for entity beans, where &rsquor;Transaction' is a name that describes the entity.

TransactionBean

TransactionIfc

TransactionLocal

TransactionLocalHome

TransactionRemote

TransactionHome

EJBs—session

Use these conventions for session beans, where &rsquor;Transaction' is a name that describes the session.

TransactionService

TransactionAdapter

TransactionManager


SQL Guidelines

The following general guidelines apply when creating SQL code:

  • Keep SQL code out of client/UI modules. Such components should not interact with the database directly.

  • Table and column names must be no longer than 18 characters.

  • Comply with ARTS specifications for new tables and columns. If you are creating something not currently specified by ARTS, strive to follow the ARTS naming conventions and guidelines.

  • Document and describe every object, providing both descriptions and default values so that we can maintain an up-to-date data model.

  • Consult your data architect when designing new tables and columns.

  • Whenever possible, avoid vendor-specific extensions and strive for SQL-92 compliance with your SQL.

  • While database-specific extensions are common in the code base, do not introduce currently unused extensions, because they must be ported to the DataFilters and JdbcHelpers for other databases.

  • All SQL commands should be uppercase because the DataFilters currently only handle uppercase.

  • If database-specific code is used in the source, move it into the JdbcHelpers.

  • All JDBC operations classes must be thread-safe.

Do the following to avoid errors:

  • Pay close attention when cutting and pasting SQL.

  • Always place a carriage return at the end of the file.

  • Test your SQL before committing.

The subsections that follow describe guidelines for specific database environments.

Table 3-3 provides some examples of common syntax problems which cause Oracle to produce errors.

Table 3-3 Oracle SQL Code Problems

Problem Problem Code Corrected Code

Blank line in code block causes error.

CREATE TABLE BLAH
(
 FIELD1 INTEGER,
 FIELD2 VARCHAR(20)
 
);      
CREATE TABLE BLAH
(
 FIELD1 INTEGER,
 FIELD2 VARCHAR(20)
);

When using NOT NULL with a default value, NOT NULL must follow the DEFAULT statement.

CREATE TABLE BLAH
(
 FIELD1 INTEGER NOT NULL DEFAULT 0,
 FIELD2 VARCHAR(20)
);      
CREATE TABLE BLAH
(
 FIELD1 INTEGER DEFAULT 0 NOT NULL,
 FIELD2 VARCHAR(20)
);

In a CREATE or INSERT, do not place a comma after the last item.

CREATE TABLE BLAH
(
 FIELD1 INTEGER,
 FIELD2 VARCHAR(20),
);      
CREATE TABLE BLAH
(
 FIELD1 INTEGER,
 FIELD2 VARCHAR(20)
);

Unit Testing

For details on how to implement unit testing, see separate guidelines on the topic. Some general notes apply:

  • Break large methods into smaller, testable units.

  • Although unit testing may be difficult for tour scripts, apply it for Java components within Point-of-Service code.

  • If you add a new item to the codebase, make sure your unit tests prove that the new item can be extended.

  • In unit tests, directly create the data/preconditions necessary for the test (in a setup() method) and remove them afterwards (in a teardown() method). JUnit expects to use these standard methods in running tests.

Architecture and Design Guidelines

This section provides guidelines for making design decisions which are intended to promote a robust architecture.

AntiPatterns

An AntiPattern is a common solution to a problem which results in negative consequences. The name contrasts with the concept of a pattern, a successful solution to a common problem.

Table 3-4 identifies AntiPatterns which introduce bugs and reduce the quality of code.

Table 3-4 Common AntiPatterns

Pattern Description Solution

Reinvent the Wheel

Sometimes code is developed in an unnecessarily unique way that leads to errors, prolonged debugging time and more difficult maintenance.

The analysis process for new features provides awareness of existing solutions for similar functionality so that you can determine the best solution.

There must be a compelling reason to choose a new design when a proven design exists. During development, a similar pattern should be followed in which existing, proven solutions are implemented before new solutions.

Copy-and-paste Programming, classes

When code needs to be reused, it is sometimes copied and pasted instead of using a better method. For example, when a whole class is copied to a new class when the new class could have extended the original class. Another example is when a method is being overridden and the code from the super class is copied and pasted instead of calling the method in the super class.

Use object-oriented techniques when available instead of copying code.

Copy-and-paste Programming, XML

A new element (such as a Site class or an Overlay XML tag) can be started by copying and pasting a similar existing element. Bugs are created when one or more pieces are not updated for the new element. For example, a new screen might have the screen name or prompt text for the old screen.

If you copy an existing element to create a new element, manually verify each piece of the element to ensure that it is correct for the new element.

Project Mismanagement/ Common Understanding

A lack of common understanding between managers, Business Analysts, Quality Assurance and developers can lead to missed functionality, incorrect functionality and a larger-than-necessary number of defects. An example of this is when code does not match requirements, including details like maximum length of fields and dialog message text.

Read the requirement before you code. If there is disagreement with content, raise an issue with the Product Manager. Before you consider code for the requirement finished, all issues must be resolved and the code must match the requirements.

Stovepipe

Multiple systems within an enterprise are designed independently. The lack of commonality prevents reuse and inhibits interoperability between systems. For example, a change to till reconcile in Back Office may not consider the impact on Point-of-Service. Another example is a making change to a field in the Oracle Retail database for a Back Office feature without handling Point-of-Service effects.

Coordinate technologies across applications at several levels. Define basic standards in infrastructures for the suite of products. Only mission-specific functions should be created independently of the other applications within the suite.


Designing for Extension

This section defines how to code product features so that they may be easily extended.


Note:

See the Oracle Retail POS Suite Security Guide for more information about specific security features and implementation guidelines for the POS Suite products.

  • Separate external constants such as database table and column names, JMS queue names, port numbers from the rest of the code. Store them in (in order of preference):

    • Configuration files

    • Deployment descriptors

    • Constant classes/interfaces

  • Make sure the SQL code included in a component does not touch tables not directly owned by that component.

  • Make sure there is some separation from DTO and ViewBean type classes so we have abstraction between the service and the presentation.

  • Consider designing so that any fine grained operation within the larger context of a coarse grain operation can be factored out in a separate algorithm class, so that it can be replaced without reworking the entire activity flow of the larger operation.

Common Frameworks

This section provides guidelines which are common to the Oracle Retail POS Suite applications.

Internationalization

Internationalization is the process of creating software that can be translated easily. Changes to the code are not specific to any particular market. Oracle Retail POS Suite has been internationalized to support multiple languages. This section describes configuration settings and features of the software that ensure that the base application can handle multiple languages.

Translation

Translation is the process of interpreting and adapting text from one language into another. Although the code itself is not translated, components of the application that are translated may include the following, among others:

  • Graphical user interface (GUI)

  • Error messages

The following components are not usually translated:

  • Documentation (for example, Online Help, Release Notes, Installation Guide, User Guide, Operations Guide)

  • Batch programs and messages

  • Log files

  • Configuration Tools

  • Reports

  • Demo data

  • Training Materials

The user interface for Oracle Retail POS Suite has been translated into:

  • Chinese (Simplified)

  • Chinese (Traditional)

  • Croatian

  • Dutch

  • French

  • German

  • Greek

  • Hungarian

  • Italian

  • Japanese

  • Korean

  • Polish

  • Portuguese (Brazilian)

  • Russian

  • Spanish

  • Swedish

  • Turkish

Logging

Oracle Retail POS Suite applications use Log4J for logging. When writing log commands, use the following guidelines:

  • Use calls to Log4J rather than System.out from the beginning of your development. Unlike System.out, Log4J calls are naturally written to a file, and can be suppressed when desired.

  • Log exceptions where you catch them, unless you are going to rethrow them. This is preserves the context of the exceptions and helps reduce duplicate exception reporting.

  • Use the correct logging level:

    • FATAL—exceptions that cause the application to fail

    • ERROR—nonfatal, unhandled exceptions (there should be few of these)

    • INFO—life cycle/heartbeat information

    • DEBUG—information for debugging purposes

The following sections provide additional information on guarding code, when to log, and how to write log messages.

Guarding Code

Testing shows that logging takes up very little of a system's CPU resources. However, if a single call to your formatter is abnormally expensive (stack traces, database access, network IO, large data manipulations, and so forth), you can use Boolean methods provided in the Logger class for each level to determine whether you have that level (or better) currently enabled; Jakarta calls this a code guard:

Example 3-2 Wrapping Code in a Code Guard

if (log.isDebugEnabled()) {
log.debug(MassiveSlowStringGenerator().message());
}

An interesting use of code guards, however, is to enable debug-only code, instead of using a DEBUG flag. Using Log4J to maintain this functionality lets you adjust it at runtime by manipulating Log4J configurations.

For instance, you can use code guards to simply switch graphics contexts in your custom swing component:

Example 3-3 Switching Graphics Contexts via a Logging Level Test

protected void paintComponent(Graphics g) {
   
    if (log.isDebugEnabled()) {
        g = new DebugGraphics(g, this);
    }
 
    g.drawString("foo", 0, 0);
}

When to Log

There are three main cases for logging:

  • Exceptions—Should be logged at an error or fatal level.

  • Heartbeat/Life cycle—For monitoring the application; helps to make unseen events clear. Use the info level for these events.

  • Debug—Code is usually littered with these when you are first trying to get a class to run. If you use System.out, you have to go back later and remove them to keep. With Log4J, you can simply raise the log level. Furthermore, if problems pop up in the field, you can lower the logging level and access them.

Writing Log Messages

When Log4J is being used, any log message might be seen by a user, so the messages should be written with users in mind. Cute, cryptic, or rude messages are inappropriate. The following sections provide additional guidelines for specific types of log messages.

Exception Messages

A log message should have enough information to give the user a good shot at understanding and fixing the problem. Poor logging messages say something opaque like ”load failed.”

Take this piece of code:

try {
    File file = new File(fileName);
    Document doc = builder.parse(file);
    
    NodeList nl = doc.getElementsByTagName("molecule");
    for (int i = 0; i < nl.getLength(); i++) {
        Node node = nl.item(i);
        // something here
    }    
 
} catch {
   // see below
}
 

and these two ways of logging exceptions:

} catch (Exception e){
    log.debug("Could not load XML");
}
        
 
} catch (IOException e){
    log.error("Problem reading file " + fileName, e);
} catch (DOMException e){
    log.error("Error parsing XML in file " + fileName, e);
} catch (SAXException e){
    log.error("Error parsing XML in file " + fileName, e);
}

In the first case, you'll get an error that just tells you something went wrong. In the second case, you're given slightly more context around the error, in that you know if you can't find it, load it, or parse it, and you're given that key piece of data: the file name.

The log lets you augment the message in the exception itself. Ideally, with the messages, the stack trace, and type of exception, you'll have enough to be able to reproduce the problem at debug time. Given that, the message can be reasonably verbose.

For instance, the fail() method in JUnit really just throws an exception, and whatever message you pass to it is in effect logging. It's useful to construct messages that contain a great deal of information about what you are looking for:

Example 3-4 JUnit

if (! list.contains(testObj)) {
 
    StringBuffer buf = new StringBuffer();
    buf.append("Could not find object " + testObj + " in list.\n");
    buf.append("List contains: ");
    for (int i = 0; i < list.size(); i++) {
        if (i > 0) {
             buf.append(",");
        }
        buf.append(list.get(i));
    }
    fail(buf.toString());
}

Heartbeat or Life Cycle Messages

The log message here should succinctly display what portion of the life cycle is occurring (login, request, loading, etc.) and what apparatus is doing it (is it a particular EJB are there multiple servers running, etc.)

These message should be fairly terse, since you expect them to be running all the time.

Debug Messages

Debug statements are going to be your first insight into a problem with the running code, so having enough, of the right kind, is important.

These statements are usually either of an intra-method-life cycle variety:

    log.debug("Loading file");
 
    File file = new File(fileName);
    log.debug("loaded.  Parsing...");
    Document doc = builder.parse(file);
    log.debug("Creating objects");
    for (int i ...

or of the variable-inspection variety:

    log.debug("File name is " + fileName);
 
    log.debug("root is null: " + (root == null));
    log.debug("object is at index " + list.indexOf(obj));

Exception Handling

The key guidelines for exception handling are:

  • Handle the exceptions that you can (File Not Found, etc.)

  • Fail fast if you can't handle an exception

  • Log every exception with Log4J, even when first writing the class, unless you are rethrowing the exception

  • Include enough information in the log message to give the user or developer a fighting chance at knowing what went wrong

  • Nest the original exception if you rethrow one

Types of Exceptions

The EJB specification divides exceptions into the following categories:

JVM Exceptions

You cannot recover from these; when one is thrown, it's because the JVM has entered a kernel panic state that the application cannot be expected to recover from. A common example is an Out of Memory error.

System Exceptions

Similar to JVM exceptions, these are generally, though not always, ”non-recoverable” exceptions. In the commons-logging parlance, these are ”unexpected” exceptions. The canonical example here is NullPointerException. The idea is that if a value is null, often you don't know what you should do. If you can simply report back to your calling method that you got a null value, do that. If you cannot gracefully recover, say from an IndexOutOfBoundsException, treat as a system exception and fail fast.

Application Exceptions

These are the expected exceptions, usually defined by specific application domains. It is useful to think of these in terms of recoverability. A FileNotFoundException is sometimes easy to rectify by simply asking the user for another file name. But something that's application specific, like JDOMException, may still not be recoverable. The application can recognize that the XML it is receiving is malformed, but it may still not be able to do anything about it.

Avoid java.lang.Exception

Avoid throwing the generic Exception; choose a more specific (but standard) exception.

Avoid Custom Exceptions

Custom exceptions are rarely needed. The specific type of exception thrown is rarely important; do not create a custom exception if there is a problem with the formatting of a string (ApplicationFormattingException) instead of reusing IllegalArgumentException.

The best case for writing a custom exception is if you can provide additional information to the caller which is useful for recovering from the exception or fixing the problem. For example, the JPOSExceptions can report problems with the physical device. An XML exception could have line number information embedded in it, allowing the user to easily detect where the problem is. Or, you could subclass NullPointer with a little debugging magic to tell the user what method of variable is null.

Catching Exceptions

The following sections provide guidelines on catching exceptions.

Keep the Try Block Short

The following example, from a networking testing application, shows a loop that was expected to require approximately 30 seconds to execute (since it calls sleep(3000) ten times):

Example 3-5 Network Test

    for (int i = 0; i < 10; i++) {
        try {
            System.out.println("Thread " + Thread.currentThread().getName() + " requesting number " + i);
            URLConnection con = myUrl.openConnection();
            con.getContent();
            Thread.sleep(3000);
        } catch (Exception e) {
            log.error("Error getting connection or content", e);
        }
    }

The initial expectation was for this loop to take approximately 30 seconds, since the sleep(3000) would be called ten times. Suppose, however, that con.getContent() throws an IOException. The loop then skips the sleep() call entirely, finishing in 6 seconds. A better way to write this is to move the sleep() call outside of the try block, ensuring that it is executed:

Example 3-6 Network Test with Shortened Try Block

    for (int i = 0; i < 10; i++) {
 
        try {
            System.out.println("Thread " + Thread.currentThread().getName() + " requesting number " + i);
            URLConnection con = myUrl.openConnection();
            con.getContent();
        } catch (Exception e) {
            log.error("Error getting connection or content", e);
        }
        Thread.sleep(3000);
    }
Avoid Throwing New Exceptions

When you catch an exception, then throw a new one in its place, you replace the context of where it was thrown with the context of where it was caught.

A slightly better way is to throw a wrapped exception:

Example 3-7 Wrapped Exception

1:   try {
2:       Class k1 = Class.forName(firstClass);
3:       Class k2 = Class.forName(secondClass);
4:       Object o1 = k1.newInstance();
5:       Object o2 = k2.newInstance();
6:
7:   } catch (Exception e) {
8:      throw new MyApplicationException(e);
9:   }

However, the onus is still on the user to call getCause() to see what the real cause was. This makes most sense in an RMI type environment, where you need to tunnel an exception back to the calling methods.

The better way than throwing a wrapped exception is to simply declare that your method throws the exception, and let the caller figure it out:

Example 3-8 Declaring an Exception

public void buildClasses(String firstName, String secondName)
       throws InstantiationException, ... {
 
       Class k1 = Class.forName(firstClass);
       Class k2 = Class.forName(secondClass);
       Object o1 = k1.newInstance();
       Object o2 = k2.newInstance();
   }

However, there may be times when you want to deal with some cleanup code and then rethrow an exception:

Example 3-9 Clean Up First, then Rethrow Exception

try {
        someOperation();
    } catch (Exception e) {
        someCleanUp();
        throw e;
    }
Catching Specific Exceptions

There are various exceptions for a reason: so you can precisely identify what happened by the type of exception thrown. If you just catch Exception (rather than, say, ClassCastException), you hide information from the user. On the other hand, methods should not generally try to catch every type of exception. The rule of thumb is the related to the fail-fast/recover rule: catch as many different exceptions as you are going to handle.

Favor a Switch over Code Duplication

The syntax of try and catch makes code reuse difficult, especially if you try to catch at a granular level. If you want to execute some code specific to a certain exception, and some code in common, you're left with either duplicating the code in two catch blocks, or using a switch-like procedure. The switch-like procedure, shown below, is preferred because it avoids code duplication:

Example 3-10 Using a Switch to Execute Code Specific to an Exception

    try{
        // some code here that throws Exceptions...
    } catch (Exception e) {
        if (e instanceof LegalException) {
            callPolice((LegalException) e);
        } else if (e instanceof ReactorException) {
            shutdownReactor();
        }
        logException(e);
        mailException(e);
        haltPlant(e);
    }

This example is preferred, in these relatively rare cases, to using multiple catch blocks:

Example 3-11 Using Multiple Catch Blocks Causes Duplicate Code

try{
        // some code here that throws Exceptions...
    } catch (LegalException e) {
        callPolice(e);
        logException(e);
        mailException(e);
        haltPlant(e);
    } catch (ReactorException e) {
        shutdownReactor();
        logException(e);
        mailException(e);
        haltPlant(e);
    }

Exceptions tend to be the backwater of the code; requiring a maintenance developer, even yourself, to remember to update the duplicate sections of separate catch blocks is a recipe for future errors.