d195e8c3a9
---ci--- project: atelier phase: 3 milestone: v0.2 status: complete requirements: covered: [ATELIER-48, ATELIER-49, ATELIER-50, ATELIER-51, ATELIER-52, ATELIER-59] partial: [] ---/ci---
8.0 KiB
8.0 KiB
Anti-Patterns
A catalog of violations. Each entry names the principle it breaches. Use this to recognize and reject patterns on sight.
How to Use
When you see a pattern listed here, it is a defect. Cite the principle it violates and require a fix. These are not "to be reviewed later"; they are rejected on sight.
Core Anti-Patterns (C1–C8)
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Code that "mostly works" | C1 Correctness | Mostly correct is incorrect |
function doStuff() |
C2 Clarity | Name reveals nothing |
| A 500-line function | C3 Simplicity | Complexity is a liability |
| Config in a distant repo, read silently | C4 Locality | Coupling you cannot see |
A migration with no down |
C5 Reversibility | Irreversible bet |
| A component reading global state implicitly | C6 Composability | Hidden dependency |
| A service with no logs | C7 Observability | Cannot debug what you cannot see |
| Loading all records into memory | C8 Economy | Unbounded = OOM |
Domain Anti-Patterns
UI/UX
| Anti-Pattern | Breaches | Why |
|---|---|---|
Image without alt |
P2 Accessibility | Disqualifying |
| "Delete" with no confirmation | P5 Forgiveness | Irreversible surprise |
color: #3b82f6 in a component |
P8 Consistency (via tokens) | Bypasses design system |
| "Submit" on a delete button | P3 Clarity | Wrong verb |
| Layout shift on image load | P7 Hierarchy / CLS | Visual instability |
API
| Anti-Pattern | Breaches | Why |
|---|---|---|
/getUsers (verb in URL) |
P2 Clarity | Resources are nouns |
| 200 with an error body | P9 Error Transparency | Status code lies |
| 500 with a stack trace | P8 Security, P9 | Information leak |
No Idempotency-Key on a POST |
P6 Idempotency | Retry is unsafe |
| 10MB response by default | P7 Performance | Unbounded payload |
Security
| Anti-Pattern | Breaches | Why |
|---|---|---|
eval() of any string |
P4, P5 | Code injection |
| Hardcoded API key in source | P9 Secret Hygiene | Committed secret |
catch (e) {} (swallow) |
P7, P8 | Silent failure, fail-open |
md5 for password hashing |
P6 Crypto | Broken primitive |
Open CORS * in production |
P1, P10 | Zero trust violated |
chmod 777 |
P2 Least Privilege | Maximum privilege |
| Logging the request body | P9 Secret Hygiene | Token leak |
Data
| Anti-Pattern | Breaches | Why |
|---|---|---|
VARCHAR for a UUID |
P7 Type Fidelity | Wrong type |
| No FOREIGN KEY | P3, P9 | Unenforced relationship |
FLOAT for money |
P7, P1 | Floating point error |
is_deleted without filtering |
P8 Lifecycle | Soft-delete leak |
SELECT * |
P10 Performance | Unbounded columns |
Testing
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Test that cannot fail | P10 No Test Theater | Not a test |
Date.now() in a fixture |
P3 Determinism | Flaky |
| Shared fixture mutated across tests | P2 Independence | Order-dependent |
| 500 e2e tests, 50 unit | P4 Fast Feedback | Inverted pyramid |
name: "test" fixture |
P7 Realism | Hides bugs |
Performance
| Anti-Pattern | Breaches | Why |
|---|---|---|
| N+1 query in a loop | P3 Complexity | O(N) queries |
| No timeout on HTTP call | P4, P8 (concurrency) | Hang forever |
| Cache with no invalidation | P5 Caching | Stale forever |
| Unbounded in-memory sort | P4 Resource Bounds | OOM |
| Optimization without measurement | P1 Measure First | Guesswork |
Observability
| Anti-Pattern | Breaches | Why |
|---|---|---|
console.log("here") |
P1 Structured | Not queryable |
user_id as a metric label |
P4 Cardinality | Unbounded bill |
| Average latency only | P8 SLO | Hides the tail |
No trace_id propagation |
P2 Correlation | Cannot trace |
Logs without request_id |
P3 Context | No correlation |
Errors
| Anti-Pattern | Breaches | Why |
|---|---|---|
catch (e) { return null } |
P2 Fail Loudly | Silent failure |
throw new Error("error") |
P3 Fail Specifically | Generic |
return null for "not found" |
P1 Errors are Data | Conflates absence with error |
| Retry without backoff | P5, P8 | Retry storm |
throw in a recovery path |
P6 | Fail fast in wrong place |
Documentation
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Doc with no examples | P3 Examples | Incomplete |
| Stale doc (wrong, not updated) | P4 Currency | Worse than no doc |
| Unlisted doc (not in MANIFEST) | (framework rule) | Not part of framework |
| No audience statement | P2 Audience | Who is this for? |
Concurrency
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Shared mutable state, no lock | P1 Immutability | Race condition |
| Unbounded queue | P9 Bounded Queues | OOM |
channel.send() with no timeout |
P8 Timeout | Hang |
| Mutex held across I/O | P3 Lock Scope | Lock too long |
| Spawned work with no cancellation | P7 Cancellation | Orphaned work |
DevOps
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Manual deploy script | P2 Automation | Not repeatable |
| No rollback path | P4 Rollback | Irreversible deploy |
| Big-bang deploy | P5 Progressive | All-or-nothing |
| Rebuild per environment | P7 Immutability | Different artifacts |
| Snowflake server | P1, P6 | Not reproducible |
Infrastructure as Code
| Anti-Pattern | Breaches | Why |
|---|---|---|
Unpinned provider (source without version) |
P5 Version Everything | Unversioned drift |
terraform apply without a read plan |
P4 Plan Before Apply | Unreviewed mutation |
| Local state in a shared environment | P8 Remote State with Locking | No lock = corruption |
| Hardcoded secret in HCL | P10 Secrets Never in Code | Committed secret |
| Copy-pasted block instead of a module | P6 Modules Compose | Bug duplicated |
| Manual change to a managed resource | P9 Drift is Recoverable | Unreconciled drift |
| Admin credentials in CI | P7 Least Privilege Providers | Overbroad grant |
Committed terraform.tfstate |
P3 State is Truth, P10 | Secret-bearing artifact in repo |
Kubernetes
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Bare pod (no controller) | P2 Pods are Mortal | No recovery/scale |
| No resource requests in prod | P4 Requests and Limits | BestEffort, first evicted |
| Liveness probe checks a dependency | P5 Probes Drive Health | Cascade restart |
cluster-admin bound to a workload |
P7 RBAC by Intent | Overbroad grant |
:latest image tag in prod |
P5 Version Everything | Unversioned drift |
emptyDir for data that must persist |
P8 Storage is Explicit | Data lost on pod death |
| Secrets baked into the image | P9 Config and Secrets Separate | No rotation without rebuild |
default namespace in prod |
P6 Namespaces Bound Blast Radius | No blast boundary |
| Shared PVC across StatefulSet replicas | P8 Storage is Explicit | Concurrent write corruption |
Delete reclaim policy on prod storage |
P8, P5 Reversibility | PVC delete = data delete |
Cross-Cutting Anti-Patterns
| Anti-Pattern | Breaches | Why |
|---|---|---|
| "I'll add tests later" | C1 (no proof of correctness) | Later never comes |
| "It's just a prototype" | C5 (irreversible by default) | Prototypes go to prod |
| Copy-paste code | C6 (no composition) | Bug duplicated |
| God object | C3, C6 | One thing, many things |
| Leaky abstraction | C6, C2 | Hidden coupling |
v0.2 Chaos Anti-Patterns (from IDEATE-13, IDEATE-14)
| Anti-Pattern | Breaches | Why |
|---|---|---|
| Orphaned P-rule (a domain principle with no matrix row) | matrix completeness, C6 | Breaks the conflict-resolution arbiter; the rule has no core trace |
Deployable example artifact (standalone .tf/.yaml under examples/) |
PROJECT.md "no runtime code", D-025 | Violates the docs-only contract; examples must be .md with fenced code |
| Unlisted v0.2 doc (new doc not added to MANIFEST) | manifest rule | Not part of the framework by definition |