Pantheon/docs/coding-best-practices.md

205 lines
7.8 KiB
Markdown

# Coding Best Practices — C
The standard code in this repo is held to. PRs are reviewed against it, and review
comments cite it by rule number (`blocker: rule 3, this file is 1,400 lines`).
Rule numbers are frozen. New rules append; they never renumber.
The PR process itself — size, evidence, comment conventions — is in
[`../CONTRIBUTING.md`](../CONTRIBUTING.md).
---
## Rules
**1. Descriptive names.** Every name says what it is at a glance — files, functions,
types, variables. The test: would you know what it is from the tree alone, without
opening it? `pn_prefix_cache_lookup()`, not `lookup()`. Abbreviate only what is
universal (`id`, `url`, `http`, `kv`, `ctx`).
**2. Small functions — ~100 lines of body.** Doc comments don't count. Past ~100, look
for the extraction point. One blank line between logical chunks, never two.
**3. Small files — under 1,000 lines of code.** Comments and headers don't count. Prefer
more files with fewer functions. Every function in a file must belong there.
**4. Library-first.** Write every function as if it will be extracted into a standalone
library — self-contained, minimal dependencies, no hard-coded project values in reusable
logic. Headers define the contract, sources hold the implementation. Extraction should
mean moving files, not rewriting them.
**5. Folder nesting is navigation.** Group by domain, not by file type. Each directory
has one clear purpose. A directory holding one file is usually wrong.
**6. Error transparency.** Never swallow an error. Return a code or log it — never both
nothing. No empty error paths. Callers get a typed error, not a guess.
**7. Delete dead code.** Don't comment it out, don't leave unused functions or includes.
Git has it if you need it back.
**8. Prove a test can fail.** A test you have never seen fail is not evidence. Break the
code it covers, watch it go red, report the failure count.
> **Apple's `make` is 3.81 (2006)** and compares mtimes at one-second granularity. A
> source edited in the same second as its object is reported `up to date` and is **not
> rebuilt** — so the obvious mutation loop can be running the previous binary and
> reporting a vacuous gate that isn't. When proving a test red, `rm` the specific `.o`
> **and** the linked binary first. Switching build configs needs `rm -rf build` entirely.
---
## File headers
Every source file opens with the SPDX pair, then the header block:
```c
/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Copyright (C) 2026 David (Corey) Herrera */
/**
* @file prefix_cache.c
* @brief Longest-common-prefix KV reuse across turns.
*
* @status None
* @issues None
* @todo None
*/
```
`@status` / `@issues` / `@todo` are always present, `None` when empty — the empty slot is
the reminder that the slot exists. When you work in a file, clear resolved entries and
reset to `None`.
**Required on every new file.** Existing coverage is partial and is being backfilled;
until that lands, CI enforces the rule on added files only.
**Exempt:** test files, generated sources, and third-party-derived files (which keep their
upstream attribution — a Pantheon block may go below it, never in place of it).
---
## Documentation
**Public functions are documented in the header**, which is the API contract:
```c
/**
* Look up the longest cached prefix for a token sequence.
*
* @param cache Prefix cache handle
* @param tokens Token sequence to match
* @param n Length of tokens
* @return Matched prefix length, or 0 if nothing matched
*
* Thread-safe: yes (internal locking)
* Reentrant: no
* Signal-safe: no
*/
```
The three safety annotations are always present. `TBD` is an acceptable answer; omitting
them is not. Internal helpers need docs only when the name and signature don't carry it.
**Inline comments explain why, not what.** The code already says what it does.
```c
/* K80 has no __ldg for 8-byte types — split the load or it serialises */
```
---
## Layout
`include/` and `src/` mirror each other. `snake_case` filenames that name their contents
(`die_manager.c`, not `dm.c`). One header + one source per logical unit. Tests mirror the
source tree.
Every public symbol carries its module prefix — `pn_` for Pantheon, `fc_`/`tm_`/`ft_` for
Foundry. Anything not declared in a header is `static`. No exceptions.
---
## C conventions
**Formatting.** 4 spaces, no tabs. 100 columns. `char *ptr` — the star binds to the
variable. Opening brace on the **same line**, for functions and control flow alike.
```c
int pn_cache_lookup(pn_cache *cache, const uint32_t *tokens, size_t n) {
if (!cache || !tokens)
return -PN_EINVAL;
for (size_t i = 0; i < cache->count; i++) {
if (entry_matches(&cache->entries[i], tokens, n))
return (int)cache->entries[i].len;
}
return 0;
}
```
Omit braces on single-statement bodies — unless any branch of an `if`/`else` chain needs
them, in which case every branch gets them.
**Includes**, four groups separated by blank lines: this module's own header first (it
catches missing dependencies in that header at compile time), then standard library, then
system/platform, then project headers.
**Errors.** Functions return `int` (0 success, negative error) or a pointer (`NULL` on
failure). Use goto-cleanup for anything acquiring more than one resource — one exit path,
deterministic teardown. Error codes are a prefixed enum with a string conversion function.
**Memory.** `sizeof(*ptr)`, never `sizeof(Type)`. `calloc` or explicit zeroing. Document
ownership in the function doc — who allocates, who frees. Every `_open`/`_create`/`_new`
has a matching `_close`/`_destroy`/`_free`. `free(p); p = NULL;`. Arena-allocate groups
sharing a lifetime. Never return a stack address.
**const.** Every pointer parameter is `const` unless the function must modify what it
points at.
**Strings and buffers.** `sprintf`, `strcpy`, `strcat`, `gets`, and unbounded `scanf("%s")`
are banned — use `snprintf`, `strlcpy`, `strlcat`, `fgets`, width-limited `scanf`. Always
pass buffer sizes explicitly; check the `snprintf` return for truncation.
**Initialize everything.** `int rc = 0;`, `char *buf = NULL;`, `struct foo f = {0};`.
Declare close to first use, except where goto-cleanup requires it earlier.
**Opaque types** at public API boundaries — the struct definition stays in the `.c`.
Callers go through the API, never into fields.
**Header guards** use the project prefix (`PANTHEON_PREFIX_CACHE_H`), with the guard name
repeated in the `#endif` comment, and `extern "C"` in every public header.
**Asserts** document invariants that must never be false if the code is correct. Don't
assert things that legitimately fail — I/O, allocation, user input — those get error codes.
**Compiler.** The standard, modelled on SQLite and comparable C projects:
```
-std=c11 -Wall -Wextra -Werror -pedantic
```
plus `-Wshadow`, `-Wstrict-prototypes`, `-Wformat=2`, `-Wnull-dereference`,
`-Wconversion`, `-fstack-protector-strong`. Zero warnings, always.
The build does not yet meet this — it is `-Wall -Wextra -std=c11` today. Closing that gap
is tracked in the conformance spec in `plans/`; the standard above is the target the code
is held to, not a description of the current Makefile.
---
## Logging
| Level | Use |
|---|---|
| `ERROR` | operation failed, cannot continue |
| `WARN` | unexpected but recoverable |
| `INFO` | major state transitions, operation boundaries |
| `DEBUG` | decision points — which branch, and why |
| `TRACE` | entry/exit, raw values, wire data |
`DEBUG` and `TRACE` compile out in release builds. Log at boundaries and decisions, never
inside hot loops at `INFO` or above. Never log secrets, raw binary, or the success of
trivial operations. One line per message, greppable, with timestamp, level, and source
location.
The test: if you would add a `printf` to debug it, a `log_debug` should already be there.