By contributing to this repository, you are expected to follow this style guide. Please also ensure that you remain familiar with this document as it may change from time to time.
Multiple versions of this style guide may exist throughout this repository. As with other documents on this repository, the version on the master branch should be followed, as this version should be the most up-to-date.
- Introduction
- IDE formatting
- Checking for violations
- Source files
- Formatting
- Naming
- Javadoc
- Programming practices
- JUnit tests
- Recommended reading
- Updates to this document
- Acknowledgements
Good judgement should be followed. There may be times where it is more readable to not follow a particular guideline. Readable code is preferred over code that strictly follows this guide.
A short summary explaining why the guideline is in place is included to help explain the rationale behind having it.
IDE-specific code style files have been exported and can be imported into your IDE. The files are located in the ide directory.
For IntelliJ, you can use import the code style file. Go to File > Settings > Editor > Code Style > Java in Linux or Windows. Click on the settings cog and choose Import Scheme > IntelliJ IDEA code style XML.
If you use Eclipse, you can import the formatter file by going to Window > Preferences > Java > Code Style > Formatter and selecting the import button to import the Eclipse code style file.
We're currently using Checkstyle to check for style guide violations. To o check for any violations, you can run the following command in the root directory of the project:
$ ./mvnw validate
By using the maven wrapper, you don't need to have Maven installed. Alternatively, you could run mvn validate
if you do have Maven installed.
All code and comments should be written in English (United States).
A source file should contain the following in order:
- License notice and copyright information
- Package statement
- Import statements
- Exactly one top-level class
There should be exactly one blank line to separate every section.
Wildcard imports, regardless of whether they are static, should not be used:
// good
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
// bad
import com.vaadin.flow.component.*;
The package statement and import statements should not be line-wrapped.
All fields should appear at the top of the class before any constructors. Fields should not be interspersed between methods.
Overloaded methods, including constructors, should appear together with nothing in between.
Every source file should have one newline at the end of the file.
We use the One true brace style (1TBS).
// bad: braces should be used even where it is optional to do so in if, else if, while and do statements
if (condition)
doSomething();
// good
if (condition) {
doSomething();
} else {
doSomethingElse();
}
A line should generally not exceed 100 characters.
Exceptions to this rule:
package
andimport
statements- Long URL in Javadoc
A line break should follow every statement. This includes variable declarations.
Empty blocks can be concise providing they do not form a part of a multi-block statement.
// fine
void foo() {}
// also fine
void bar() {
}
// not fine
if (condition) {
foo();
} else { }
This applies to if/else
and try/catch/finally
blocks.
Where there should be one blank line
-
Between the end of one method (the closing brace) and the start of another
-
Between groups of logical statements. This could be a group of related imports, fields, or statements within a method.
Generally speaking, there should not be more than one blank line.
Where there should not be one blank line
- Between the class name and the first field declaration (or method declaration or definition)
// acceptable
private int numberOfPages;
private String authorFirstName;
// unacceptable
private int numberOfPages;
private Strring authorFirstName;
There should be no horizontal alignment. While it looks nice and improves readability, it can be harder to maintain.
Optional grouping parentheses are recommended as they can improve readability. It can help when not everyone is clear with certain precedence rules.
If an enum class has no methods, it can be written as an array initialiser:
private enum EventType { SAVED, DELETED }
If a case continues onto the next statement group with a break
, this should be commented. Something like // fall through
is sufficient. For example:
switch (param) {
case 1:
case 2:
foo();
// fall through
case 3:
bar();
break;
default:
baz();
}
The final statement in the switch block, the default
case above, does not require a fall-through comment.
Either cover all of the cases or use the default
case. Covering all cases is the preferred approach.
Annotations should appear after any javadoc for the class, field or method.
Each annotation should appear on its own line. For example:
@Override
@Nullable
public Integer getPagesReadIfPresent() {
...
}
However, for fields, multiple annotations can be on the same line:
@NotNull @VisibleForTesting String authorFirstName;
This section is to do with implementation comments, not Javadoc.
Comments should not be decorated with asterisks or any other characters.
Only add comments where they add value. Comments should be used as a last resort. We prefer to make code readable by making the code trivial such that having a comment is redundant. Alternatively, we prefer to extract code into a method with a descriptive method name such that the method name makes the comment redundant.
Where applicable, class and member modifiers should appear in the following order, as recommended by the Java Language Specification:
public protected private abstract default static final transient volatile synchronized native strictfp
For numeric literals that are ten thousand or higher, underscores are recommended to separate digits by thousands:
// bad
int booksSold = 10000;
// good
int booksSold = 10_000;
long
variables should be suffixed with an uppercase L
. The lowercase l
should not be used as it may cause confusion with the digit 1
. For example, 1_000_000_000L
instead of 1_000_000_000l
.
A variable declaration should appear on its own line. There should not be multiple declarations on one line:
// good
private String authorFirstName;
private String authorLastName;
// bad
private String authorFirstName, authorLastName;
Local variables should be declared as close as reasonably possible to where it is used. This can help limit the scope and improve readability. Local variables should not be declared at the top of a method for the sake of it.
The square brackets should be next to the type name, not the variable name. This is because they a part of the type.
// good
String[] args
// bad
String args[]
Four spaces should be used for indentation. This is clearer than two spaces (more than four is extraneous).
This also applies to CSS.
An exception to the above four spaces indentation rule is aligning method calls (see below). This can improve readability.
// good
bookGrid.addColumn(AUTHOR_KEY)
.setSortable(true);
// bad
bookGrid.addColumn(AUTHOR_KEY)
.setSortable(true);
Special prefixes or suffixes, such as to represent member variables, are not used. For example, the following are not permitted: mTitle
and _title
.
Package names are written in lowercase. Consecutive words are concatenated together without any underscores. For example, com.example.readinggoal
, not com.example.readingGoal
or com.example.reading_goal
.
To aid readability, try to keep package names short and one-word names wherever possible.
Class names are written in PascalCase (also referred to as UpperCamelCase).
Class names should be nouns or noun phrases.
Interface names can be nouns or noun phrases. However, in some cases, adjectives or adjective phrases are better (e.g. Iterable
).
Test classes should be the name of the class that it is testing followed by the suffix Test
. For example. the test class for the GenreStatistics
class should be called GenreStatisticsTest
.
Method names are written in lowerCamelCase.
Method names should usually be verb or verb phrases. For example, processTransaction
or findId
.
Underscores are allowed in JUnit test methods to separate logical components. One popular pattern is <methodUnderTest>_<state>
; for example, pop_emptyStack
. The most important thing is that the test method name clearly summarises what is being tested.
Constants are written in CONSTANT_CASE. It is written in uppercase letters with each word separated by an underscore.
Non-constant field names, regardless of whether they are static, are written inlowerCamelCase
.
The names tend to be noun or noun phrases.
Parameter names are written in lowerCamelCase
.
Single letter parameter names should be avoided in methods (common exception: for loops).
Local variables names are written in lowerCamelCase
.
Even if they are immutable (e.g. marked as final
), they are not considered constants, so they are not styled as constants.
Type variables should be either:
- One capital letter, which can be optionally succeeded by a letter (e.g.
T
,E
orT2
) - A class name followed by the capital letter
T
(e.g.AuthorT
)
Author tags can quickly become outdated as methods are updated or completely written by new authors. Git is far better at tracking changes.
The @Override annotation should be used for overridden methods. This is so that the compiler can run a check at compile-time to see whether the method annotated with @Override actually overrides a method.
While it is usually better to limit members and methods, if it's required for testing, it will need to be made package-private. In such cases, they should be tagged as @VisibleForTesting to make it clear.
Rarely should there be no response to a caught exception (e.g. you may want to log it).
If it is right not to do anything, then this has to be justified in a comment.
An exception to ignore exceptions is if the exception is expected in a test. For example, you may want to test whether the code under test does indeed throw an exception:
try {
foo(-1);
} catch (IndexOutOfBoundsException expected) {
}
In such cases, the exception parameter should be called or include the word expected
, as above.
StringBuilder should be used instead of a StringBuffer for single-threaded code.
If you override hashCode()
, it is good practice to also override equals()
.
The overrides of equals()
and hashCode()
should be equivalent; if x.equals(y)
is true, then x.hashCode()
should have
the same value as y.hashCode()
.
It is sometimes worthwhile overriding toString()
for logging purposes.
// OK, but could be better. The hardcoded class name has to be changed if the class name changes
@Override
public String toString() {
return "Book[title= " + title
+ ", published = " + published
+ "]" ;
}
// Better
@Override
public String toString() {
return getClass.getName()
+ "[title= " + title
+ ", published = " + published
+ "]" ;
}
Wherever possible, try to keep methods short (under 15 lines). This makes it easier to test, comprehend and reuse.
TODO comments are acceptable and encouraged if they are useful. However, we ask that if you create a TODO, please also create a new corresponding issue.
Optional
provides useful semantics that something may be null
. If you need to use null
s, consider using Optional
s instead.
For enums, favor EnumMap
over HashMap
for performance reasons.
For assertions and assumptions, we're using AssertJ. We find this to be more readable (it flows nicer as it reads like a sentence) and it's easier to have a consistent team style.
The pattern we follow for JUnit tests are given/when/then. For example, given some input some setup, when an action occurs, then assert as desired. Concrete example:
@Test
void errorShownWhenEmailInUse() {
// given
userRepository.save(VALID_TEST_USER);
EmailField emailField = _get(EmailField.class, spec -> spec.withId("email"));
// when
_setValue(emailField, VALID_TEST_USER.getEmail());
// then
assertThat(emailField.getErrorMessage()).isNotBlank();
}
In general, we add comments to separate out the given, when and then sections. We find that this greatly improves readability.
In every test method, try to minimise the number of assertions. Ideally, there should only be one.
If a test method needs multiple assertions, assertSoftly()
should be used. Otherwise, lazy evaluation is used. For example, if you had two assertions, and both assertions fail, you will not know about the second assertion failing until you have fixed the first assertion.
Instead of commenting out tests, we use the @Disable
annotation. Please note that if a test fails, you are generally expected to fix it. This should be used as a last resort if you are unable to fix the failing test.
All test classes should be annotated with a @DisplayName
. For example, the test class TagServiceTest
should
be annotated with the display name TagService should
. We find this greatly improves readability as the method
names can be read like a sentence (e.g. TagService should
findAllTags()
or deleteExistingTag()
).
A @DisplayName
annotation should be used at a method level where it adds value. For example, you could replace a Javadoc comment with a display name. This is also useful if the method name is concise but not comprehensive. Adding a display name should be used where it serves as useful documentation.
While it may seem better to use pseudorandom bounded values so that you can test more cases, it rarely improves coverage. It's better to use fixed input data with well-defined edge cases.
- Clean code, Robert C. Martin
If you change any of the recommended styles in this guide, please note that the following may also need to be changed:
In addition, please update the table of contents.
This style guide has been adapted from Google's Java style guide and Twitter's common style guide.