The core.
These rules apply to all supported languages as far as the language allows them naturally. The language profiles build on them.
Pureline standardizes principles, not syntax. Every language stays idiomatic: Pureline removes unnecessary noise without stripping away the features that make Java, JavaScript, TypeScript, Go, Crystal, Emvus, and Rust effective.
Core rules
Control Flow
Prefer Guard Clauses
Special cases leave the function early. The happy path stays as close to the base indentation level as possible.
Reading flow: Input → Validation → Transformation → Execution → Result. Not: if → if → else → if → callback → branch.
if (request != null) {
if (request.valid()) {
var session = sessions.create(request.user());
return executor.execute(session, request);
} else {
return Result.invalid();
}
}
return Result.empty(); if (request == null) {
return Result.empty();
}
if (!request.valid()) {
return Result.invalid();
}
var session = sessions.create(request.user());
return executor.execute(session, request); Avoid else
Instead of an else branch, invert the condition, handle the special case, and leave the block.
A ternary expression may be used for simple value selection. Complex nested ternaries are not Pureline.
if (valid) {
execute();
} else {
reject();
} var state = valid ? (open ? State.READY : State.CLOSED) : failed ? State.FAILED : State.INVALID; if (!valid) {
reject();
return;
}
execute(); var state = valid ? State.READY : State.INVALID; Explicit Blocks in Brace-Based Languages
In Java, JavaScript, TypeScript, Go, Emvus, and other brace-based languages, every control-flow body uses an explicit block, even for a single statement. This applies to if, for, while, and comparable constructs.
The rule does not ban idiomatic shorthand in languages where it is a normal, readable part of the language. Simple Crystal postfix guards such as return unless valid are Pureline-compliant.
if (!valid) return; if (!valid)
return; for (const user of users) notify(user); if (!valid) {
return;
} for (const user of users) {
notify(user);
} return unless valid Idiomatic Shorthand When It Stays Immediately Readable
Pureline standardizes clarity and structure, not identical syntax across all languages. Shorthand that is established in a language remains allowed as long as the condition and action are simple.
As soon as a shorthand requires thought, it becomes an explicit block.
notify_admins(session, reason: :expired) unless session.valid? || session.renewable? && !user.banned? return unless session.valid? if session.closed? || session.expired?
logger.info("Session is no longer usable")
return
end Names, Functions & Structure
Names Must Be Meaningful
As short as possible, as explicit as necessary. Cryptic abbreviations are just as bad as names that turn into sentences.
Exceptions are established local conventions such as loop counters (i, j, k).
r s p x tmp obj data2 thing currentIncomingPlayerConnectionRequestObject var tmp = sessions.find(r.id());
var data2 = tmp.user(); request session player packet handler
storage result connection worker One Clear Responsibility per Function
A function represents one clearly identifiable action. Vague names reveal that it does too much.
Size heuristic: ~5–30 lines normal, ~30–50 lines review, 50+ lines usually split. These are heuristics, not absolute limits.
handleEverything()
process()
doStuff()
executeAll() public void process(Request request) {
validate(request);
var session = sessions.create(request.user());
storage.save(session);
mailer.sendWelcome(request.user());
metrics.increment("sessions");
} validateRequest()
createSession()
sendPacket()
loadUser()
closeConnection() public Session openSession(Request request) {
var session = sessions.create(request.user());
storage.save(session);
return session;
} One Clear Responsibility per Class and Module
Manager classes that bundle database, players, packets, commands, configuration, HTTP, and logging are God Classes. Code is cut along domain responsibility.
A class has one clear responsibility when its members and internal state contribute to one cohesive concern. Line count alone does not determine whether a class is well designed; see PL-STRUCT-003 for size guidance.
ServerManager
├─ database
├─ players
├─ packets
├─ commands
├─ configuration
├─ HTTP
└─ logging server/ ServerRuntime
network/ Connection, PacketRegistry, PacketDispatcher
session/ Session, SessionService
storage/ UserStorage, SessionStorage Keep Namespaces Small Enough to Scan
File and type density are part of readability. A package or source folder should be small enough that its contents can be understood at a glance, without searching a long flat list of unrelated files.
When the limit is exceeded, split by responsibility instead of letting the folder become a dumping ground. A split should introduce a real responsibility boundary, not arbitrary nesting.
Default limits: Java 8 production top-level types/source files per package; Emvus 8 .ev files per source/namespace folder; JavaScript and TypeScript 10 production modules per feature/source folder; Go 12 non-test, non-generated .go files per package directory; Crystal 10 .cr files per folder; Rust 10 hand-maintained .rs files per feature/source folder.
Not counted: tests and specs, generated code, metadata-only files, package-info.java, module-info.java, barrel files such as index.ts/index.js, generated bindings or protocol output, and migration snapshots and comparable machine-generated artifacts.
A project may override the thresholds through a future Pureline configuration file; the default remains intentionally strict.
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 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 Cohesion over Size
Cohesion over Size: the preferred maximum class size is guidance, never a hard limit. Strongly related logic stays together when splitting it would make the code harder to read, maintain, test, or reason about. A larger cohesive class is preferable to multiple artificial classes connected by unnecessary delegation.
Guidance: up to 200 lines is the preferred normal case; 201–400 lines is unproblematic when the class has a clear responsibility; 401–600 lines is a deliberate architectural decision whose cohesion should stay understandable; above 600 lines a review is recommended, but nothing is prohibited. These are heuristics: crossing a threshold is not a style violation by itself and does not require a split.
Larger classes are justified for parsers and lexers (a cohesive state machine gets more complicated when states and transitions are scattered across artificial abstractions), for algorithms with tightly coupled processing phases, for central data structures whose methods share the same invariants and internal state, and for complex but cohesive logic where a split only adds indirection, delegation, and coordination without more clarity.
Split anyway along real responsibility boundaries, not by line count. Indicators are distinct subsystems evolving independently, unrelated reasons to change, weakly coupled groups of methods, or separate lifecycles obscured by one class.
Tooling: Pureline Checker and IDE inspections may show an informational size notice with a cohesion-review suggestion, particularly above 600 lines. They must not treat line count as sufficient evidence of a violation, automatically extract classes, or require suppressions merely to keep a cohesive class intact. The principle applies to all language profiles (Java, JavaScript, TypeScript, Go, Crystal, Rust, Emvus); language-specific syntax and idioms continue to take precedence.
Lexer
├─ LexerStateA (delegates to LexerHelper)
├─ LexerStateB (delegates to LexerHelper)
└─ LexerHelper (shares all states with both) Lexer // one state machine, ~500 lines, one responsibility ReportService
├─ PDF rendering → ReportRenderer
├─ Mail delivery → ReportMailer
└─ Billing rules → BillingPolicy Abstraction Requires a Reason
Duplication alone does not justify an abstraction. Good reasons are: multiple real implementations, clear API boundaries, platform-specific implementations, test seams, dependency inversion, and shared domain contracts.
AbstractBaseFactoryManagerProvider public interface ISessionFactory {
Session create(User user);
}
public final class SessionFactoryImpl implements ISessionFactory {
public Session create(User user) {
return new Session(user);
}
} var session = new Session(user); multiple real implementations
clear API boundaries
platform-specific implementations
test seams
dependency inversion
shared domain contracts Formatting & Comments
Prefer Compact Horizontal Calls
Calls stay horizontal as long as they are readable. Arguments are not written one per line just because it is possible.
Vertical formatting is allowed when it reveals meaningful semantic structure, for example in builders.
var box = Block.box(
min,
min,
max,
max,
max,
16
); var box = Block.box(min, min, max, max, max, 16); var server = Server.builder()
.host(config.host())
.port(config.port())
.executor(executor)
.build(); Comments Explain Why, Not What
The code explains what happens. Comments explain why something unusual is necessary: protocol constraints, workarounds, external bugs, non-obvious mathematical behavior, or unusual performance constraints.
// Check if the player exists
if (player == null) {
return;
} // timeout in seconds for the handshake
var t = 30; // The protocol allows only one retry, a second one gets the client banned.
if (attempts > MAX_RETRIES) {
return;
} var handshakeTimeout = Duration.ofSeconds(30); Language & Values
Modern Features Where They Improve Clarity
Modern language features are used when they make code shorter, safer, or easier to understand, not to look modern.
String label;
switch (state) {
case READY:
label = "ready";
break;
default:
label = "unknown";
} var label = switch (state) {
case READY -> "ready";
default -> "unknown";
}; Prefer Framework-Native APIs
What the platform or framework already solves cleanly is not rebuilt. Custom wrappers need a real reason.
function copy(value) {
return JSON.parse(JSON.stringify(value));
} const copy = structuredClone(value); Avoid Magic Values
Values with meaning get a name. Values that may change operationally belong in configuration rather than deep in the implementation.
if (attempts > 3) {
connection.close(4008);
} if (attempts > MAX_LOGIN_ATTEMPTS) {
connection.close(CloseCode.POLICY_VIOLATION);
} Architecture & Runtime
Clear Dependency Direction
Dependencies flow in one direction: domain → service → infrastructure, or feature-oriented in modules such as session/, network/, storage/, command/, runtime/, api/.
Cycles such as network → session → storage → network are avoided.
network → session → storage → network public record Session(UUID id, JdbcTemplate jdbc) {
} domain
↓
service
↓
infrastructure Keep Side Effects Visible
Pureline does not require exposing every internal operation. It requires avoiding surprising hidden behavior.
sessions.createAndPersistAndNotify(user); var user = users.load(id);
var session = sessions.create(user);
sessions.save(session);
events.publish(new SessionCreated(session.id())); Immutability by Default
Values are immutable by default. Mutation is intentional, local, and easy to identify: Java records, TypeScript readonly data, const in JavaScript, immutable value objects, and short-lived mutable local state.
Shared mutable state is avoided where practical.
public static List<Session> ACTIVE = new ArrayList<>();
session.setState(State.CLOSED); public record Session(SessionId id, State state) {
public Session close() {
return new Session(id, State.CLOSED);
}
} interface Session {
readonly id: string;
readonly state: SessionState;
} Explicit Dependencies
Dependencies are visible in constructors, function parameters, or explicit module wiring. Hidden global access and unnecessary service locators are avoided.
Dependency injection is encouraged, but a heavy DI framework is not required.
public Session open(User user) {
var storage = ServiceLocator.get(IStorage.class);
return storage.create(user);
} public final class SessionService {
private final IStorage storage;
public SessionService(IStorage storage) {
this.storage = storage;
}
} Performance with Intent
Readability is the default. Hot paths are optimized deliberately: avoid unnecessary allocations, avoid repeated reflection, cache expensive resolution, use appropriate collections, avoid hidden blocking, reduce copying, and keep ownership clear.
Optimizations must remain understandable.
public void dispatch(Packet packet) {
var handler = packet.getClass().getMethod("handle");
handler.invoke(packet);
} private final Map<Class<?>, IPacketHandler> handlers;
public void dispatch(Packet packet) {
handlers.get(packet.getClass()).handle(packet);
} Resources with a Clear Lifecycle
Whatever is opened, registered, started, allocated, or subscribed has an equally clear way back. The language profiles make this concrete: try-with-resources in Java, defer in Go, cleanup paths in JavaScript.
function watch(element) {
window.addEventListener("resize", () => layout(element));
} function watch(element) {
const onResize = () => layout(element);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
} Review checklist
Is the code Pureline? The checklist covers what no checker can judge automatically.