Nick Bennett and Todd Nordquist" />

DDC Style Guide: Java

Coding conventions for the Deep Dive Coding Java + Android Bootcamp

Overview

This portion of the style guide is a Deep Dive Coding-specific extension to the Google Java Style Guide (GJSG).

The GJSG is not reproduced here; instead, only amendments—additional constraints and relaxations or other changes to stated constraints—are listed below. Thus, a Java source file conforms to this style guide if (and only if) it conforms to the GJSG and the amendments stated below—except where the amendments contradict the constraints declared in the GJSG, in which cases the amendments below dictate the rules to be followed.

Numbered links below reference (and link to) the corresponding sections of the GJSG.

Source file basics

GJSG 2 Source file basics is normative, without amendment.

Source file structure

GJSG 3 Source file structure is normative, with the amendments below.

package statement

Though not explicitly stated in the source file structure shown in the GJSG, it is implied that there will always be a package statement. In fact, this is a strict requirement of this bootcamp: All classes (including enums) and interfaces must use the package statement (with a corresponding directory structure) to place the class or interface in a package other than the default (unnamed) package.

Wildcard imports

GJSG 3.3.1 No wildcard imports:

Wildcard imports, static or otherwise, are not used.

We permit just one exception to this rule: In testing frameworks such as JUnit, it is common to have a large number of static methods within a class, providing a variety of specialized assertions. A wildcard import static statement (e.g. import static org.junit.jupiter.api.Assertions.*;) is permitted in test classes that make use of these frameworks. However, when practical, a command like IntelliJ IDEA’s Code/Optimize Imports should be used to resolve the wildcard imports into non-wildcard forms.

Ordering of class contents

GJSG 3.4.2 Ordering of class contents:

The order you choose for the members and initializers of your class can have a great effect on learnability. However, there’s no single correct recipe for how to do it; different classes may order their contents in different ways.

What is important is that each class uses some logical order, which its maintainer could explain if asked. For example, new methods are not just habitually added to the end of the class, as that would yield “chronological by date added” ordering, which is not a logical ordering.

We agree with the above. However, we require more predictable structure in the ordering of class members:

  1. The order of class members must follow this general sequence:

    • static final fields
    • static (but non-final) fields
    • final (but non-static) fields
    • other (non-static, non-final) fields
    • static initializer blocks
    • non-static initializer blocks
    • constructors
    • methods
    • nested classes and interfaces

    This still does not fully dictate the order of members within each of the above groups. There’s no single correct way of ordering these contents; this may even vary from class to class. However, there are strict rules that must be followed; one is stated in GJSG 3.4.2.1 Overloads: never split; two more follow below.

  2. All getters and setters must be grouped together.

    Accessors and mutators that follow the JavaBeans naming conventions (aka getters and setters) must be grouped together. That is, if a class has multiple inferrable properties that are accessible for reading via getProperty (for a non-boolean-valued property) or isProperty (for a boolean-valued property) methods, and/or accessible for writing via setProperty methods, the accessor and mutator methods for all such properties must be grouped together.

  3. Never split accessor/mutator pairs.

    When a class has a pair of methods that follow the JavaBeans naming convention for accessors & mutators for some inferable property name, these methods must appear sequentially, with no other code between them (not even private members).

Formatting

GJSG 4 Formatting is normative, with the amendments below.

Vertical whitespace

GJSG 4.6.1 Vertical whitespace:

Multiple consecutive blank lines are permitted, but discouraged, and never required.

We constrain the use of vertical whitespace further with these rules:

  1. Consecutive blank lines are discouraged but permitted between members of a class, but the number of blank lines used must be consistent or predictable.

  2. Consecutive blank lines are not permitted inside the body of an initializer, constructor, or method.

Grouping parentheses

GJSG 4.7 Grouping parentheses: recommended:

Optional grouping parentheses are omitted only when author and reviewer agree that there is no reasonable chance the code will be misinterpreted without them, nor would they have made the code easier to read. It is not reasonable to assume that every reader has the entire Java operator precedence table memorized.

