Pureline

Der Core.

Diese Regeln gelten für alle unterstützten Sprachen, soweit die Sprache sie natürlich erlaubt. Die Sprachprofile bauen darauf auf.

Linear FlowExplicit IntentMinimal NoiseSmall UnitsClear OwnershipCohesion over Size

Pureline standardisiert Prinzipien, nicht Syntax. Jede Sprache bleibt idiomatisch: Pureline entfernt unnötigen Lärm, ohne die Features wegzunehmen, die Java, JavaScript, TypeScript, Go, Crystal, Emvus und Rust stark machen.

Kernregeln

Kontrollfluss

PL-CF-001 Checker

Guard Clauses bevorzugen

Sonderfälle verlassen die Funktion früh. Der Happy Path bleibt so nah wie möglich an der Basis-Einrückung.

Lesefluss: Input → Validation → Transformation → Execution → Result. Nicht: if → if → else → if → callback → branch.

Avoid
if (request != null) {
    if (request.valid()) {
        var session = sessions.create(request.user());
        return executor.execute(session, request);
    } else {
        return Result.invalid();
    }
}

return Result.empty();
Pureline
if (request == null) {
    return Result.empty();
}

if (!request.valid()) {
    return Result.invalid();
}

var session = sessions.create(request.user());
return executor.execute(session, request);
PL-CF-002 Checker

else vermeiden

Statt eines else-Zweigs wird die Bedingung umgedreht, der Sonderfall erledigt und der Block verlassen.

Für einfache Wertauswahl ist ein Ternary erlaubt. Verschachtelte Ternaries sind nicht Pureline.

Avoid
if (valid) {
    execute();
} else {
    reject();
}
Verschachtelter Ternary
var state = valid ? (open ? State.READY : State.CLOSED) : failed ? State.FAILED : State.INVALID;
Pureline
if (!valid) {
    reject();
    return;
}

execute();
Erlaubt: einfache Wertauswahl
var state = valid ? State.READY : State.INVALID;
PL-CF-003 Checker

Explizite Blöcke in Klammer-Sprachen

In Java, JavaScript, TypeScript, Go, Emvus und anderen Sprachen mit geschweiften Klammern bekommt jeder Kontrollfluss-Body einen expliziten Block, auch bei genau einer Anweisung. Das gilt für if, for, while und alle vergleichbaren Konstrukte.

Die Regel verbietet keine idiomatischen Kurzformen in Sprachen, in denen sie ein normaler, lesbarer Teil der Sprache sind. Einfache Crystal-Postfix-Guards wie return unless valid sind Pureline-konform.

Avoid
Inline-Body
if (!valid) return;
Eingerückter Einzeiler
if (!valid)
    return;
Schleife ohne Block
for (const user of users) notify(user);
Pureline
if (!valid) {
    return;
}
for (const user of users) {
    notify(user);
}
Idiomatische Kurzform in Crystal
return unless valid
PL-CORE-005

Idiomatische Kurzformen, wenn sie sofort lesbar bleiben

Pureline standardisiert Klarheit und Struktur, nicht identische Syntax über alle Sprachen. Kurzformen, die in einer Sprache etabliert sind, bleiben erlaubt, solange Bedingung und Aktion einfach sind.

Sobald eine Kurzform Nachdenken erfordert, wird sie zum expliziten Block.

Avoid
Kurzform mit zu viel Logik
notify_admins(session, reason: :expired) unless session.valid? || session.renewable? && !user.banned?
Pureline
return unless session.valid?
Komplex: expliziter Block
if session.closed? || session.expired?
  logger.info("Session is no longer usable")
  return
end

Namen, Funktionen & Struktur

PL-NAME-001 Checker

Namen tragen Bedeutung

So kurz wie möglich, so explizit wie nötig. Kryptische Kürzel sind genauso schlecht wie Namen, die zu Sätzen werden.

Ausnahmen sind etablierte lokale Konventionen wie Schleifenzähler (i, j, k).

Avoid
Bedeutungslos
r   s   p   x   tmp   obj   data2   thing
Überlang
currentIncomingPlayerConnectionRequestObject
Im Code
var tmp = sessions.find(r.id());
var data2 = tmp.user();
Pureline
request   session   player   packet   handler
storage   result    connection   worker
PL-FN-001 Checker

Eine klare Verantwortung pro Funktion

Eine Funktion steht für eine klar erkennbare Aktion. Vage Namen verraten, dass sie zu viel tut.

Größen-Heuristik: ~5–30 Zeilen normal, ~30–50 Zeilen Review, ab 50 Zeilen in der Regel teilen. Das sind Heuristiken, keine absoluten Limits.

