« Home

Coding Standards, Practices, and Conventions

This document outlines the conventional practices used in the design and implementation of the Android library.

Hungarian Notation

Hungarian notation refers to an identifier-naming convention proposed by Charles Simonyi at XEROX PARC. It proposes that each identifier's name begin with a semantically-meaningful prefix indicating the pragmatic purpose of the variable in a given context. This is referred to as "app Hungarian", as opposed to "systems Hungarian" which tries to strictly encode a variable's programmatic type into the prefix — a practice which would not be as useful in Java, which already enforces types for its variables.

For notes describing the specific notations used in the zerobandwidth Android library, see Notational Conventions.

The One True Brace Style (and other whitespace conventions)

The One True Brace Style is "always on the next line". Anything else that you have seen or heard to the contrary was trying nefariously to mislead you into a life of depravity and sin.

Brackets around complicated expressions should have comfortable spacing within, while brackets around single identifiers may remain dense.

Semicolons should be padded by a space from the end of the line, except where it would go over the canonical length of a line, which is 80 characters.

JavaDoc Conventions

All classes, members, and methods shall be documented with JavaDoc comments. A new version of the library's complete JavaDoc set is generated with each release.

Any class, member, or method which is added to the library shall be decorated with a @since annotation, showing that it is a part of the zerobandwidth-net/android library, the version in which the item was added to the library, and the GitHub issue number to which the related commit was ascribed. This distinctive format, in addition to carrying valuable versioning information, is chosen to be obviously distinctive from the code provided by other zerobandwidth.net libraries and apps, as well as the code produced by anyone who would consume this library.

Similarly, any item which is deprecated from the library shall be decorated with a @deprecated annotation. This annotation will show the version of the library in which the item was first considered deprecated, and, if applicable, will also suggest a usable alternative/replacement, and describe the plans for removing the item from the library altogether.

ADB Logging Patterns

Each class definition which writes anything to the ADB logs will include a static LOG_TAG constant which is used as the tag for Android system logs generated by that class. The value of this constant is, conventionally, the simple name of the class itself.

When writing to the ADB logs, if there is any dynamic data to be included in the entry's text, then the text shall be composed with a StringBuilder. We use this class for the following reasons:

The static portions of the log line should enclose the dynamic content with square-brackets, to make the value "pop" when reading the text of the entry in the ADB logs.

Fluidity

Any non-static method that does not otherwise return a value should return the instance of the object that executed it, rather than being declared void. This allows consumers of the library class to invoke the class's methods in a long, continuous chain, rather than repeating its identifier multiple times.

public class Water
{
    protected int m_nDrops = 0 ;
    protected Color m_color = Color.CLEAR ;

    public Water flowQuickly()
    { m_nDrops += 10 ; return this ; }

    public Water flowSlowly()
    { m_nDrops += 1 ; return this ; }

    public Water turnColor( Color color )
    { m_color = color ; return this ; }

    public String toString()
    {
        return (new StringBuilder())
            .append( "You have " ).append( m_nDrops )
            .append( " drops of " ).append( m_color.toString() )
            .append( " water." )
            .toString()
            ;
    }
}

public class WaterTest
{
    public void testWater()
    {
        String s = (new Water())
            .flowQuickly()
            .flowSlowly()
            .turnColor( Color.GREEN )
            .flowQuickly()
            .toString()
            ;
        assertEquals( "You have 21 drops of green water.", s ) ;
    }
}
   

Reveal Nothing, Provide Everything

Each class should keep its member fields protected unless it is absolutely necessary to hide them at private scope for some specific reason. The protected scope conceals the inner workings of the class from its consumers, but allows classes in the same package to use the items directly. Most significantly, this allows classes that execute automated unit tests to access the class members directly when evaluating test assertions, which makes the tests much more efficient, and easier to write. It also follows the general convention of "fluidity" described in the previous section.

While member fields should be concealed behind protected scope, the class should also provide adequate accessor and mutator methods for those fields, where appropriate. Mutators for each field might or might not be required; instead, it might be more appropriate for a mutator to ingest a single object or set of inputs, from which the class members are extracted and processed.

Conventionally, the accessors and mutators provided by this library are not quite in the "bean" paradigm (whose mutators are declared void) but not quite the "builder" paradigm (in which mutators are fluid but in a separate class). Instead, the mutator methods themselves are fluid but still provided by the class itself; that is, each class is its own "builder". This allows consumers of the class to interact with it more easily.

public class Flargle
{
    protected String m_sFoo ;
    protected String m_sBar ;

    public String getFoo()
    { return m_sFoo ; }

    public Flargle setFoo( String s )
    { m_sFoo = s ; return this ; }

    public String getBar()
    { return m_sBar ; }

    public Flargle setBar( String s )
    { m_sBar = s ; return this ; }

    public String toString()
    {
        return (new StringBuilder())
            .append( m_sFoo ).append( " " )
            .append( m_sBar ).append( "!" )
            .toString()
            ;
    }
}

public class FlargleBarg
{
    public void testFlargle()
    {
        Flargle flargle = (new Flargle()).setFoo( "Hello" ).setBar( "world" ) ;
        assertEquals( "Hello world!", flargle.toString() ) ;
    }
} 
   

Copy and Paste Only Once

As a general rule, if it's worth copying and pasting once, then it's worth pasting into a method which can be reused. This increased modularity ensures that, if that bit of repetitive code ever has to be tested or changed, then those tests and changes will be needed only once, and all consumers of that code can be left alone as much as possible; that is, the singular change may be confined to a nicely-limited context.

Besides, you never know when that new, factored-out method might become useful in some other way. Many of the features of this library were, themselves, bits of reusable code required across several of our own projects.

Extended Example Code

/**
 * Illustrates library coding standards.
 * @since zerobandwidth-net/android 0.1.7 (#36)
 */
public class CodingExample
extends CodingStandard
implements Practice
{
    private static final boolean ALWAYS_NEXT_LINE = true ;

    /**
     * Lines longer than 80 characters should be broken reasonably, like at the
     * equals (=) sign for an assignment operation, or before one of the dots in
     * a chain of fluid method invocations.
     */
    private static final String LOG_TAG =
        CodingExample.class.getSimpleName() ;

    /** Members get the 'm_' prefix. Signed integers get 'z'... */
    protected int m_zFoo ;

    /** ...while unsigned integers get 'n'. */
    protected int m_nAllGoodThings ;

    /**
     * Templatized items stay dense (no whitespace within brokets).
     * A collection of nondescript objects gets 'a' (array-like) and 'o'
     *  (objects).
     */
    protected List m_aoThings ;

    /** Trivial one-liner methods, like accessors, can indeed be one-liners. */
    public int getFoo()
    { return m_zFoo ; }

    /**
     * Trivial mutators might be one line, too.
     * Mutators should also be "fluid" (return the mutated object for chaining).
     */
    public CodingExample setFoo( int z )
    { m_zFoo = z ; return this ; }

    /**
     * This function does stuff with things.
     * @param oThings an object containing things with which to do stuff
     * @return (fluid)
     */
    public CodingExample doStuff( Things oThings )
    {
        if( oThings.areGoodThings() )
        { // Briefly explain a block's contents on its opening brace line.
            ++m_nAllGoodThings ;
            oThings.setCounted(true) ;
        }
        else // Briefly explain a one-liner's contents on its if or else line.
            oThings.setCounted(true) ;

        switch( oThings.getSomeNumberThing() )
        { // The logic of this switch is silly, but illustrates spacing.
            case 0:
                m_zFoo = 0 ;
                break ;
            case 1:
                m_zFoo = 1 ;
                break ;
            default:
                m_zFoo += oThings.getSomeNumberThing() ;
        }

        // For type casts, keep terms dense but be paranoid with parens.
        CastedThing oCasted = ((CastedThing)(oThings.get(0))) ;

        // Things that look like array indices should be dense if simple.
        Thing[] aoThings = oThings.toArray() ;
        Thing oFourth = aoThings[4] ;
        Thing oFifth = oThings.get(5) ;

        // Use judgment when the index is NOT something simple.
        Thing oLast = aoThings[ aoThings.length - 1 ] ;

        // Complicated log lines should be constructed with StringBuilders,
        // since they are faster than string concatenation, but don't need to
        // survive long enough to be thread-safe like StringBuffers.
        Log.d( LOG_TAG, (new StringBuilder())
                .append( "We have [" )
                .append( m_aoThings.size() )
                .append(( m_aoThings.size() == 1 ? "] thing." : "] things." ))
                .toString()
            );

        return this ;
    }
}