Developer Guide
Build requirements
How to build
We use Gradle to build Keva. The following command will compile Keva, run tests and generate JARs:
./gradlew --parallel build
Setting up your IDE
You can import Keva 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 Keva.
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
Avoid using lombok.val
and lombok.var
Avoid using lombok.val
and lombok.var
in your code.
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() { ... }
Git flow
Follow the Git flow
For feature PR (to develop) use squash merge, for release PR (to master) use rebase merge, commit message merged to master/develop branch should be lowercase first word (e.g. "fix: some bug").
All pull requests must be passed to the code review and passed all the tests.