Avoid
Vage Namen
handleEverything()
process()
doStuff()
executeAll()
Eine Funktion, drei Aufgaben
public void process(Request request) {
    validate(request);
    var session = sessions.create(request.user());
    storage.save(session);
    mailer.sendWelcome(request.user());
    metrics.increment("sessions");
}
Pureline
validateRequest()
createSession()
sendPacket()
loadUser()
closeConnection()
Eine klar benannte Aktion
public Session openSession(Request request) {
    var session = sessions.create(request.user());
    storage.save(session);
    return session;
}
PL-STRUCT-001 Checker

Eine klare Verantwortung pro Klasse und Modul

Manager-Klassen, die Datenbank, Spieler, Pakete, Commands, Konfiguration, HTTP und Logging bündeln, sind God Classes. Code wird nach fachlicher Verantwortung geschnitten.

Eine Klasse hat eine klare Verantwortung, wenn ihre Member und ihr interner Zustand zu einem zusammenhängenden Anliegen beitragen. Die Zeilenzahl allein entscheidet nicht, ob eine Klasse gut entworfen ist; Größenorientierung steht in PL-STRUCT-003.

Avoid
God Class
ServerManager
 ├─ database
 ├─ players
 ├─ packets
 ├─ commands
 ├─ configuration
 ├─ HTTP
 └─ logging
Pureline
server/     ServerRuntime
network/    Connection, PacketRegistry, PacketDispatcher
session/    Session, SessionService
storage/    UserStorage, SessionStorage
PL-STRUCT-002

Pakete und Ordner klein genug zum Überblicken

Datei- und Typ-Dichte gehören zur Lesbarkeit. Ein Paket oder Quellordner soll so klein sein, dass man seinen Inhalt auf einen Blick versteht, ohne eine lange flache Liste unzusammenhängender Dateien zu durchsuchen.

Wird das Limit überschritten, wird nach Verantwortung geteilt, statt den Ordner zur Müllhalde werden zu lassen. Ein Split soll eine echte Verantwortungsgrenze einführen, nicht willkürliche Verschachtelung erzeugen.

Default-Limits: Java 8 Produktions-Top-Level-Typen/Quelldateien pro Paket; Emvus 8 .ev-Dateien pro Quell-/Namespace-Ordner; JavaScript und TypeScript 10 Produktionsmodule pro Feature-/Quellordner; Go 12 Nicht-Test-, nicht generierte .go-Dateien pro Paketverzeichnis; Crystal 10 .cr-Dateien pro Ordner; Rust 10 handgepflegte .rs-Dateien pro Feature-/Quellordner.

Nicht mitgezählt werden: Tests und Specs, generierter Code, reine Metadaten-Dateien, package-info.java, module-info.java, Barrel-Dateien wie index.ts/index.js, generierte Bindings oder Protokoll-Ausgaben sowie Migrations-Snapshots und vergleichbare maschinell erzeugte Artefakte.

Ein Projekt darf die Schwellen künftig über eine Pureline-Konfigurationsdatei überschreiben; der Default bleibt bewusst streng.

Avoid
14 Dateien in einem Paket
network/
    Connection.java
    ConnectionFactory.java
    ConnectionState.java
    Packet.java
    PacketCodec.java
    PacketDecoder.java
    PacketEncoder.java
    PacketRegistry.java
    PacketDispatcher.java
    PacketHandler.java
    PacketQueue.java
    PacketMetrics.java
    SocketConfig.java
    SocketRuntime.java
Pureline
Nach Verantwortung geteilt
network/
    Connection.java
    ConnectionFactory.java
    ConnectionState.java

network/packet/
    Packet.java
    PacketCodec.java
    PacketDecoder.java
    PacketEncoder.java
    PacketRegistry.java
    PacketDispatcher.java

network/runtime/
    PacketQueue.java
    PacketMetrics.java
    SocketConfig.java
    SocketRuntime.java
PL-STRUCT-003

Zusammengehörigkeit vor Zeilenbegrenzung

Cohesion over Size: Die bevorzugte Maximalgröße einer Klasse ist eine Orientierung, niemals ein hartes Limit. Eng zusammengehörige Logik bleibt zusammen, wenn ein Split den Code schwerer lesbar, wartbar, testbar oder nachvollziehbar machen würde. Eine größere kohäsive Klasse ist besser als mehrere künstliche Klassen, die durch unnötige Delegation verbunden sind.

Orientierung: bis 200 Zeilen ist der bevorzugte Normalfall; 201–400 Zeilen sind unproblematisch bei klarer Verantwortung; 401–600 Zeilen sind eine bewusste Architekturentscheidung, deren Kohäsion verständlich bleiben soll; über 600 Zeilen wird ein Review empfohlen, aber nichts ist verboten. Das sind Heuristiken: Eine überschrittene Schwelle ist für sich kein Stilverstoß und erzwingt keinen Split.

