General Coding Guidelines¶
Applies to all projects, independent of programming language or artifact type. OOP-specific guidance is in the OO Design and Programming Guidelines. Tool chain and build environment guidance is in the Tool Chain Guidelines.
Clean Code¶
- Clarity, readability, and maintainability over performance (unless performance is explicitly critical).
- Comments in English. Explain what and why, never how (if you need to explain how, rewrite the code).
- Idiomatic code following the community standards of each language/ecosystem.
- Define type aliases to give the programmer better hints and to improve type safety.
- Move complex conditionals to separate, testable function with self-explanatory name.
- Collapse a two-branch, side-effect-free conditional that only returns a value into a single expression — a ternary where the language has one (Java, C++, TypeScript). Go has no ternary operator, so an if/else with two return statements remains idiomatic there. This is distinct from a guard clause (an early return for validation/error handling, followed by unrelated further code) — keep those as early returns.
Clean Architecture¶
- Dependencies point inward only:
app→infra→usecase→domain. Never outward. - Main program - Handles command line arguments, environment variables, signals and exit codes. Instanciates and runs app
app- An abstraction for the application, instactiates and wires componentsdomain— technology-agnostic data structures and domain logic; immutable/functional, no side-effectsusecase— orchestrates domain objects; defines interfaces implemented byinfra; spans transaction boundaries, side-effects viainfra- Repository and other output-port interfaces are defined here, not in
domain/. The domain layer has no knowledge of persistence.
- Repository and other output-port interfaces are defined here, not in
infra— implements side effects: HTTP handlers, DB clients, message brokers, etc.- Enforce in the pipeline (architecture check): ArchUnit (Java), GoArch (Go), Clang-Tidy (C++).
Immutability/Functional Programming¶
- Prefer immutability over mutation
- Prefer functional style over procedural style
Architectural Red Flags¶
These patterns are not bugs — they are design signals. Each one is a mandatory prompt to re-examine the architecture before writing more code.
- Locks, sleep/delay, polling: explicit synchronization primitives, timed waits, and busy-wait loops indicate a missing abstraction (event, queue, reactive stream). Reach for the right primitive; do not patch a structural problem with a timing hack.
- Sockets and thread management in business services: direct use of sockets or thread
APIs in domain or use-case code signals a broken layer boundary — infrastructure concerns
leaking into business logic. These belong exclusively in the
infralayer. Business services orchestrate domain rules; they do not manage I/O or concurrency. - Hand-rolled technical types/utilities: implementing a technical data type or algorithm (UUID generation, retry/backoff, hashing, date/time math, ...) from scratch when a well-maintained library already provides it. Acceptable only when no adequately maintained library exists, or the only fitting library is too heavyweight for the small slice of functionality actually needed — in that case, record the tradeoff in an ADR.
- A
scripts/directory in a business service: business (fach-)services have a uniform shape — request in, process, respond — and rarely need bespoke scripts. Its presence signals that infrastructure concerns (cert generation, DNS/config distribution, ...) have crept into a layer that shouldn't own them; that tooling belongs in a technical/infrastructure service. - Oversized or elaborate build/CI files: a business service's Makefile, pom.xml, CMakeLists.txt, or CI pipeline should be boilerplate — these services are structurally identical to one another. Unusual length or intricacy usually means custom logic was added that duplicates something the toolchain or a library already provides.
- Custom scripts reinventing a solved problem: e.g. a hand-written script to reformat a coverage report. If the problem is generic and widely solved, look for an existing tool or library before writing one.
- A copy/sync step standing in for a direct reference: a build or CI step whose only job is moving or transforming a file from A to B so a tool can find it, when the tool could instead be pointed straight at A. Every such step is a new failure mode — it can fail outright, it assumes write access where only reading was needed, and it creates two locations that must be kept in sync, so it may run stale or be forgotten. Before adding one, check whether the tool has a built-in way to read directly from the original location.
Directives¶
- Clarity and readability over performance unless performance is explicitly critical
- Comments in English; explain why not how; never comment what obvious code does
- Dependency direction: app → infra → usecase → domain; never outward
- Domain layer: technology-agnostic, no I/O, no persistence, no external calls
- Repository and output-port interfaces are defined in
usecase, not indomain - Enforce architecture constraints in CI (ArchUnit for Java, go-arch-lint for Go, Clang-Tidy for C++)
- Prefer immutability and functional style over mutation and procedural style
- Collapse a two-branch, side-effect-free value-returning conditional into a single ternary expression (Java/C++/TypeScript); Go has no ternary, so if/else with two returns stays idiomatic there — guard-clause early returns (validation/error short-circuiting) are unaffected
- Locks, sleep/delay, polling → signal a missing abstraction; do not patch with timing hacks
- Direct socket or thread management belongs exclusively in the
infralayer - Hand-rolling a technical type/utility is allowed only if no well-maintained library exists, or the existing library is too heavyweight for the need (document that tradeoff in an ADR)
- A
scripts/directory in a business service is a red flag — script-based tooling belongs in infrastructure/technical services - Business-service build/CI files should stay boilerplate-simple; unusual size or complexity signals reinvented tooling
- Prefer an existing tool over a custom script for generic, already-solved problems
- Before adding a build/CI step that copies or syncs a file so a tool can find it, check whether the tool can read directly from the original location instead