Coding Standards: Clean CodeΒΆ
This page turns Clean Code (Robert C. Martin) into rules an agent can apply directly in this repository. Every rule is checkable: you can look at the code and answer yes or no.
Style mechanics (formatting, naming conventions, import order, lint gates) live in the root AGENTS.md under Coding Standards. This page covers readability: names, functions, comments, structure, error handling, and boundaries.
ScopeΒΆ
Apply these rules to code you write or modify. Do not mass-refactor working code you did not touch β clean the code in your change, and leave neighboring code alone unless it violates a rule in a way that causes bugs.
Languages: Python (mcpgateway/, plugins/), Rust (crates/), TypeScript/JavaScript (Admin UI). Examples are Python; the rules transfer.
NamesΒΆ
- A name states what the thing is or does.
validate_tool_payloadtells the truth;process_datadoes not. - Follow repo convention:
snake_casefunctions and variables,PascalCaseclasses,UPPER_CASEconstants. - One concept, one word, at least per module.
fetch,retrieve, andgetare one concept β pick one and keep it. - Use the encodings the codebase already uses (
Dbprefix for ORM models,Pydanticalias prefix). Invent no new ones. - Booleans read as predicates:
is_active,has_scope,can_execute. - Delete disinformation. A name that understates or overstates scope misleads harder than a vague one.
FunctionsΒΆ
- One function does one thing. When you can extract a function and name a step of the current function with it, extract it.
- Keep functions small. A function past ~50 lines gets a second look; past ~100, split it or record why not. These numbers trigger attention; they are not hard gates.
- Few arguments. Prefer 3 positional or fewer; move to a dataclass or keyword-only arguments beyond that.
- A boolean that switches a function between two behaviors is two functions. A boolean that filters a query is a parameter.
- Command-query separation: a function performs an action or returns data. Repository exception: service methods that mutate and return the updated record β the return value documents the new state.
- A
get_*orlist_*function never mutates persisted state. - Raise the specific exception (
ToolNotFoundError,PermissionError). ReturnNonefor "absent", never for "failed".
Comments: Last ResortΒΆ
- Make the code self-documenting first. Extract a function, rename a variable, introduce an explaining constant β keep going until the comment you were about to write becomes redundant.
- A comment earns its place only when the code cannot express the content:
- a security invariant and why it fails closed,
- a workaround for an upstream bug, with the link,
- a dependency pin and what breaks without it,
- a protocol or regulatory constraint external to the code.
- Comments never carry transient implementation process or decisions. Route each to its owner:
- an architectural decision goes to an ADR (
docs/docs/architecture/adr/), - implementation process goes to the commit body,
- future work goes to a GitHub issue.
- Delete on sight: commented-out code, change history ("now uses X", "fixed in #123" β the commit owns this), narration of what the next line does.
- A
TODOnames its issue number, or becomes one. - Docstrings are API contract, not comments. Presence and parameter coverage are enforced (Ruff
D1/D417, interrogate). Summary line first, then the contract. Prose follows the Agent Prose Standard.
StructureΒΆ
- One responsibility per class and module. The moment a service validates schemas and also sends notifications, split it.
- Keep related code together and declare near first use.
- Behavior that belongs to one model lives in one service; keep it out of routers and away from duplication.
- Hide chains.
gateway.peer_cache.entries.keys()inside a method is fine; passing that chain around the codebase is not.
Error HandlingΒΆ
- Catch what you handle; let the rest propagate.
except Exceptionappears only at a genuine boundary (request handler, task runner) that converts the failure to a response or a log. - Never swallow silently. A handler that logs and continues states why continuing is safe.
- Fail closed on security paths: an unknown auth mode rejects; it never defaults.
- Error text carries the technical name verbatim (
QueuePool limit exceeded) plus the context an operator needs. For security, HTTP responses do NOT contain technical names that would reveal implementation structure or details.
BoundariesΒΆ
- Validate at the edge: Pydantic schemas own input validation at the routers; internals trust typed inputs.
- Wrap external services (httpx, redis, cpex): map their failures to repo exception types in one place.
- Adapt third-party return types at the boundary; do not leak them past the service layer.
TestsΒΆ
Clean tests guard the rules above; full conventions live in tests/AGENTS.md. The readability rules that apply to tests: a test name states the behavior (test_expired_token_is_rejected), one behavioral concept per test, arrange-act-assert structure.
What Not to RefactorΒΆ
- The synchronous-SQLAlchemy-in-async-handlers pattern β a documented design decision (root
AGENTS.md). - Dependency-pin comments in
pyproject.tomlβ the durable-constraint comment done right. - Alembic migrations already merged β historical record.
Checking Your WorkΒΆ
A change meets this standard when:
- Every name you added states its purpose.
- Every function you wrote or touched does one thing.
- Every comment you added or kept states a constraint the code cannot express.
- No comment carries process, history, or decisions.
-
make ruff interrogate pylintandmake mypypass on your files.