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).
- Prefer the simplest solution that fully satisfies the requirement over a clever or elaborate one — less code means fewer bugs, less to review, and less to maintain.
- Comments in English. Explain what and why, never how (if you need to explain how, rewrite the code).
- A comment citing a business rule, ADR, or other spec reference (e.g.
BR-008,ADR-012) states the tag alone — never restate the rule's content next to it. The tag is the traceability link; the spec is the single source of truth for what it says, and a restatement is a second copy that goes stale the moment the spec changes. Add prose beyond the tag only for a genuinely non-obvious constraint the tag doesn't convey by itself (e.g. an invariant the code would silently violate). - Idiomatic code following the community standards of each language/ecosystem.
- Code identifiers for a domain concept mirror its glossary term exactly, adjusted only for the language's own naming convention (camelCase, snake_case, PascalCase, ...); never invent a synonym for a term the glossary already defines.
- 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
- Prefer the simplest solution that satisfies the requirement; less code means fewer bugs
- Write idiomatic code that follows the community standards of the language
- Comments in English; explain why, not how; never comment what obvious code does
- Refer to a specification or ADR by its identifier; never restate its content
- An identifier for a domain concept mirrors its glossary term; never invent a synonym
- Extract a complex conditional into a testable function with a self-explanatory name
- Dependency direction: app → infra → usecase → domain; never outward
- The domain layer is technology-agnostic: no I/O, no persistence, no external calls
- Repository and output-port interfaces live in
usecase, not indomain - Prefer immutability and functional style over mutation
- Collapse a two-branch, side-effect-free conditional that returns a value into a ternary expression where the language has one
- Locks, sleeps, and polling signal a missing abstraction; never patch with timing hacks
- Sockets and threads are managed in the
infralayer only - Hand-roll a technical utility only if no maintained library fits; record the trade-off in an ADR
- Prefer an existing tool over a custom script; a
scripts/directory in a business service is a red flag - Keep build and CI files boilerplate-simple; size or complexity signals reinvented tooling
- Before adding a step that copies a file so a tool can find it, check whether the tool can read the original location