Pureline

Pureline Java

Modernes Java ohne Enterprise-Zeremonie.

Pureline Java nutzt die moderne Sprache direkt: Records, Sealed Types, Pattern Matching, Switch Expressions und Virtual Threads. Framework- und Infrastrukturcode bleibt so nah wie möglich an der eigentlichen Aufgabe.

RecordsI-prefixed InterfacesModern JavaExplicit Null HandlingVirtual Threads
10Profilregeln
18automatisiert
Java-Code prüfen

Profilregeln

PL-JAVA-001

Packages nach Verantwortung

Packages werden nach fachlicher Verantwortung geschnitten. Generische Sammelstellen wie util/, manager/, misc/ oder helper/ sind nur erlaubt, wenn das Package einen echten, engen Zweck hat.

Avoid
Sammelstellen
src/main/java/
    space/example/project/
        util/
        manager/
        helper/
        misc/
        stuff/
Pureline
src/main/java/
    space/example/project/
        api/
        runtime/
        service/
        network/
        storage/
        model/
PL-JAVA-002 Checker

Naming

Klassen verwenden PascalCase, Methoden sind kurze Verben, Variablen benennen ihre Rolle. Interfaces tragen das Prefix I.

Ein-Buchstaben-Variablen sind nicht erlaubt, außer bei etablierten lokalen Konventionen wie Schleifenzählern.

Avoid
Interface ohne Prefix, kryptische Variablen
public interface Session {
}

Session s = sessions.open(r);
class sessionService {
}
Pureline
Klassen
SessionService
PacketRegistry
UserStorage
ConnectionState
Interfaces
ISession
IStorage
IPacket
IConnection
Methoden
open()
close()
load()
save()
execute()
resolve()
PL-JAVA-003 Checker

Records für immutable Daten

Wenn ein Record genügt, gibt es kein Boilerplate-POJO mit Feldern, Konstruktor und Gettern.

Avoid
public final class SessionId {
    private final UUID value;

    public SessionId(UUID value) {
        this.value = value;
    }

    public UUID getValue() {
        return value;
    }
}
Pureline
public record SessionId(UUID value) {
}
PL-JAVA-004 Checker

Explizites Null-Handling

Objects.requireNonNull ist kein normaler Kontrollfluss. Null-Zustände werden sichtbar als Guard behandelt oder mit einer fachlichen Exception beantwortet.

Avoid
Objects.requireNonNull(value);
Pureline
if (value == null) {
    return;
}
Fachlicher Fehler
if (config == null) {
    throw new ConfigurationException("Missing configuration");
}
PL-JAVA-005 Checker

Imports statt FQCNs

Voll qualifizierte Klassennamen gehören in den Import-Block, nicht in den eigentlichen Code.

Avoid
java.util.concurrent.CompletableFuture<Result> future;
Pureline
import java.util.concurrent.CompletableFuture;
PL-JAVA-006 Checker

Modernes Java bevorzugen

Records, Sealed Types, Pattern Matching, Switch Expressions, var, Virtual Threads, Text Blocks und Streams werden genutzt, wenn sie den Code verbessern.

Moderne Syntax soll Lärm reduzieren, nicht Neuheit um ihrer selbst willen erzeugen.

Avoid
if (shape instanceof Circle) {
    Circle circle = (Circle) shape;
    return circle.area();
}
Pureline
if (shape instanceof Circle circle) {
    return circle.area();
}
PL-JAVA-007 Checker

switch für echte Zustandsverzweigung

Echte Zustandsverzweigungen werden als Switch Expression formuliert, statt als lange Kette unzusammenhängender Zustandsprüfungen.

Avoid
if (state == State.READY) {
    return start();
}

if (state == State.CLOSED) {
    return stop();
}

if (state == State.FAILED) {
    return recover();
}
Pureline
return switch (state) {
    case READY -> start();
    case CLOSED -> stop();
    case FAILED -> recover();
};
PL-JAVA-008 Checker

Nebenläufigkeit

Main- und Tick-Threads werden nie durch HTTP, Datenbankzugriffe, Dateisystem oder blockierende Netzwerk-I/O blockiert.

Für blockierende I/O sind Virtual Threads die bevorzugte Wahl, wo es passt.

Avoid
Blockierende I/O im Tick
public void tick() {
    var profile = http.send(request, BodyHandlers.ofString());
    players.update(profile.body());
}
Plattform-Thread pro Aufgabe
new Thread(() -> storage.save(session)).start();
Pureline
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> storage.save(session));
}
PL-JAVA-009

Streams nur, wenn klarer

Streams sind willkommen, wenn sie die Lesbarkeit verbessern. Eine Schleife ist die bessere Wahl, wenn sie klarer, leichter zu debuggen oder für den Hot Path besser geeignet ist.

Avoid
Stream mit Seiteneffekten
sessions.stream()
    .filter(Session::expired)
    .peek(session -> log.info("closing {}", session.id()))
    .forEach(session -> {
        session.close();
        storage.delete(session.id());
    });
Pureline
Stream für Transformation
var names = users.stream().map(User::name).toList();
Schleife für Seiteneffekte
for (var session : sessions) {
    if (!session.expired()) {
        continue;
    }

    session.close();
    storage.delete(session.id());
}
PL-JAVA-010 Checker

Lifecycle-Symmetrie

Alles, was geöffnet, registriert, gestartet, allokiert oder abonniert wird, braucht einen ebenso klaren Gegenpfad: close, unregister, stop, release oder unsubscribe.

Avoid
Kein Gegenpfad
public void start() {
    events.register(listener);
    scheduler.scheduleAtFixedRate(this::tick, 0, 50, MILLISECONDS);
}
Ressource ohne close
var reader = Files.newBufferedReader(path);
return reader.readLine();
Pureline
public void start() {
    events.register(listener);
}

public void stop() {
    events.unregister(listener);
}
try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}

Core-Regeln im Java-Checker

Zusätzlich zu den Profilregeln prüft der Checker für Java diese Regeln aus dem Pureline Core: