Java Coding Style - raisercostin/software-wiki GitHub Wiki
References
- https://github.com/twitter-archive/commons/blob/master/src/java/com/twitter/common/styleguide.md
- https://google.github.io/styleguide/javaguide.html
Records
- Use records for java16
- java15 and smaller
- Use minimal lombok
- Lombok's
@Data
and@Value
generate getters and setters that is a bad practice to have (to add link to argument). - Best to use just
@ToString @EqualsAndHashCode
based on the documentation of @Data and @Value:- @Data = Equivalent to @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode
- @Value= Equivalent to @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @AllArgsConstructor @ToString @EqualsAndHashCode
- Lombok's
- Use minimal lombok
- Kotlin - Use records
- Scala - Use case classes
Specialized
- Lombok, Mapstruct, JPA
- JPA
- Exception Handling & Audit
- Formatting and cleanup configuration
- Vavr lesons
- jackson should read to java Iterable/List (vavr reads native java and then converts)
- every step in vavr generates a new collection is not good for heap
- preferable to use
vavr.Iterator
orRichIterable
- preferable to use
- jackson write can use Iterable for writing which is perfect. You don't need to have full structures but just to iterate on some
- domain should be in VAVR since is imutable and can be changed while iterating over without any concurrency issues
- Vavr - use
RichIterable
RichIterable.fromVavr
- should be used on top of anyvavr.Traversable
. This is just a wrapper so is O(1).RichIterable.fromJava
- should be used on top of anyjava.Collection
orjava.Iterable
. This is just a wrapper so is O(1).- The operations on RichIterable are transitory so will not create intermediate vavr collections.
- If needed a
memoizeAsJava()
ormemoizeAsVavr
will create a collection and use the iterable on that further. In this way the previous filters/maps will not be applied on each iterator. - Sorting/reversing and other operations will be transfered in java collections that are faster than vavr.
- In the future we might change the RichIterable implementation to be based on
- paguro - https://github.com/GlenKPeterson/Paguro
- see XForm - Functional transformations, like Clojure's transducers
- bifurcan
- virtual collections - https://github.com/lacuna/bifurcan#virtual-collections
- paguro - https://github.com/GlenKPeterson/Paguro
- See more on https://github.com/lacuna/bifurcan/blob/master/doc/comparison.md
- Vavr & jackson
- jackson library reads java lists then converts to vavr collections. Let's keep the java collections better or Iterable?
- Doubles - see https://github.com/raisercostin/revomatico/wiki/Data-Structures
- search, remove or replace with BigDecimal any reference to
double
andDouble
field - replace withBigDecimal
doubleValue()
- conversion to doublenew Double(value)
- withBigDecimal.valueOf(value)
Double.parseDouble(stringValue)
- withnew BigDecimal(stringValue)
- com.google.common.math.DoubleMath
- comparing double using
==
orequals
orcomparator
with the expectation to get a 0 in case of equality - (!warning!) comparing Double or BigDecimal must be make with an epsilon/precision
- search, remove or replace with BigDecimal any reference to
- Antipatterns
sorted
follwed byreverse
orreverseOrder
.sorted(selector).reverse()
=>.sorted(comparator.reverse())
sortBy
followed byreverse
.sortBy(selector).reverse()
=>.sorted(Comparator.comparing((T outcome) -> selector).reversed())
.sortBy(selector).reverse()
=>.sortBy(-selector)
if is possible
sortBy
followed bysortBy
- use a single sort(comparator)- RichIterable has a sortByReversed
.sortBy(selector).reverse()
=>(RichIterable)list.sortByReversed(selector)
- Libraries
- async-http-client library
- WebClient (or WebClientLocation that is the jedio wrapper) that is very good async library
- Do Not Use
- Unirest, apache-http-client (sync/async), fibers
- async-http-client library
Lombok, Mapstruct, JPA
There are some issues when combining these technologies.
Advice
-
Do not pass to hibernate
vavr.Seq.asJava()
since the collection is not mutable. Usevavr.Seq.toJavaList()
-
The mapper can create objects
- forced via constructors instead builder -
@Mapper(config = GotappMapstructConfig.class, builder = @Builder(disableBuilder = true)) public interface InsisMapper {
- using no args constructor
- using all args constructor
- forced via constructors instead builder -
-
This is what we use in
revobet.core.domain.player.BetslipDetail
class
//Lombok & JPA
// - this sample: https://stackoverflow.com/a/60900881/99248
// https://stackoverflow.com/questions/34241718/lombok-builder-and-jpa-default-constructor
//Lombok
//Mandatory in conjunction with JPA: an equal based on fields is not desired
@lombok.EqualsAndHashCode(onlyExplicitlyIncluded = true)
//Mandatory in conjunction with JPA: force is needed to generate default values for final fields, that will be overriden by JPA
@lombok.NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
//Hides the constructor to force usage of the Builder.
@lombok.AllArgsConstructor(access = AccessLevel.PRIVATE)
@lombok.ToString
//Good to just modify some values
@lombok.With
//Mandatory in conjunction with JPA: Some suggest that the Builder should be above Entity - https://stackoverflow.com/a/52048267/99248
//Good to be used to modify all values
@lombok.Builder(toBuilder = true)
//final fields needed for imutability, the default access to public - since are final is safe
@lombok.experimental.FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC)
//no getters and setters
@lombok.Getter(value = AccessLevel.NONE)
@lombok.Setter(value = AccessLevel.NONE)
//JPA
@javax.persistence.Entity
@javax.persistence.Table(name = "BET_SLIP_DETAIL")
//jpa should use field access
@javax.persistence.Access(AccessType.FIELD)
public class BetSlipDetail {
@javax.persistence.Id
@javax.persistence.GeneratedValue
//Used also automatically as JPA
@lombok.EqualsAndHashCode.Include
Long id;
Long betId;
String matchId;
String marketCode;
- Adding
@lombok.Builder(toBuilder = true)
will be used by mapstruct but the@AfterMapping
will need to use theLeagueUpdate.LeagueUpdateBuilder
lombok generated class. The problem shows up if you want to call some operations on originalLeagueUpdate
class seevalidateFromFeed()
:
@Deprecated
@Mapping(target = "leagueCodeFromFeed", source = "league.id")
@Mapping(target = "leagueName", source = "league.name")
@Mapping(target = "locationCodeFromFeed", source = "league.country")
@Mapping(target = "locationName", source = "league.country")
@Mapping(target = "season", source = "league.season")
@Mapping(target = "start", source = "league.dateStart")
@Mapping(target = "end", source = "league.dateEnd")
@Mapping(target = "source", expression = "java(league)")
LeagueUpdate toLeague(GSLeague league, @Context GSSport context);
@AfterMapping
default void toLeagueAfter(@MappingTarget /*LeagueUpdate*/LeagueUpdate.LeagueUpdateBuilder leagueUpdate, GSLeague league,
@Context GSSport sport) {
leagueUpdate.sportCodeFromFeed = sport.id + "";
leagueUpdate.sportName = sport.name;
leagueUpdate.leagueCodeFromFeed = league.id;
//leagueUpdate.validateFromFeed(); - cannot be called on builder
}
lombok with wither
Also see https://github.com/rzwitserloot/lombok/issues/2150
I'm using lombok-1.18.12. I was able to initialize the fields with
class LombokTest {
@lombok.Value
@lombok.NoArgsConstructor(force = true)
@lombok.AllArgsConstructor(access = AccessLevel.PRIVATE)
@lombok.With
@lombok.Builder(access = AccessLevel.PRIVATE)
//@lombok.experimental.FieldDefaults(makeFinal = true, level = AccessLevel.PUBLIC)
//@lombok.Getter(value = AccessLevel.NONE)
//@lombok.Setter(value = AccessLevel.NONE)
public static class Point {
@lombok.Builder.Default
private final int x = 3;
private final int y;
}
@Test
void testWithers() {
assertThat(new Point().withY(2).toString()).isEqualTo("NodesTest.Point(x=3, y=2)");
assertThat(new Point().withX(5).withY(2).toString()).isEqualTo("NodesTest.Point(x=5, y=2)");
assertThat(new Point().withX(5).toString()).isEqualTo("NodesTest.Point(x=5, y=0)");
}
}
Lombok field defaults
When using lombok @FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true)
and need to override some default fields it is mandatory to also use the @Builder.Default
on the targeted fields otherwise the field will not be overridden.
Also in the lombok.config
file the following setting is needed:
lombok.anyConstructor.addConstructorProperties=true
If the @lombok.NoArgsConstructor
is enabled then the com.fasterxml.jackson.annotation.JsonCreator
should be added to one of constructors in onConstructor
configuration
Using lombok-1.18.20, the expexcted behaviour would be achieved as follows:
@Slf4j
@Value
@lombok.AllArgsConstructor(access = AccessLevel.PUBLIC, onConstructor_ = { @ConstructorBinding })
//@lombok.NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
//if enabled add com.fasterxml.jackson.annotation.JsonCreator to one of constructors. see onConstructor
@lombok.With
@lombok.Builder(toBuilder = true)
@lombok.Getter(value = AccessLevel.NONE)
@lombok.Setter(value = AccessLevel.NONE)
@FieldDefaults(level = AccessLevel.PUBLIC, makeFinal = true)
@ConfigurationProperties(prefix = "lsports.kafka.preplay")
public class LsportsKafkaConfig {
public static LsportsKafkaConfig readPreplayConfig() {
return RichConfig.readConfig("classpath:lsports-kafka.conf", "lsports.kafka.preplay",
LsportsKafkaConfig.class);
}
public String topic;
public String brokerAddress;
@Builder.Default
public long bufferMemorySize = 26000002;
@Builder.Default
public long maxRequestSize = 25000002;
}
JPA
- All time fields that are stored in database should be like this. Exceptions should be discussed. (#1784)
@Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL") OffsetDateTime date;
- All database entities should have a created and updated field managed by database
@Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL") OffsetDateTime created; @Column(columnDefinition = "TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL") @UpdateTimestamp OffsetDateTime updated;