To the above, we add 2 strict rules:

  1. Though the compiler treats parentheses around a single lambda parameter as optional, they are required in this bootcamp.

  2. If the conditional (Boolean) operand of a ternary expression contains 1 or more binary operators, other than the . member access operator, the entire conditional operand must be enclosed in parentheses. For example, take the these 2 statements that include ternary expressions:

     int a = (c >= 0) ? c : -c;
    
     String s = t.isEmpty() ? null : t;
    

    In the first, the conditional operand includes the binary operator >=; thus, we enclose the entire conditional operand (c >= 0) in parentheses. In the second, while there are parentheses for the isEmpty method invocation, the only operator in the conditional operand is the member access operator .; thus, parentheses around the conditional operand are optional, but not required.

switch statements

GJSG 4.8.4.3 The default case is present:

Each switch statement includes a default statement group, even if it contains no code.

Exception: A switch statement for an enum type may omit the default statement group, if it includes explicit cases covering all possible values of that type. This enables IDEs or other static analysis tools to issue a warning if any cases were missed.

The above is further constrained: when a default is present (which, as noted, must be the case, except when the case values are all of the enumerated values of an enum), it must be the last statement group in the switch. (In fact, there are use cases for a default statement group that is not the last statement group in a switch; however, in our opinion, the potential for confusion in this practice far outweighs its utility.)

Naming

GJSG 5 Naming is normative, with the amendments below.

GJSC 5.2.3 Method names:

Method names are written in lowerCamelCase.

Method names are typically verbs or verb phrases. For example, sendMessage or stop.

Underscores may appear in JUnit test method names to separate logical components of the name, with each component written in lowerCamelCase. One typical pattern is <methodUnderTest>_<state>, for example pop_emptyStack. There is no One Correct Way to name test methods.

Indeed, there is no One Correct Way to name test methods—or any method, for that matter. However, in this bootcamp, we add the following requirements:

GJSC 5.2.5 Non-constant field names:

Non-constant field names (static or otherwise) are written in lowerCamelCase.

These names are typically nouns or noun phrases. For example, computedValues or index.

GJSC 5.2.6 Parameter names:

Parameter names are written in lowerCamelCase.

One-character parameter names in public methods should be avoided.

GJSC 5.2.7 Local variable names:

Local variable names are written in lowerCamelCase.

Even when final and immutable, local variables are not considered to be constants, and should not be styled as constants.

For this bootcamp, names are further constrained:

  1. Parameter names must consist of 2 or more characters each, except for catch parameters and lambda parameters, which may have single-character names. The following rules in this list do not apply to single-character names for catch or lambda parameter names.

  2. When constructing a lowerCamelCase name, avoid names beginning with a single lowercase character, followed immediately by an uppercase character. Such names are easily mistyped, and can cause problems in automatic generation of setters and getters. For example, while the name of the OAuth authentication standard might intuitively lead to a field name such as oAuthKey, oauthKey is preferred.

  3. Field, parameter, and local variable names of all scalar types (primitives, wrappers, String) except boolean and Boolean, must be singular nouns or noun phrases.

  4. boolean or Boolean fields, parameters, and local variables must be named using adjectives or adjective phrases.

  5. Fields, parameters, and local variables that are references to arrays and collections must be named with plural or collective nouns or noun phrases. For example, in a card game application, we may have a field (or several) of type List<Card>. Both cards and deck would be acceptable names for such a field: the former is a plural noun, and the latter is a collective noun.

  6. Do not name boolean or Boolean fields with prefixes such as is, are, or has. (The is prefix is part of the JavaBeans specification for accessor methods, not fields. Also, all of these prefixes make the field name a verb phrase, rather than an adjectival phrase.)

  7. Do not use Hungarian notation for field, parameter, or local variable names. That is, do not prefix the name with type or role identifiers.

  8. Avoid unnecessary suffixes on field, parameter, and local variable names. For example, prefer

    private List<String> words;
    

    to

    private List<String> wordList;
    

    Similarly, while the m and s prefixes on non-public, non-static and static fields (respectively) are often seen in Java source code (including the Android library source code), these are also unnecessary, and should be avoided.

  9. If a non-static field is intended to act as a primary key value of a persistent instance (e.g a field annotated with @PrimaryKey in an entity class), prefer the simpler id to {entity name}Id.

