# Developer Guide
# Build requirements
- OpenJDK 1.8
- Consider using SDKMAN! for convenient installation of JDK.
# How to build
We use Gradle to build Jiny. The following command will compile Jiny, run tests and generate JARs:
$ ./gradlew --parallel build
# Setting up your IDE
You can import Jiny into your IDE (IntelliJ IDEA or Eclipse) as a Gradle project.
- IntelliJ IDEA - See Importing Project from Gradle Model
- Eclipse - Use Buildship Gradle Integration
IntelliJ IDEA is our primary IDE for developing Jiny.
# Always make the build pass
Make sure your change does not break the build.
- Run
./gradlew --parallel build
locally. - It is likely that you'll encounter some Checkstyle or Javadoc errors. Please fix them because otherwise the build will be broken.
# Avoid redundancy
Avoid using redundant keywords. To list a few:
final
method modifier in afinal
classstatic
orpublic
modifier in aninterface
public
method modifier in a package-local or private classprivate
constructor modifier in anenum
- field access prefixed with
this.
where unnecessary
# Use public
only when necessary
The classes, methods and fields that are not meant to be used by a user should not be
public. Use the most restrictive modifier wherever possible, such as private
,
package-local and protected
, so that static analysis tools can find dead code easily.
# Organize
Organize class members carefully for readability, using top-down approach. Although there's no absolute rule of thumb, it's usually like:
static
fieldsstatic
methods- member fields
- constructors
- member methods
- utility methods (both
static
and member) - inner classes
# Check null
Do explicit null
-check on the parameters of user-facing public methods.
Always use @lombok.NonNull
to do a null
-check.
import lombok.NonNull
@Override
public int process(@NonNull String text) {
// ...
}
# Use @Nullable
Use @Nullable
annotation for nullable parameters and return types.
# Avoid redundant null checks
Avoid unnecessary null
-checks, including the hidden checks in Objects.hashCode()
and Objects.equals()
.
public final class MyClass {
private final String name;
public MyClass(String name) {
// We are sure 'name' is always non-null.
this.name = requireNonNull(name, "name");
}
@Override
public int hashCode() {
// OK
return name.hashCode();
// Not OK
return Objects.hash(name);
}
@Override
public boolean equals(@Nullable Object obj) {
... usual type check ...
// OK
return name.equals(((MyClass) obj).name);
// Not OK
return Objects.equals(name, ((MyClass) obj).name);
}
}
# Use meaningful exception messages
When raising an exception, specify meaningful message which gives an explicit clue about what went wrong.
switch (fileType) {
case TXT: ... break;
case XML: ... break;
default:
// Note that the exception message contains the offending value
// as well as the expected values.
throw new IllegalStateException(
"unsupported file type: " + fileType +
" (expected: " + FileType.TXT + " or " + FileType.XML + ')');
}
# Validate
Do explicit validation on the parameters of user-facing public methods. When raising an exception, always specify the detailed message in the following format:
public void setValue(int value) {
if (value < 0) {
// Note that the exception message contains the offending value
// as well as the expected value.
throw new IllegalArgumentException("value: " + value + " (expected: >= 0)");
}
}
# Prefer JDK API
Prefer using plain JDK API when the same behavior can be achieved with the same amount of code.
// Prefer A (JDK) - less indirection
Map<String, String> map = new HashMap<>(); // A (JDK)
Map<String, String> map = Maps.newHashMap(); // B (Guava)
// Prefer B (Guava) - simpler yet more efficient
List<String> list = Collections.unmodifiableList( // A (JDK)
otherList.stream().filter(...).collect(Collectors.toList()));
List<String> list = otherList.stream().filter(...) // B (Guava)
.collect(toImmutableList());
# Prefer early-return style
Prefer 'early return' code style for readability.
// Great
public void doSomething(String value) {
if (value == null) {
return;
}
// Do the actual job
}
// Not great
public void doSomething(String value) {
if (value != null) {
// Do the actual job
}
}
However, when the 'normal' execution path is very simple, this may also look beautiful:
public void doSomething(String value) {
if (value != null) {
return value.trim();
} else {
return null;
}
}
# Prefer MoreObjects.toStringHelper()
Prefer MoreObjects.toStringHelper()
to hand-written toString()
implementation.
However, consider writing hand-written or caching toString()
implementation
in performance-sensitive places.
# Think aesthetics
Do not insert an empty line that hurts code aesthetics.
// OK
if (...) {
doSomething();
}
// Not OK
if (...) {
doSomething();
// <-- Remove this extra line.
}
Similarly, do not use two or more consecutive empty lines.
// OK
public void a() { ... }
public void b() { ... }
// Not OK
public void a() { ... }
// <-- Remove this extra line.
public void b() { ... }