手册:代码编写约定/Java
此页面记载MediaWiki的开发指引,由开发人员随着时间推移达成共识,精心制作而来(或有些时候由开发人员的领导公告而来) |
This page describes the coding conventions used within files of the MediaWiki codebase written in Java. See also the general conventions that apply to all program languages, including Java.
Build
Maven is used as a build tool. All projects should inherit from the JVM Parent POM. It configures a number of static analysis tools and linters that will help in keeping projects coherent. Code conventions are embodied in the configuration used by that pom.xml and will not be repeated here. The README file in that project describes how to use this parent pom.
Java projects hosted on Gerrit should be built by Jenkins by using the -maven-java8-docker template. Other templates are available to publish documentation and to manage the release process.
Java projects should be analyzed by SonarCloud.
Java version
Most of our projects are at the moment targeting Java 8. We are in the process of transitioning to Java 11.
SDKMAN can help you manage multiple JDK and easily switch between them.
Projects can use a .sdkmanrc
file to configure SDKMAN (see 任务 T346611).
Libraries
A number of libraries are commonly used throughout our various Java projects. Using the same libraries for similar use cases can help coherence across projects. That being said, use the list below as suggestions, not as a hard rule.
Production Code
Guava
Guava is used on a number of projects. Note that in some cases, we have dependencies that themselves rely on outdated Guava versions, which prevents us from using the latest version.The features of Guava that we use the most are:
Note that Caffeine should be preferred to Guava for caching.
Lombok
While Lombok is not strictly a library, it provides very useful syntactic sugar to Java. Since Lombok is an annotation processor executed at compile time, Lombok has no runtime dependencies and is thus not creating compatibility issues.
Lombok can be further configured by adding a lombok.config
file at the package level, which might be particularly useful to make it play nice with Jackson.
An example of such configuration:
lombok.equalsAndHashCode.callSuper = call # disable SpotBugs, it does not like generated code by lombok lombok.extern.findbugs.addSuppressFBWarnings = true lombok.addLombokGeneratedAnnotation = true # useful for jackson that will be able to use the all args constructor when deserializing lombok.anyConstructor.addConstructorProperties = true
A few particularly useful features:
- @Data: makes it easy to generate DTO / DO, taking care of all this getters / setters and having a proper, valid
equals()
andhashCode()
. - @SneakyThrows: a better way of dealing with checked exceptions than wrapping them in an unchecked exception.
Depending on your IDE, you might need to install a lombok plugin. See documentation for your specific IDE.
JSR 305
JSR 305 provides annotations to better document expected behaviour and help detect bugs. Those annotations are only compile time dependencies and not required at runtime, they don't generate dependency issues.
In particular:
- @Nonnull / @Nullable - specifies that a method parameter or return value should be non-null or nullable.
- @ParametersAreNonnullByDefault - all parameters on this class / package / method are expected to be non null unless otherwise specified.
- @Immutable: this class is immutable.
- @ThreadSafe / @NotThreadSafe: this class is or is not thread safe.
JSON / XML
Jackson is used for most JSON and XML parsing / serialization, with the use of annotations.
HTTP client
Apache HttpComponents is used as an HTTP client.
As much as possible a single instance of HttpClient
should be shared within an application.
A custom User-Agent string should be configured.
Testing
Unit Testing Framework
We mostly use JUnit as a testing framework. Some projects are using JUnit 4, while others are using JUnit 5. Some projects have dependencies on component specific testing libraries that don't support JUnit 5 yet. For projects that don't have this limitation, JUnit 5 should be preferred.
Mocking
Mockito is used as a mocking framework where needed.
HTTP Mocking
WireMock is used as an HTTP mock server, which allows testing code that relies on HTTP interactions. Note that WireMock makes it easy to also test various faults (delays, timeout, etc...).
Assertions
AssertJ is our main assertion framework.
In almost all cases AssertJ should be preferred over plain assert
, JUnit assertions or Hamcrest.
Various conventions
Unit vs Integration Tests
We follow the usual naming conventions to identify unit tests vs integration tests.
Managing null
and defensive programming
As much as possible, null
values should be avoided.
Null Objects should be preferred.
Parameters should be expected to be non null and not explicitly checked, unless they come from an untrusted caller.
Parameters should be marked with @Nonnull
/ @Nullable
to allow static analysis tools to validate nullity.
Optionals should be used sparingly. Read this blog post before committing to using Optionals.
serialVersionUID
Context
Serialization and the use of serialVersionUID is complex topic. In short:
- when a class is serialized, it's
serialVersionUID
is part of the serialization - when a class is deserialized, the serialized
serialVersionUID
is compared with the target class, if they don't match, deserialization fails serialVersionUID
can be set manually, otherwise it is auto computed by the serialization runtime, the UID will change if the structure of the class changes, it might be different across different JRE vendors or versions
Leaving the runtime to compute the serialVersionUID
can lead to cases where class versions should be compatible but still fail deserialization.
Manually setting the serialVersionUID
can lead to cases where class versions should not be compatible but still successfully deserialize, leading to silent problems (when the class structure has changed, but the serialVersionUID
has not been updated).
guidelines
serialVersionUID
should be manually defined only when:
- classes are expected to be deserialized (not if they just need to extend Serializable)
- the class or JVM version might change between serialization and deserialization
If manually defined, serialVersionUID
should start at 0
and be incremented on each change to the class structure.
The range from 0
to 10000
is considered non-generated by the FindBugs' ImmatureClass
rules
Constants
Constants are declared following the usual Java convention: public static final <TYPE> CONSTANT_NAME
with the constant name in capital letters.
Scala constants follow the usual convention of using UpperCamelCase.
Maven
Dependencies
In multi module projects, we define version numbers in the main pom of the project, in the <dependencyManagement/>
section. This ensures that if a dependency is used in multiple modules, the same version of that dependency is used. Even if a dependency is used in a single module, we still define version in the main pom of the project. In the rare case where a module needs a different version of a dependency than the rest of the project, it is defined in the pom of that module. This makes it easier to spot exceptions.
When multiple dependencies share the same version, that version number is specified as a property. For example, we often use Jackson, which provides multiple jars for different extensions, all those jars needs to share the same version number.