Note that there are a few long-standing exceptions to the naming & casing rules listed in GJSG and above. For example, UUID is a class in the Java standard ibrary, written as indicated in UPPERCASE; however, uuid (rather than uUID) is the correct way to use that initialism as a field or variable name.

Programming practices

GJSG 6 Programming Practices is normative, with the amendments and additions below.

GJSG 6.2 Caught exceptions: not ignored:

Except as noted below, it is very rarely correct to do nothing in response to a caught exception. (Typical responses are to log it, or if it is considered “impossible”, rethrow it as an AssertionError.)

When it truly is appropriate to take no action whatsoever in a catch block, the reason this is justified is explained in a comment.

Exception: In tests, a caught exception may be ignored without comment if its name is or begins with expected. The following is a very common idiom for ensuring that the code under test does throw an exception of the expected type, so a comment is unnecessary here.

try {
  emptyStack.pop();
  fail();
} catch (NoSuchElementException expected) {
}

While expected (or ignored) should be used for exceptions that are expected and will not require any special handling, the justification must be explained in a comment within the catch block. For example, the code immediately above is not acceptable, but the following is:

try {
  emptyStack.pop();
  fail();
} catch (NoSuchElementException expected) {
  // Exception is expected if code functions as intended.
}

Packages

As stated in “Source file structure: package statement”, the default package must not be used for Java source and bytecode files created in this bootcamp.

Types

  1. If a non-static field is intended to act as a non-compound primary key value of a persistent instance (e.g a field annotated with @PrimaryKey in an entity class), the type of the field must be one of:

    • UUID
    • long or Long
    • int or Integer

    Though the type selected should be independent of the RDBMS being used, this is not always possible. For example, when using SQLite, the most performant—and appropriate—choice for the type of a field intended for use as a non-compound primary key value is long or Long.

Access level modifiers

  1. Class members are declared at the lowest practical access level.

  2. The only fields allowed to have the public access level are those marked final. (Note that interface fields are implicitly public static final, as are the enumerated values of an enum class.)

  3. Non-static fields, even if final, must not be public, except in inner private classes, anonymous classes, or local classes.

  4. Prefer protected accessors and mutators to protected fields.

if-else if ladders

  1. An if-else if statement ladder should include a final else, especially when one or more of the following holds:

    • else if occurs multiple times in the ladder.

    • The purpose of the ladder is to assign one of a number of alternative values to a field.

    • The purpose of the ladder is to assign one of a number of alternative values to a local variable, and that variable is not declared immediately before the ladder.

Javadoc

GJSG 7 Javadoc is normative, with the amendments below.

Where Javadoc is used

GJSC 7.3.1 Exception: self-explanatory methods:

Javadoc is optional for “simple, obvious” methods like getFoo, in cases where there really and truly is nothing else worthwhile to say but “Returns the foo”.

Important: it is not appropriate to cite this exception to justify omitting relevant information that a typical reader might need to know. For example, for a method named getCanonicalName, don’t omit its documentation (with the rationale that it would say only /** Returns the canonical name. */) if a typical reader may have no idea what the term “canonical name” means!

While we do not contradict this entirely, we qualify it:

  1. Omitting the Javadoc comment entirely, even for a “self-explanatory” method, should be treated as a truly exceptional condition. In most cases, there should be some comment, even if just a summary fragment.

    Remember: Your documentation is likely to be read in an HTML page, without the reader having access to the implementation details of your class. Thus, a comment like

     /** Returns age. */
    

    may well be meaningless or of little value, especially if age is a private field.

    However, the comment

     /** Returns the age (in generations) of this cell. */
    

    is potentially much more meaningful, doesn’t depend on the reader knowing anything about the existence of an age field, and shouldn’t be omitted.

  2. On the other hand, a Javadoc comment may be omitted for a @param or @return tag, when such a comment would simply repeat what is already stated in the method-level Javadoc comment. This is most often the case for an accessor or mutator (getter or setter) method, where a comment fragment for the @return or @param tag (respectively) would tend to repeat the summary fragment of the method itself.

    Note that the reverse of the above exception is not permitted: The method-level Javadoc comment must not be omitted if a comment is provided for a @param, @return, or @throws tag of the same method. In other words, if a method includes any Javadoc comments at all, it must include at least the summary fragment comment for the method itself.