Größere Klassen sind gerechtfertigt bei Parsern und Lexern (eine kohäsive State Machine wird komplizierter, wenn Zustände und Übergänge über künstliche Abstraktionen verstreut sind), bei Algorithmen mit eng gekoppelten Verarbeitungsphasen, bei zentralen Datenstrukturen, deren Methoden dieselben Invarianten und denselben internen Zustand teilen, sowie bei komplexer, aber kohäsiver Logik, bei der ein Split nur Indirektion, Delegation und Koordinationsaufwand ohne mehr Klarheit bringt.

Trotzdem gesplittet wird entlang echter Verantwortungsgrenzen, nicht nach Zeilenzahl. Hinweise sind eigenständige Subsysteme mit unabhängiger Entwicklung, unzusammenhängende Änderungsgründe, schwach gekoppelte Methodengruppen oder getrennte Lebenszyklen, die eine Klasse verdeckt.

Tooling: Pureline Checker und IDE-Inspektionen dürfen einen informativen Größenhinweis mit Kohäsions-Review zeigen, besonders über 600 Zeilen. Sie dürfen die Zeilenzahl nicht als hinreichenden Beleg für einen Verstoß werten, keine Klassen automatisch extrahieren und keine Suppressions verlangen, nur um eine kohäsive Klasse intakt zu lassen. Das Prinzip gilt für alle Sprachprofile (Java, JavaScript, TypeScript, Go, Crystal, Rust, Emvus); sprachspezifische Syntax und Idiome haben weiterhin Vorrang.

Avoid
Künstlicher Split nur wegen der Zeilenzahl
Lexer
 ├─ LexerStateA   (delegiert an LexerHelper)
 ├─ LexerStateB   (delegiert an LexerHelper)
 └─ LexerHelper   (teilt alle Zustände mit beiden)
Pureline
Kohäsiv, auch wenn groß
Lexer          // eine State Machine, ~500 Zeilen, eine Verantwortung
Trotzdem teilen: echte Grenzen
ReportService
 ├─ PDF-Rendering       → ReportRenderer
 ├─ Mail-Versand        → ReportMailer
 └─ Abrechnungsregeln   → BillingPolicy
PL-ARCH-001

Abstraktion braucht einen Grund

Duplikation allein rechtfertigt keine Abstraktion. Gute Gründe sind: mehrere echte Implementierungen, klare API-Grenzen, plattformspezifische Implementierungen, Test Seams, Dependency Inversion und gemeinsame Domain-Verträge.

Avoid
AbstractBaseFactoryManagerProvider
Interface ohne zweite Implementierung
public interface ISessionFactory {
    Session create(User user);
}

public final class SessionFactoryImpl implements ISessionFactory {
    public Session create(User user) {
        return new Session(user);
    }
}
Pureline
Direkt, bis es einen echten Grund gibt
var session = new Session(user);
Gute Gründe
multiple real implementations
clear API boundaries
platform-specific implementations
test seams
dependency inversion
shared domain contracts

Formatierung & Kommentare

PL-FMT-001 Checker

Kompakte horizontale Aufrufe

Aufrufe bleiben horizontal, solange sie lesbar sind. Argumente werden nicht einzeln untereinander geschrieben, nur weil es geht.

Vertikale Formatierung ist erlaubt, wenn sie echte semantische Struktur zeigt, etwa bei Buildern.

Avoid
var box = Block.box(
    min,
    min,
    max,
    max,
    max,
    16
);
Pureline
var box = Block.box(min, min, max, max, max, 16);
Semantische Gruppierung
var server = Server.builder()
    .host(config.host())
    .port(config.port())
    .executor(executor)
    .build();
PL-DOC-001 Checker

Kommentare erklären das Warum

Der Code erklärt, was passiert. Kommentare erklären, warum etwas Ungewöhnliches nötig ist: Protokoll-Constraints, Workarounds, externe Bugs, nicht offensichtliche Mathematik oder ungewöhnliche Performance-Anforderungen.

Avoid
Kommentar wiederholt den Code
// Check if the player exists
if (player == null) {
    return;
}
Kommentar ersetzt einen Namen
// timeout in seconds for the handshake
var t = 30;
Pureline
Kommentar erklärt das Warum
// The protocol allows only one retry, a second one gets the client banned.
if (attempts > MAX_RETRIES) {
    return;
}
Name statt Kommentar
var handshakeTimeout = Duration.ofSeconds(30);

Sprache & Werte

PL-CORE-011

Moderne Features, wenn sie Klarheit schaffen

Moderne Sprachmittel werden genutzt, wenn sie Code kürzer, sicherer oder verständlicher machen, nicht um modern auszusehen.

Avoid
String label;
switch (state) {
    case READY:
        label = "ready";
        break;
    default:
        label = "unknown";
}
Pureline
var label = switch (state) {
    case READY -> "ready";
    default -> "unknown";
};
PL-CORE-017

Framework-native APIs bevorzugen

Was die Plattform oder das Framework bereits sauber löst, wird nicht nachgebaut. Eigene Wrapper brauchen einen echten Grund.

Avoid
function copy(value) {
    return JSON.parse(JSON.stringify(value));
}
Pureline
const copy = structuredClone(value);
PL-CORE-018

Magic Values vermeiden

Werte mit Bedeutung bekommen einen Namen. Werte, die sich betrieblich ändern können, gehören in die Konfiguration statt tief in die Implementierung.

Avoid
if (attempts > 3) {
    connection.close(4008);
}
Pureline
if (attempts > MAX_LOGIN_ATTEMPTS) {
    connection.close(CloseCode.POLICY_VIOLATION);
}

Architektur & Laufzeit

Klare Abhängigkeitsrichtung

Abhängigkeiten fließen in eine Richtung: domain → service → infrastructure, oder feature-orientiert in Modulen wie session/, network/, storage/, command/, runtime/, api/.

Zyklen wie network → session → storage → network werden vermieden.

Avoid
Zyklus
network → session → storage → network
Domain kennt Infrastruktur
public record Session(UUID id, JdbcTemplate jdbc) {
}
Pureline
domain
↓
service
↓
infrastructure
PL-CORE-015

Seiteneffekte sichtbar halten

Pureline verlangt nicht, jede interne Operation offenzulegen. Überraschendes, verstecktes Verhalten wird aber vermieden.

Avoid
sessions.createAndPersistAndNotify(user);
Pureline
var user = users.load(id);
var session = sessions.create(user);

sessions.save(session);
events.publish(new SessionCreated(session.id()));
PL-CORE-010

Immutability als Default

Werte sind standardmäßig unveränderlich. Mutation ist bewusst, lokal und leicht erkennbar: Java Records, readonly in TypeScript, const in JavaScript, immutable Value Objects und kurzlebiger lokaler State.

Geteilter veränderlicher Zustand wird vermieden, wo es praktikabel ist.

Avoid
Geteilter, mutierbarer Zustand
public static List<Session> ACTIVE = new ArrayList<>();

session.setState(State.CLOSED);
Pureline
public record Session(SessionId id, State state) {
    public Session close() {
        return new Session(id, State.CLOSED);
    }
}
interface Session {
    readonly id: string;
    readonly state: SessionState;
}
PL-CORE-019

Explizite Abhängigkeiten

Abhängigkeiten sind in Konstruktoren, Parametern oder expliziter Modul-Verdrahtung sichtbar. Versteckter globaler Zugriff und unnötige Service Locator werden vermieden.

Dependency Injection ist willkommen, ein schweres DI-Framework ist aber keine Pflicht.

Avoid
Versteckter globaler Zugriff
public Session open(User user) {
    var storage = ServiceLocator.get(IStorage.class);
    return storage.create(user);
}
Pureline
public final class SessionService {
    private final IStorage storage;

    public SessionService(IStorage storage) {
        this.storage = storage;
    }
}
PL-CORE-020

Performance mit Absicht

Lesbarkeit ist der Default. In Hot Paths wird bewusst optimiert: unnötige Allokationen vermeiden, keine wiederholte Reflection, teure Auflösung cachen, passende Collections wählen, kein verstecktes Blocking, weniger Kopien und klare Ownership.

Optimierungen müssen verständlich bleiben.

Avoid
Reflection pro Paket im Hot Path
public void dispatch(Packet packet) {
    var handler = packet.getClass().getMethod("handle");
    handler.invoke(packet);
}
Pureline
Einmal auflösen, dann nachschlagen
private final Map<Class<?>, IPacketHandler> handlers;

public void dispatch(Packet packet) {
    handlers.get(packet.getClass()).handle(packet);
}
PL-CORE-016 Checker

Ressourcen mit klarem Lebenszyklus

Was geöffnet, registriert, gestartet, allokiert oder abonniert wird, hat einen ebenso klaren Weg zurück. Die Sprachprofile konkretisieren das: try-with-resources in Java, defer in Go, Aufräumpfade in JavaScript.

Avoid
function watch(element) {
    window.addEventListener("resize", () => layout(element));
}
Pureline
function watch(element) {
    const onResize = () => layout(element);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
}

Review-Checkliste

Ist der Code Pureline? Die Checkliste deckt ab, was kein Checker automatisch beurteilen kann.