diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index a42cc85..861d86e 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -581,6 +581,15 @@ "strict": false, "description": "CPO methodology — product vision and strategy (North Star, product principles), competitive analysis and positioning, roadmap prioritization (RICE, Kano, OST), product-market fit frameworks (Sean Ellis test, retention curves), market sizing (TAM/SAM/SOM), platform strategy, product lifecycle management." }, + { + "name": "programming-principles", + "source": "./", + "skills": [ + "./programming-principles" + ], + "strict": false, + "description": "Distilled coding principles from 14 classic software books." + }, { "name": "pydanticai", "source": "./", diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index d0260ed..79635e6 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -85,6 +85,7 @@ "./product-discovery", "./product-methodology", "./product-strategy", + "./programming-principles", "./pydanticai", "./qa-methodology", "./raleigh", diff --git a/README.md b/README.md index dbfaf7b..903d0c1 100644 --- a/README.md +++ b/README.md @@ -273,6 +273,10 @@ Product management frameworks for translating validated evidence into prioritize CPO methodology — product vision and strategy (North Star, product principles), competitive analysis and positioning, roadmap prioritization (RICE, Kano, OST), product-market fit frameworks (Sean Ellis test, retention curves), market sizing (TAM/SAM/SOM), platform strategy, product lifecycle management. +### [programming-principles](programming-principles/SKILL.md) + +Distilled coding principles from 14 classic software engineering books (Clean Code, DDD, Refactoring, Release It!, DDIA, Code Complete, and more). Cross-cutting principles organized by concern, task-to-book mapping, per-book mini and full rule sets, and a structured code-assessment workflow. + ### [pydanticai](pydanticai/SKILL.md) Build production-grade AI agents and graph-based state machines with PydanticAI and PydanticGraph. Covers agent creation, function tools with RunContext dependencies, structured output validation, streaming (text/events/graph nodes), a 20+ capability plugin system with on-demand loading (Thinking, WebSearch, MCP, Hooks, etc.), 16 model providers with FallbackModel and concurrency limiting, multi-agent delegation and programmatic hand-off, comprehensive testing with TestModel/FunctionModel, and the PydanticEvals evaluation framework. Includes the full PydanticGraph API — both BaseNode (class-based) and GraphBuilder (function-based) with parallel map/broadcast operations, joins with reducers, decisions, Mermaid rendering, and step-by-step execution. Ships 8 reference files covering core agents, capabilities/hooks, graph, models/output, patterns/integrations, testing/evals, worked examples, and an API surface quick reference. diff --git a/bundles/neckbeard/references/routing-table.md b/bundles/neckbeard/references/routing-table.md index d1a8d89..0e4d473 100644 --- a/bundles/neckbeard/references/routing-table.md +++ b/bundles/neckbeard/references/routing-table.md @@ -27,6 +27,7 @@ ledger around it. | Producing a pass/conditional/blocked verdict with evidence | `verification-methodology` | Ledger boundary rules | | Reliability objectives, incident response, operational recovery, or delivery | `site-reliability-engineering` | Contract, ledger, rollback evidence | | Architecture decision records | `adr-authoring` | Decision-record template, ledger | +| Code review, refactoring, or implementation quality assessment against classic engineering principles | `programming-principles` | Contract, ledger, boundary verification | Two further specialists compose well but are narrower: `product-methodology` (prioritization, backlog, documented decisions) for the requirements stage, and diff --git a/programming-principles/README.md b/programming-principles/README.md new file mode 100644 index 0000000..31d664e --- /dev/null +++ b/programming-principles/README.md @@ -0,0 +1,33 @@ +# Programming Principles — 14 Classic Software Books + +Distilled coding principles from 14 classic software engineering books, organized for agent consumption. Cross-cutting principles by concern, task-to-book mapping, per-book rule sets at two depths, and a structured code-assessment workflow. + +## Why Install This Skill + +When your agent loads this skill, it gains a **shared vocabulary for code quality** grounded in established literature rather than vibes. That means: + +- **Principled code review** — findings cite specific books and principles, not just "this looks wrong" +- **Task-aware loading** — a task-to-book mapping table tells the agent which principles matter for the current work (refactoring? load Refactoring + WELC. architecture? load APoSD + Clean Architecture) +- **Progressive depth** — SKILL.md has enough to change decisions; reference files provide per-book depth on demand +- **Structured assessment** — a reproducible workflow for combining all 14 books against a real repo + +## What You Get + +| Directory | Purpose | +|-----------|---------| +| `SKILL.md` | Cross-cutting principles by concern, task-to-book mapping, per-book compressed rule cards, compatibility guide | +| `references/*.mini.md` | 14 per-book mini rule sets with triggers, decision rules, and checklists | +| `references/*.full.md` | 14 complete rule catalogs (11–63 KB each) for deep reference or audits | +| `references/code-assessment-workflow.md` | Structured workflow for combining all books against a real repo | + +## Triggers + +Load this skill for code review, refactoring, implementation quality assessment, architecture decisions, or any coding task where you want principled guidance rather than ad-hoc judgment. Also load when performing a structured code assessment against a repository. + +## Requirements + +Platform-agnostic. Works with any agent that supports the Agent Skills directory format. No external dependencies. + +## Quick Start + +Load SKILL.md for the core principles and task-to-book mapping. When a specific book's guidance is needed, load the corresponding `references/.mini.md`. For deep audits, load the `.full.md` variant. diff --git a/programming-principles/SKILL.md b/programming-principles/SKILL.md new file mode 100644 index 0000000..6940e10 --- /dev/null +++ b/programming-principles/SKILL.md @@ -0,0 +1,341 @@ +--- +name: programming-principles +description: Distilled coding principles from 14 classic software books. +license: MIT +compatibility: Platform-agnostic. Works with any agent that supports the Agent Skills + directory format. No external dependencies. +metadata: + source: https://github.com/mattpocock/agent-rules-books +--- + +# Programming Principles (14 Books) + +Principles distilled from the `mattpocock/agent-rules-books` repo — 14 pre-made +AGENTS.md rule sets derived from classic software engineering books. Use this +skill directly during code review, refactoring, design, and implementation. +For deeper per-book coverage, load the relevant reference file. + +## Task-to-Book Mapping + +When the task involves... Load / apply principles from +──────────────────────────────────────────────────────────────────────── +Everyday implementation & code review Clean Code, Code Complete +Refactoring existing code Refactoring, WELC +Architecture / dependency management Clean Architecture, APoSD +Domain modeling / business rules DDD, DDD Distilled, IDDD +Enterprise app patterns / layering PoEAA +Production reliability / stability Release It! +Data consistency / scalability / events DDIA +Engineering craft / automation Pragmatic Programmer +Designing APIs / module boundaries APoSD +Legacy code / risky changes WELC, Refactoring + +## How to Perform a Code Assessment + +For a structured, reproducible workflow that combines book principles with +actual repo exploration, see `references/code-assessment-workflow.md`. It +covers: loading the evaluation framework, reading the repo and GitHub context, +deduplicating against existing issues/PRs, classifying findings by book + +priority, and deciding whether each merits an issue. + +## Cross-Cutting Principles + +Principles synthesized from multiple books, organized by concern. + +### Naming & Communication + +- One term per concept across the codebase. RENAMING is design work. +- Names reveal abstraction, not mechanism. Prefer domain vocabulary over + technical implementation detail. +- Functions are verbs, classes/types are nouns, booleans are predicates. +- A name needing a comment to explain it is the wrong name. +- Comments exist for rationale, contracts, warnings, and non-obvious constraints + — never to narrate code or compensate for bad names. + +### Functions & Routines + +- ONE level of abstraction per function. Tell the story top-down. +- Keep parameters few. Boolean flags mean split the function. +- Separate commands (mutate) from queries (answer). Never both. +- When a module exceeds ~400 lines or requires scrolling to understand its + full scope, it's a candidate for decomposition. Split by stable + responsibility boundary, not by execution order or framework convention. +- The happy path must be readable. Isolate error handling, edge cases, and + cleanup from the main flow. +- A function too long to name precisely is too long. Extract. + +### Architecture & Boundaries + +- Source dependencies point INWARD toward higher-level policy. Domain and use + cases never import frameworks, databases, UI, or vendor SDKs. +- Every external dependency — HTTP clients, filesystems, databases, vendor + SDKs — must sit behind a trait or interface owned by inner layers. Direct + instantiation of infrastructure code inside application or domain logic is + a structural violation (Dependency Inversion Principle). +- Frameworks, databases, delivery mechanisms, and devices are outer-layer + details. Keep them behind ports, gateways, and adapters. +- Organize by business capability / use case first, NOT by technical layer + (controllers/, services/, repositories/). +- The dependency rule: inner layers OWN the interfaces they need; outer layers + IMPLEMENT them. +- Choose boundaries by volatility and policy importance, not by size or habit. +- A wrapper, layer, or abstraction must HIDE more complexity than it adds. + Pass-through layers are debt. + +### Data & State + +- Prefer types that make invalid states unrepresentable. Use enums for closed + sets, Value Objects for meaningful primitives, and booleans only for binary + meanings. +- Make data ownership explicit: distinguish source of truth from derived, + cached, or ephemeral data. +- Treat shared mutable state, globals, and ambient context as costs that must + earn themselves. +- Keep identity, lifecycle, mutation, and loading behavior visible. +- One authoritative representation per piece of system knowledge. Derive or + generate everything else. + +### Testing + +- Tests are production code: readable, deterministic, aligned with behavior. +- Keep tests focused on externally visible behavior, not internal + implementation details. +- Add a characterization test before changing behavior in untested code. +- When fixing a bug, add a regression test that would have caught it. +- Treat ignored, flaky, or skipped tests as unresolved questions. +- The Boy Scout Rule for tests: leave the test suite cleaner than you found it. + +### Error & Failure Handling + +- Distinguish: programmer errors (assert), contract violations (panic/fail), + expected domain failures (return/result type), retryable failures, and + permanent failures. +- Treat every dependency, timeout, retry, queue, and degraded state as capable + of failing in slow, partial, or prolonged ways. +- Timeouts on ALL outbound calls. No infinite waits. Finite retries with + exponential backoff and jitter. Never retry validation errors or permanent + failures — distinguish retryable from non-retryable outcomes. +- Validate external input at trust boundaries. Never trust shape, size, or + semantics of things from outside the process. +- Fail fast when continuing hides unrecoverable trouble. Let-it-crash only with + supervision and isolation. + +### Refactoring & Change + +- Refactoring is behavior-preserving structural improvement. Never disguise a + feature or redesign as cleanup. +- Work in small, reversible, buildable steps. Split patches too large for + local reasoning. +- Identify the structural friction blocking a change. Refactor BEFORE the + feature only when it makes the feature safer or simpler. +- Target the current blocking smell, not every smell in sight. Stop when the + next cleanup would be speculative. +- Remove duplication when the SAME edit appears a third time. +- Decompose when a module exceeds ~400 lines or handles 3+ distinct + responsibilities. A module you can't name in a single sentence is + doing too much — extract the cohesive sub-concepts. +- Prefer the simplest named move: rename, extract, inline, move, split, or + substitute. + +## Per-Book Compressed Rules + +### Clean Code (Martin) +Readability, local reasoning, maintainability. Corrects: "working code = clean." +- Functions: one thing, one level of abstraction, top-down narrative +- Names: intention-revealing, pronounceable, one concept per term +- No boolean flags, no output parameters, no hidden side effects +- Comments only for rationale/constraints, never to narrate +- Tests as production code: readable, deterministic, fast +- Boy Scout Rule: leave touched code cleaner + +### A Philosophy of Software Design (Ousterhout) +Deep modules, information hiding, complexity reduction. Corrects: "familiar patterns = simple." +- Deep modules: small interface, meaningful hidden complexity. Reject thin wrappers. +- Pull complexity downward into the module that owns the detail +- Design interfaces around what callers need to know, not how impl works +- Reduce exception surface via stronger invariants. Define away invalid states. +- Comments document design decisions and hidden complexity +- Names, consistency, and obviousness ARE design information + +### Clean Architecture (Martin) +Business rules independent of frameworks/databases/UI. Corrects: "details = architecture." +- Dependencies point inward. Domain never imports frameworks, DB, UI, vendors. +- Entities guard enterprise invariants; Use Cases orchestrate one action +- Frameworks, DB, delivery = outer-layer details behind Ports/Adapters +- Inner layers OWn interfaces; outer layers IMPLEMENT +- Use cases are not merged by sharing; duplication from different actors stays +- Core tests run without real DB, network, framework, or hardware + +### Code Complete (McConnell) +Construction discipline, defect reduction, verifiable code. Corrects: "typing = construction." +- Sketch pseudocode at consistent abstraction before complex routines +- Input validation at every trust boundary. Assertions for programmer assumptions. +- Handle errors at the right abstraction. Never silently continue from corruption. +- Rising complexity IS defect risk. Split tangled routines. +- Build in small, verifiable increments. Integrate often. +- Comments explain intent, constraints, contracts — not mechanics. + +### Domain-Driven Design (Evans) + DDD Distilled (Vernon) +Ubiquitous language, bounded contexts, tactical patterns. Corrects: "model = data schema." +- Name the Bounded Context before interpreting any term or module +- One term per concept within the context. Code speaks Ubiquitous Language. +- Aggregates: small, one root, invariant-protected, one per transaction default +- Value Objects: immutable, validated at construction, compare by value +- Repositories return domain objects, not tables or ORM rows +- Domain Events: past-tense business facts, not property-change notifications +- Anti-corruption layer at every context boundary +- Core Domain gets richer modeling; supporting subdomains stay simpler + +### Implementing DDD (Vernon) +Practical DDD: aggregates, events, services, persistence. Corrects: "renamed CRUD = DDD." +- Reference other Aggregates by identity, not by object graph +- Domain Services for operations that fit no Entity or Value Object +- Application Services coordinate use cases — they don't own domain decisions +- CQRS when consistency or representation needs justify separate models +- Event Sourcing only when the event sequence IS the right persistence model +- Test invariants, valid/invalid state transitions, and events directly + +### Patterns of Enterprise Application Architecture (Fowler) +Layering, patterns for enterprise apps. Corrects: "more patterns = better design." +- Choose business logic pattern by force: Transaction Script → Table Module → Domain Model +- Service Layer for use-case coordination and transaction boundaries +- Repository speaks domain terms; Data Mapper keeps SQL out of domain objects +- Unit of Work for one logical commit; Identity Map for one identity per scope +- Remote Facade + DTOs at cross-layer boundaries — never leak domain internals +- Session state chosen deliberately: client, server, or DB with scaling accounted for + +### Refactoring (Fowler) +Behavior-preserving structural improvement. Corrects: "cleanup = rewrite." +- Preserve observable behavior. Isolate behavior change from structural change. +- Small, reversible, testable steps. Safety net before risky work. +- Preparatory refactoring: reshape blocking structure BEFORE the feature +- Targeted at the current blocking smell, not every smell in sight +- Simplest named move: rename, extract, inline, move, encapsulate, substitute +- Stop when the requested change is easy and the blocking smell is gone + +### Working Effectively with Legacy Code (Feathers) +Safe change in untested code. Corrects: "rewrite = first move." +- Legacy = code without trustworthy tests. Characterize before redesign. +- Find or create a seam: place to change behavior without editing surrounding code +- Break the ONE blocking dependency before making the change +- Sprout Method, Sprout Class, Wrap Method, Wrap Class for insertion +- Leave the area more testable than found +- Reject: hidden dependency expansion, cosmetic-only refactoring, big rewrites + +### Designing Data-Intensive Applications (Kleppmann) +Distributed data, consistency, events, replication. Corrects: "everything is local/ordered/exactly-once." +- Source of truth, derived representations, and consistency expectations must be EXPLICIT +- Treat crashes, partial writes, duplicates, and timeouts as normal input +- Write semantics: durable when? visible when? conflicts how? stale reads allowed? +- Events describe facts. Consumers tolerate lag, duplicates, replay, versioned payloads. +- Schemas, APIs, and events evolve across old/new readers/writers +- Partition by workload-relevant locality; make hot-key and cross-partition costs explicit +- Transactions and isolation matched to actual invariants, not blanket defaults + +### Release It! (Nygard) +Production reliability, stability patterns. Corrects: "happy path = production readiness." +- Timeouts on every outbound call. No infinite waits. Bounded retries with backoff. +- Circuit breakers, bulkheads, fast failure to isolate dependency failures +- Design overload behavior: finite queues, load shedding, capacity for critical traffic +- Startup, health checks, migrations, and operational controls: restartable, observable +- Validate external responses for shape, plausibility, and semantics before trusting +- Observability at every boundary: latency, saturation, errors, queue depth, breaker state + +### The Pragmatic Programmer (Hunt & Thomas) +Engineering craft, accountability, automation. Corrects: "local edit = done." +- One authoritative source per fact. Everything else derives or traces. +- Preserve orthogonality: independent components, narrow interfaces, separated concerns +- Tracer bullets over piles of isolated pieces. Validate architecture end-to-end early. +- Automate repetitive, error-prone, easy-to-forget work +- Shorten feedback loops: relevant tests, automated checks, cheap early signals +- Broken windows: fix or visibly contain small quality decay before it normalizes +- Debug from reproduced facts: observe, isolate, explain, fix, verify + +### Refactoring.Guru +Smell catalog and technique catalog. Corrects: "pattern = always the answer." +- Diagnose the smell before choosing the technique +- Prefer the simplest treatment: rename before extract, extract before redesign +- Each smell has a specific root cause and treatment path +- See `references/refactoring-guru-smells.md` for the full catalog + +## Compatibility Guide + +Books that CONFLICT (do not load as equal guidance): +- DDD ❌ PoEAA — different data ownership paradigms +- IDDD ❌ PoEAA — same conflict at implementation level + +Books that OVERLAP (choose one, they push similar pressure): +- Clean Code 🔁 Pragmatic Programmer, Code Complete, APoSD — code quality +- DDD 🔁 DDD Distilled, IDDD — DDD at different depths; pick the level you need +- Clean Architecture 🔁 IDDD, PoEAA — architecture/layering overlap +- Refactoring 🔁 Refactoring.Guru — code improvement; choose Refactoring for strategy, Guru for catalog + +All other pairs are complementary. Default: one primary always-on book, others +loaded on-demand per task. + +## Local Reference Files + +Each book's mini rule set is available as a local reference file under +`references/`. Load any with: + +``` +skill_view(name='programming-principles', file_path='references/{book-dir}.mini.md') +``` + +| File | Book | +|------|------| +| `references/a-philosophy-of-software-design.mini.md` | A Philosophy of Software Design | +| `references/clean-architecture.mini.md` | Clean Architecture | +| `references/clean-code.mini.md` | Clean Code | +| `references/code-complete.mini.md` | Code Complete | +| `references/designing-data-intensive-apps.mini.md` | Designing Data-Intensive Applications | +| `references/domain-driven-design.mini.md` | Domain-Driven Design | +| `references/domain-driven-design-distilled.mini.md` | DDD Distilled | +| `references/implementing-domain-driven-design.mini.md` | Implementing DDD | +| `references/patterns-of-eaa.mini.md` | Patterns of Enterprise App Architecture | +| `references/refactoring.mini.md` | Refactoring | +| `references/refactoring-guru.mini.md` | Refactoring.Guru | +| `references/release-it.mini.md` | Release It! | +| `references/the-pragmatic-programmer.mini.md` | The Pragmatic Programmer | +| `references/working-effectively-with-legacy-code.mini.md` | Working Effectively with Legacy Code | +| `references/code-assessment-workflow.md` | Assessment methodology — not a book, but the workflow for combining all books against a real repo | + +Each book also has a **full** version (11-63 KB) for deep reference when you need +the complete rule catalog. Load on demand: + +``` +skill_view(name='programming-principles', file_path='references/{name}.full.md') +``` + +| Full File | Book | Size | +|-----------|------|------| +| `references/a-philosophy-of-software-design.full.md` | A Philosophy of Software Design | 13 KB | +| `references/clean-architecture.full.md` | Clean Architecture | 17 KB | +| `references/clean-code.full.md` | Clean Code | 13 KB | +| `references/code-complete.full.md` | Code Complete | 12 KB | +| `references/designing-data-intensive-apps.full.md` | Designing Data-Intensive Applications | 16 KB | +| `references/domain-driven-design.full.md` | Domain-Driven Design | 42 KB | +| `references/domain-driven-design-distilled.full.md` | DDD Distilled | 11 KB | +| `references/implementing-domain-driven-design.full.md` | Implementing DDD | 12 KB | +| `references/patterns-of-eaa.full.md` | Patterns of Enterprise App Architecture | 15 KB | +| `references/refactoring.full.md` | Refactoring | 17 KB | +| `references/refactoring-guru.full.md` | Refactoring.Guru | 62 KB | +| `references/release-it.full.md` | Release It! | 13 KB | +| `references/the-pragmatic-programmer.full.md` | The Pragmatic Programmer | 13 KB | +| `references/working-effectively-with-legacy-code.full.md` | Working Effectively with Legacy Code | 13 KB | + +Progressive disclosure pattern: load the **mini** file for daily guidance (triggers, +decision rules, final checklist). Load the **full** file only for deep sessions, +audits, or when you need the complete rule catalog with code-smell indexes and +technique references. + +## Known Weaknesses (from repo criticism) + +- No empirical measurement of improvement — these are principle-based, not + benchmarked. Apply judgment about whether rules improve actual outcomes. +- Loading too many rule sets at once causes context saturation. Use at most one + primary always-on set + one task-specific on-demand set. +- Rules are book-derived, not incident-derived. The highest-value agent rules + come from real failures, not theory. +- Risk of pseudo-compliance: agent follows the letter of rules while missing + the actual task. Test outputs against real requirements, not rule conformity. diff --git a/programming-principles/references/a-philosophy-of-software-design.full.md b/programming-principles/references/a-philosophy-of-software-design.full.md new file mode 100644 index 0000000..02d9d54 --- /dev/null +++ b/programming-principles/references/a-philosophy-of-software-design.full.md @@ -0,0 +1,370 @@ +# OBEY A Philosophy of Software Design by John Ousterhout + +## Purpose + +This repository follows **A Philosophy of Software Design** in the sense of John Ousterhout: +fight complexity directly by designing modules with deep value, clean interfaces, strong information hiding, and low cognitive load. + +All code generation, edits, and reviews must optimize for: +- lower complexity +- deeper modules +- simpler interfaces +- stronger information hiding +- fewer special cases +- clear separation between interface and implementation +- strategic design over tactical patching + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Complexity is anything that makes software hard to understand or hard to change. + +When uncertain, prefer the design that: +1. reduces the number of things a reader must know at once +2. hides more details behind a stable interface +3. eliminates exceptions and awkward cases +4. creates a deeper module instead of a shallower one +5. lowers future cognitive load, not just present typing effort + +Do not optimize for shorter files, fewer lines, or clever compactness if complexity rises. + +--- + +## Core Complexity Rules + +### Symptoms of Complexity +Treat these as architectural warnings: +- change amplification +- cognitive load +- unknown unknowns +- hidden dependencies +- information spread across many places +- temporal coupling that readers must reconstruct mentally + +### Default Response +When a feature feels awkward, first ask: +- is the interface too wide? +- is the behavior scattered? +- are details leaking that should be hidden? +- are there too many special cases? +- are we solving a tactical local problem while increasing global complexity? + +--- + +## Module Depth Rules + +### Prefer Deep Modules +A deep module offers a simple interface but hides substantial complexity behind it. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Design modules to hide meaningful internal complexity. +2. Prefer a small interface with strong semantics over a large surface with minor helpers. +3. Make each module carry its own weight. +4. A module that only forwards work is usually too shallow. + +### Avoid Shallow Modules +Shallow modules are bad when: +- the interface exposes almost as much complexity as the implementation +- layers exist mostly to redirect calls +- small wrappers multiply concepts without reducing reader burden + +Anti-patterns (MUST NOT): +- pass-through service classes +- thin wrappers around libraries with no simplification +- helper modules that only rename obvious operations + +--- + +## Information Hiding Rules + +1. Hide design decisions that are likely to change. +2. Hide internal data representations. +3. Hide incidental workflow steps and bookkeeping. +4. Keep callers from depending on implementation detail, performance hacks, or storage shape. +5. Encapsulate messy edge conditions and normalization logic. + +Anti-patterns (MUST NOT): +- exposing internal representation or state through module interfaces +- leaking storage, protocol, framework, or file-format details through module interfaces +- callers coordinating object internals across multiple modules + +--- + +## Interface Design Rules + +1. Design interfaces around what clients need to know, not how the implementation works. +2. Keep interfaces narrow but meaningful. +3. Avoid APIs that require callers to stage operations in fragile sequences. +4. Eliminate arguments that only exist to expose internal implementation choices. +5. Use names and method shapes that reveal the abstraction, not the mechanism. + +Good interface signs (SHOULD): +- few methods +- strong semantic guarantees +- limited required context +- callers do not need to understand internals + +Bad interface signs (warning; usually MUST NOT): +- many configuration flags +- call-order traps +- multiple setup methods required before use +- booleans and mode parameters switching internal behavior + +--- + +## Strategic Programming over Tactical Programming + +### Strategic Programming +1. Spend time reducing future complexity, not only making the current change pass. +2. Reshape abstractions when recurring friction appears. +3. Invest in interfaces and decomposition that make future changes local. +4. Leave behind clearer structure after every substantial edit. + +### Tactical Programming +Avoid: +- patching local symptoms while increasing global complexity +- copy/paste to meet a deadline +- exposing one more internal detail instead of designing a better boundary +- adding flags and exceptions to avoid a better abstraction + +--- + +## General-Purpose vs Special-Purpose Modules + +1. Prefer modules that capture a reusable concept at the right abstraction level. +2. Do not overfit interfaces to one narrow caller if a slightly more general concept is obvious. +3. Do not generalize so far that the abstraction becomes vague. +4. The best module is specific enough to be strong and general enough to be reusable within its domain. + +--- + +## Error Handling and Exception Elimination + +1. Define APIs that make misuse hard. +2. Define away invalid states and awkward cases where possible. +3. Eliminate exception cases by changing the interface or invariant, not only by adding more checks. +4. Use special/general decomposition when unusual cases clutter the main abstraction. +5. Keep the normal path obvious and the exceptional path isolated. + +Anti-patterns (MUST NOT): +- APIs that require every caller to repeat defensive ceremony +- “special case” branches scattered across many call sites +- exposing half-valid objects and asking callers to tiptoe around them + +--- + +## Pull Complexity Downward + +1. Put complexity in one place rather than many. +2. Hide intricate logic behind a simpler public contract. +3. Prefer a slightly more complex implementation if it makes all callers simpler. +4. Remove repeated reasoning burdens from call sites. + +This is the opposite of pushing complexity outward through flags, setup steps, and coupled operations. + +--- + +## Comment Rules + +Comments must reduce complexity, not narrate obvious code. + +Use comments for: +- interface contracts +- non-obvious invariants +- hidden design decisions +- the reason an abstraction exists +- tricky implementation facts callers do not need to know + +Do not use comments to compensate for: +- bad naming +- poor decomposition +- confusing control flow +- missing abstraction + +--- + +## Function and Variable Rules + +1. Keep functions deep enough to hide a meaningful amount of work. +2. Avoid long functions only when they create cognitive load, not as a numeric ritual. +3. Avoid pass-through variables that do not add meaning. +4. Use variables to capture meaning, not to mirror syntax. +5. Keep local details local. + +Anti-patterns (MUST NOT): +- chains of tiny functions where readers must jump constantly to understand one idea +- variables introduced only to satisfy style rather than clarity +- exposing intermediate states that should stay internal + +--- + +## Temporal Decomposition Rules + +1. Do not structure modules primarily around execution order if the real structure is conceptual. +2. Prefer decomposition around stable concepts and responsibilities. +3. Initialization steps, processing phases, and cleanup stages should not force readers to reconstruct the design from time-order alone. +4. Keep call ordering simple and explicit where it matters. + +Anti-patterns (MUST NOT): +- `prepare/process/finalize` everywhere without domain concepts +- APIs that require secret temporal knowledge +- partial objects whose meaning depends on which phase has already run + +--- + +## Special-General Decomposition + +Use special-general decomposition when a small number of exceptions are cluttering the main logic. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Keep the general case simple. +2. Isolate the unusual or rare behavior. +3. Do not pollute the main abstraction with every edge case. + +This is preferable to accreting conditionals into the core path forever. + +--- + +## Combine or Separate Code + +1. Separate code only when the separation reduces complexity, hides a real design decision, or creates a stronger abstraction. +2. Combine code when split pieces force readers to jump between shallow fragments to understand one idea. +3. Keep related state, behavior, and invariants together when separating them would create change amplification. +4. Do not preserve a boundary merely because it already exists if it exposes almost as much complexity as it hides. +5. Prefer one coherent deeper module over several tiny modules that require callers to coordinate details. + +Anti-patterns (MUST NOT): +- splitting code by execution phase when the stable concept is not temporal +- separating normal and special cases so far apart that their shared invariant is hidden +- adding helper layers that distribute one design decision across many files + +--- + +## Design Alternatives and Comments-First Design + +1. For non-trivial design choices, compare at least two plausible designs before implementing the first one that works. +2. Evaluate alternatives by interface simplicity, information hiding, special-case reduction, and future cognitive load. +3. When an interface or abstraction is unclear, sketch the public contract and explanatory comments before committing to implementation. +4. Use comments-first design to clarify abstraction boundaries, not to preserve vague or misleading code. +5. Revise the abstraction when the comment needed to explain it becomes complicated. + +Anti-patterns (MUST NOT): +- accepting the first design because it is locally convenient +- using comments to justify a confusing interface instead of changing the interface +- documenting implementation mechanics that callers should not need to know + +--- + +## Naming, Consistency, and Obviousness + +1. Names must reveal the abstraction, not the internal mechanism. +2. Use consistent names, argument order, error behavior, and interface conventions for related operations. +3. Prefer obvious code where a reader can infer behavior from local structure and names. +4. Remove non-obvious behavior unless it is hidden behind a clear contract. +5. When code surprises a reader, treat that as complexity even if the code is short. + +--- + +## Performance, Trends, and Tests + +1. Do not sacrifice module depth or information hiding for performance without evidence that the tradeoff matters. +2. When performance matters, hide optimization details behind stable interfaces so callers do not inherit the complexity. +3. Prefer measurements and targeted changes over broad speculative tuning. +4. Do not adopt a trend, paradigm, pattern, or framework unless it reduces complexity in this codebase. +5. Use tests to preserve behavior while changing structure, but do not let test convenience force shallow or leaky interfaces. + +--- + +## Review Rules + +When reviewing code, actively look for: +- shallow modules +- pass-through layers +- interfaces that expose implementation detail +- excessive flags or mode parameters +- scattered special cases +- cognitive load caused by too many interacting modules +- hidden temporal constraints +- complexity pushed to callers +- comments that explain what should be encoded in structure +- tactical patches that increase future difficulty + +--- + +## Forbidden Patterns + +### Shallow Decomposition +- splitting code into many tiny units that do not reduce understanding cost +- wrappers and facades that add names but not simplification + +### Interface Leakage +- APIs that expose storage, transport, or caching mechanics to ordinary callers +- method sequences that require callers to know internal workflow + +### Tactical Complexity Debt +- adding one more flag, callback, or conditional instead of improving the abstraction +- fixing the local symptom while making the design harder overall + +### Complexity Spread +- repeating the same special handling in many places +- making all callers responsible for one module's awkwardness + +--- + +## Code Generation Rules + +When generating code, default to: +1. identify the concept that deserves a module boundary +2. design the narrowest strong interface around that concept +3. hide volatile or complicated details inside +4. simplify all callers, even if implementation grows slightly +5. isolate special cases +6. reduce the number of facts a reader must juggle at once + +Avoid by default: +- pass-through layers +- needless tiny abstractions +- exposing internal data formats +- temporal APIs with fragile call sequences +- multiplying concepts without reducing complexity + +--- + +## Testing Rules + +1. Test public behavior and interface contracts. +2. Test hidden complexity through stable public APIs where possible. +3. Add focused tests around isolated special cases. +4. Avoid tests that force callers to know implementation detail if the abstraction promises otherwise. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Did this change reduce or increase cognitive load? +- Is the module deeper or shallower after the edit? +- Did we hide more complexity behind a stable interface? +- Did we reduce special-case handling at call sites? +- Did we remove an implementation detail from the public surface? +- Did we avoid a pass-through layer? +- Does the interface describe the abstraction rather than the mechanism? +- Did we improve future changeability, not just present convenience? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, prefer the design that: +1. creates a deeper module +2. hides more complexity +3. reduces special cases +4. lowers cognitive load for callers +5. improves the abstraction instead of patching around it + +Fight complexity directly. diff --git a/programming-principles/references/a-philosophy-of-software-design.mini.md b/programming-principles/references/a-philosophy-of-software-design.mini.md new file mode 100644 index 0000000..a7d8838 --- /dev/null +++ b/programming-principles/references/a-philosophy-of-software-design.mini.md @@ -0,0 +1,46 @@ +# OBEY A Philosophy of Software Design by John Ousterhout + +## When to use + +Use for module design, API changes, decomposition, refactoring, naming, comments, tests, performance work, and changes that feel awkward or spread complexity across files. + +## Primary bias to correct + +Working code, small pieces, familiar patterns, flags, wrappers, and extra documentation do not make a design simple when they increase cognitive load or leak knowledge. + +## Decision rules + +- Use reduced complexity as the primary success metric. Prefer the design that lowers cognitive load, change amplification, hidden dependencies, temporal coupling, and the number of facts a reader must hold at once. +- Treat design as continuous work. A first working patch is not done if it worsens future changeability; compare plausible alternatives for non-trivial interface, decomposition, or abstraction choices. +- Prefer deep modules: small, semantic interfaces that hide meaningful internal complexity. Reject pass-through services, thin library wrappers, helper modules, and tiny split-outs that add names without reducing reader burden. +- Design interfaces around what callers need to know, not how the implementation works. Avoid fragile staging, setup sequences, mode flags, configuration knobs, and arguments that expose internal choices. +- Hide volatile decisions, internal representations, storage shape, protocols, file formats, performance hacks, bookkeeping, normalization, and messy edge handling inside the module that owns the knowledge. +- Pull complexity downward when the lower module owns the detail. Prefer a slightly more complex implementation if it gives callers a simpler public contract and removes repeated reasoning from call sites. +- Choose generality at the right level. Avoid one-caller overfitting, vague speculative abstractions, and core paths polluted by rare edge cases; isolate special behavior with special-general decomposition. +- Combine or split by total complexity, not by size, runtime order, habit, or aesthetics. Keep related state, behavior, invariants, and design decisions together unless the new boundary is deeper and independently understandable. +- Reduce exception surface by changing interfaces or invariants where possible. Define away invalid states and awkward cases instead of making every caller repeat defensive ceremony. +- Use comments to reduce complexity: document interface contracts, invariants, hidden design decisions, rationale, and tricky implementation facts callers should not need to know. Do not narrate code or compensate for bad names, poor decomposition, or confusing flow. +- Treat names, consistency, and obviousness as design information. Names should reveal abstractions rather than mechanisms; related operations should share conventions; surprising code is complexity even when short. +- Use tests to protect behavior through public contracts and stable APIs, especially around hidden complexity and isolated special cases. Do not let test convenience force shallow or leaky interfaces. +- Add performance optimizations, trends, paradigms, patterns, or frameworks only when they reduce complexity in this codebase or evidence shows the tradeoff matters; hide optimization details behind stable interfaces. + +## Trigger rules + +- When a feature feels awkward, one change spreads across files, or reviewers must reconstruct hidden dependencies, look for missing information hiding, shallow modules, temporal coupling, or complexity pushed to callers. +- When adding a module, layer, service, helper, wrapper, facade, pattern, option, callback, or argument, prove that it hides more complexity than it adds. +- When touching an API, check whether ordinary callers must know sequencing, representation, storage, transport, caching, protocol, file format, internal workflow, or too many setup steps. +- When adding a special case, flag, exception path, conditional, or exposed container, first ask whether the owning module can eliminate the invalid state, isolate the unusual behavior, or provide a stronger operation. +- When splitting, extracting, or introducing variables, check whether the new boundary or name captures meaning or only adds jumps, pass-through state, and visible intermediate steps. +- When code is organized as `prepare/process/finalize`, staged objects, or other execution-order phases, verify that temporal structure is the real concept; otherwise reorganize around stable responsibilities. +- When naming is vague, mechanism-focused, inconsistent, or surprising, reconsider the abstraction boundary instead of accepting a near miss. +- When comments get long, duplicate code, justify a confusing interface, or explain usage by exposing internals, redesign the abstraction or move the missing contract to the interface. +- When optimizing performance, measure first and hide the optimization; do not sacrifice module depth or information hiding without evidence that the tradeoff matters. +- When testing or reviewing, focus on public behavior, interface contracts, hidden complexity through stable APIs, and special cases isolated behind the abstraction. + +## Final checklist + +- Did the change reduce the effort required to understand, modify, verify, and extend the system? +- Does every interface element, wrapper, layer, helper, option, and name hide enough complexity to justify its existence? +- Are important decisions localized, dependencies visible, caller-needed constraints documented, and mutable internals protected? +- Did common cases become automatic while rare controls, special cases, performance tricks, and exception details stayed out of the common path? +- Are names precise and consistent, comments current and non-duplicative, and conventions followed unless new information justified changing them? diff --git a/programming-principles/references/clean-architecture.full.md b/programming-principles/references/clean-architecture.full.md new file mode 100644 index 0000000..8382adb --- /dev/null +++ b/programming-principles/references/clean-architecture.full.md @@ -0,0 +1,515 @@ +# OBEY Clean Architecture by Robert C. Martin + +## Purpose + +This repository must follow **Clean Architecture**. +When writing, modifying, or reviewing code, prefer decisions that preserve: +- independent business rules +- inward-pointing dependencies +- framework independence +- database independence +- UI independence +- testability +- replaceable details + +Treat this file as a binding implementation policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Non-Negotiable Rules + +1. **Follow the Dependency Rule** + - Source code dependencies must point inward, toward higher-level policies. + - Inner layers must not import or depend on outer layers. + - Business rules must not depend on frameworks, web handlers, database drivers, UI libraries, queues, external services, or other details. + +2. **Keep Business Rules Pure** + - Entities and use cases must contain business policy. + - Business rules must not read web requests, environment variables, framework context, database-bound structures, or database rows directly. + - Pass plain data into use cases through request models or arguments. + +3. **Treat Frameworks as Details** + - Frameworks are tools, not the foundation of the design. + - Keep framework annotations, decorators, controllers, routes, middleware, serializers, and database artifacts at the edges. + - Do not let framework types leak into core policies. + +4. **Treat the Database as a Detail** + - Do not shape the domain model around tables. + - Use gateways to isolate persistence. + - Business rules must work without a real database. + +5. **Treat the Web as a Detail** + - Controllers and endpoints translate delivery input into input models for use cases. + - Use cases must not know about web transport, status codes, cookies, headers, or routing. + - Presenters or response mappers translate use case output for delivery mechanisms. + +6. **Use Explicit Boundaries** + - Define interfaces at architectural seams. + - External systems, persistence, messaging, file systems, clocks, and service clients must sit behind boundaries. + - Prefer adapters over direct calls from policy code to implementation details. + +7. **Organize by Use Case** + - Prefer feature and use-case oriented structure over generic technical buckets. + - The architecture should scream the domain and application intent. + - Avoid codebases dominated by generic technical buckets that do not reveal use cases or business purpose. + +8. **Use Cases Must Orchestrate** + - A use case coordinates entities and gateways. + - A use case should not contain delivery concerns, database concerns, or presentation formatting concerns. + - A use case should represent one application action. + +9. **Entities Must Guard Invariants** + - Critical domain rules belong in entities or equivalent domain objects. + - Entities must protect invariants and consistency. + - Do not leave core rules in controllers, jobs, handlers, or database scripts. + +10. **Outer Layers May Depend on Inner Layers, Never the Reverse** + - Controllers may depend on use cases. + - Gateways may implement interfaces defined by the use case or domain layer. + - Presenters may implement output boundaries owned by inner layers. + - Never invert this relationship accidentally. + +--- + +## Required Layer Responsibilities + +### Domain Layer +Contains: +- entities +- enterprise business rules +- domain invariants +- core business rules + +These may be implemented with plain objects, functions, modules, or other structures. Clean Architecture requires independent business rules; it does not require a specific domain modeling style. + +Must: +- be framework free +- be persistence ignorant +- be delivery mechanism agnostic +- avoid annotations and infrastructure imports where possible + +Must not: +- import web libraries +- import database access types +- import external service clients +- perform I/O +- read configuration directly + +### Application Layer +Contains: +- use cases +- input models +- output models +- ports and boundaries +- orchestration logic + +Must: +- depend on domain abstractions and models +- define interfaces for required external behavior +- coordinate workflows explicitly + +Must not: +- contain controller logic +- contain database access details +- return framework response types +- format UI strings unless explicitly part of a presenter boundary + +### Interface Adapters Layer +Contains: +- controllers +- presenters +- view models +- gateway adapters +- mappers between external and internal models + +Must: +- translate between external formats and internal models +- depend inward on application and domain code +- isolate framework and vendor details + +Must not: +- move business policy out of the use case or domain layer +- bypass use cases to call gateways directly unless explicitly justified by architecture + +### Infrastructure Layer +Contains: +- framework bootstrap +- object graph and component wiring +- database access details +- external service integrations +- message bus clients +- filesystem implementations +- network clients + +Must: +- remain replaceable +- implement interfaces owned by inner layers +- stay at the outermost edge + +Must not: +- define business rules +- dictate domain shapes +- leak vendor types inward + +--- + +## Code Generation Rules + +When generating code, always apply the following. + +### 1. Define the Use Case First +For every non-trivial feature: +- identify the use case +- define the input +- define the output +- define required ports +- keep orchestration in one place + +Prefer this order: +1. domain rule or entity behavior +2. use case +3. boundary interfaces +4. presenter contract +5. gateway contract +6. adapters +7. framework wiring + +### 2. Use Plain Models at Boundaries +- Use request and response models owned by the application layer. +- Do not pass database-bound entities, web requests, or framework-bound data structures into core logic. +- Do not return framework objects from use cases. + +### 3. Create Ports for Volatile Dependencies +Introduce interfaces for: +- gateways +- mailers +- payment providers +- message publishers +- storage providers +- clocks +- ID generators +- transaction runners if needed + +Do not call volatile details directly from core use cases. + +### 4. Keep Wiring in the Main Component +- Object construction belongs in the composition root. +- Do not instantiate infrastructure dependencies inside use cases or entities. +- Use explicit construction, factories, or composition in the outer layer. + +### 5. Prefer Stable Dependencies +- Inner layers own the abstractions they need. +- Outer layers implement those abstractions. +- Avoid shared "common" packages that create sideways coupling. + +### 6. Keep Boundaries Visible +- When in doubt, introduce a boundary sooner. +- Partial boundaries are acceptable if they preserve future extraction options. +- Use interfaces, request models, and output models to avoid coupling to details. + +--- + +## Architecture Heuristics + +### Dependency Direction +Always verify: +- Does this import point inward? +- Is a high-level policy depending on a low-level detail? +- Is a framework or vendor type leaking into a core layer? +- Is an adapter bypassing the intended boundary? + +If yes, refactor. + +### Policy vs Detail +When placing code, ask: +- Is this business policy? +- Is this orchestration? +- Is this translation? +- Is this infrastructure? + +Put the code in the highest-level place that matches its responsibility. + +### Stable Core, Replaceable Edge +Prefer designs where you can replace: +- web framework +- persistence technology +- message broker +- job runner +- cloud vendor +- serializer +- UI +without rewriting business rules. + +### Feature First Structure +Prefer: +- feature/use-case names +- business-capability/use-case names +- names that reveal the application's use cases + +Over: +- generic controller, service, or gateway buckets +- generic technical buckets + +Technical subfolders are acceptable only when they do not obscure use-case ownership. + +--- + +## Architecture Economics and Priority + +1. Treat architecture as a way to keep future change cost proportional to the scope of change. +2. Do not sacrifice important architectural work merely because urgent feature work is louder. +3. Preserve options around frameworks, databases, delivery mechanisms, and deployment topology until evidence justifies commitment. +4. Choose boundaries by volatility, policy importance, substitution value, testability, and cost. +5. Do not overbuild boundaries whose cost exceeds the option value they preserve. +6. Revisit architecture when change shape, team ownership, deployment needs, or operational constraints reveal rising cost. + +--- + +## Paradigm and Component Rules + +1. Use structured programming to make behavior decomposable and testable. +2. Use polymorphism to invert dependencies when high-level policy must not know low-level details. +3. Use immutability or controlled mutation when it protects policy from accidental state coupling. +4. Apply SRP by separating code that changes for different actors or reasons. +5. Apply the Open-Closed Principle by protecting stable policy from volatile extension details. +6. Apply LSP by ensuring substitutable implementations preserve caller expectations. +7. Apply ISP by keeping interfaces focused on what each client actually needs. +8. Apply DIP by making source dependencies point toward stable policy and abstractions. +9. Group components by cohesion and release pressure; do not group unrelated policy just because it shares a technical layer. +10. Avoid component cycles; break cycles before they harden into deployment or test bottlenecks. +11. Balance stability and abstraction: stable components should not depend on unstable details, and abstract components should have concrete reason to exist. + +--- + +## Boundary Cost, Deployment, and Operations + +1. A boundary may be a source boundary, deployment boundary, process boundary, service boundary, or partial boundary. +2. Choose the lightest boundary that preserves the needed independence. +3. Use partial boundaries when a full deployment/runtime split is too expensive but future separation is valuable. +4. Keep development, deployment, operation, and maintenance concerns visible without letting them own business policy. +5. Do not combine unrelated use cases just because operational wiring is easier. +6. Do not eliminate duplication when the shared code would couple use cases that change for different actors. +7. Make architectural boundaries enforceable through package structure, tests, dependency rules, or build constraints. + +--- + +## Services, Distribution, and Embedded Boundaries + +1. A service is not automatically an architectural boundary; source dependencies and data ownership still decide coupling. +2. Remote calls must be treated as I/O boundaries, not as local method calls. +3. Keep service listeners humble: translate external messages into use case calls and return through output boundaries. +4. Keep embedded and hardware details behind interfaces so policy can be tested without the target device. +5. Do not let real-time, firmware, database, web, or framework concerns pull policy outward. + +--- + +## Naming Rules + +- Name modules and packages after business capabilities or use cases. +- Name use cases with action verbs from the application's use cases. +- Name ports by the role they play for the use case. +- Name adapters by the external detail or delivery mechanism they adapt. +- Avoid vague technical names when a use case, policy, boundary, presenter, controller, gateway, or entity role is more precise. +- If a class is named `Service`, justify why it is not a use case, adapter, or domain object. + +--- + +## Testing Rules + +### Core Tests First +Prioritize tests for: +- entities +- use cases +- boundary contracts + +These tests must: +- run without the real framework +- run without the real database +- run without the network +- run fast and deterministically + +### Adapter Tests +Test adapters separately for: +- mapping correctness +- gateway behavior +- controller translation +- presenter formatting +- integration with framework or external service + +Do not use slow integration tests as a substitute for testing business rules. + +### Test Through Supported Boundaries +- Avoid reaching private internals when a public use case boundary exists. +- Prefer testing use cases with fakes or mocks for ports. +- Use integration tests only where architectural seams meet real details. + +--- + +## Forbidden Patterns + +Do not generate or keep code that does any of the following unless explicitly required and justified. + +### Framework Leakage +- domain entities annotated with database or web framework metadata when avoidable +- use cases depending on `Request`, `Response`, controller base classes, framework sessions, or middleware objects +- application layer importing serializer or database base classes + +### Database Leakage +- use cases returning table rows or database-bound entities +- domain rules embedded in gateway implementations or database access +- domain objects designed primarily around persistence convenience + +### Controller-Centric Logic +- controllers containing branching business rules +- controllers performing validation that belongs to business policy +- controllers calling gateways directly instead of use cases + +### God Services +- large `*Service` classes that create, fetch, validate, persist, publish, and present everything +- services that own unrelated use cases +- application services that become dumping grounds + +### Layer Bypass +- controllers bypassing use cases to call gateways +- presenters reading directly from databases +- infrastructure code importing inward and also being imported by domain code + +### Direction Violations +- gateway interfaces defined in infrastructure and consumed by core policy +- entities importing adapters +- use cases depending on concrete implementations + +### Utility Dumping Grounds +- generic utility, shared, base, or core folders used as architecture escape hatches +- generic abstractions with no clear ownership +- convenience modules that hide bad dependency direction + +--- + +## Refactoring Rules + +When modifying existing code: + +1. **Move business rules inward** + - Extract domain logic from controllers, handlers, views, gateway classes, and jobs. + +2. **Introduce boundaries around details** + - Wrap external services, database access, message buses, filesystem operations, and clocks. + +3. **Replace concrete dependencies with ports** + - Define interfaces in inner layers. + - Implement them in outer layers. + +4. **Separate translation from policy** + - Request parsing, data mapping, serialization, and presentation formatting belong outside core business rules. + +5. **Break up god services** + - Split by use case. + - Give each use case one clear application action. + +6. **Eliminate framework coupling from tests** + - Rewrite tests to target use cases and entities directly where possible. + +7. **Preserve behavior while improving direction** + - Refactor incrementally. + - Prefer safe boundary extraction over large rewrites. + +--- + +## Output Expectations + +When asked to implement a feature, default to producing: +- a domain model or entity if business invariants exist +- a focused use case +- input and output models if needed +- ports/interfaces for external dependencies +- adapters for web, persistence, or messaging details +- composition root wiring outside the use case + +When asked to modify existing code: +- keep or improve dependency direction +- avoid adding framework dependencies to inner layers +- call out architectural debt explicitly if it cannot be fixed safely now + +When asked to review code: +- identify boundary violations +- identify dependency rule violations +- identify framework leakage +- identify misplaced business rules +- identify god services and layer bypass +- propose concrete refactorings toward Clean Architecture + +--- + +## Review Checklist + +Before finalizing any change, verify: + +- Are business rules independent from frameworks? +- Are use cases independent from delivery and persistence details? +- Do source dependencies point inward? +- Are controllers thin? +- Are gateways just persistence adapters? +- Are entities guarding domain invariants? +- Are ports owned by inner layers? +- Is composition happening at the edge? +- Can core tests run without the web framework and database? +- Does the project structure reflect the domain and use cases? +- Did we avoid generic utility dumping grounds? +- Did we avoid creating another god service? +- Did we keep details replaceable? + +If any answer is no, revise the design before shipping. + +--- + +## Preferred Default Shapes + +### Preferred feature shape +- domain +- application +- adapters +- infrastructure + +Or, if feature-oriented: +- feature/domain +- feature/application +- feature/adapters +- feature/infrastructure + +### Preferred use case shape +- request model +- use case +- output boundary or response model +- ports +- adapter implementations outside + +### Preferred dependency pattern +- inner layer defines interface +- outer layer implements interface +- composition root wires them together + +--- + +## When Tradeoffs Are Necessary + +If constraints force a compromise: +- keep the compromise at the outermost layer possible +- document the boundary violation clearly in code comments or review notes +- avoid normalizing the compromise into the core architecture +- preserve a future path to separation + +Choose the design that minimizes long-term coupling, not the one that is only shortest today. + +--- + +## Final Instruction + +When uncertain, choose the option that: +1. keeps business rules independent +2. points dependencies inward +3. isolates details behind boundaries +4. improves testability +5. makes replacement of frameworks, databases, and delivery mechanisms easier + +If a proposed change conflicts with these priorities, reject it and propose a cleaner architectural alternative. diff --git a/programming-principles/references/clean-architecture.mini.md b/programming-principles/references/clean-architecture.mini.md new file mode 100644 index 0000000..0f87919 --- /dev/null +++ b/programming-principles/references/clean-architecture.mini.md @@ -0,0 +1,49 @@ +# OBEY Clean Architecture by Robert C. Martin + +## When to use + +Use when adding, changing, reviewing, or refactoring code whose business rules should survive changes in frameworks, databases, delivery mechanisms, services, devices, vendors, deployment shape, or schedule pressure. + +## Primary bias to correct + +Do not let details become the architecture. Business policy stays independent, dependencies point inward, and volatile mechanisms remain replaceable. + +## Decision rules + +- Preserve independent business rules, inward dependencies, testability, and replaceable details even when the immediate feature would be shorter without them. +- Source dependencies must point inward toward higher-level policy. Domain and use cases must not import frameworks, databases, web handlers, queues, external service clients, UI types, or other details. +- Put enterprise rules and invariants in entities or equivalent domain objects; put application-specific orchestration in focused use cases. +- Pass plain request and response models across use-case boundaries. Do not pass web requests, framework contexts, ORM rows, database-bound structures, or framework response objects into or out of core policy. +- Treat frameworks, databases, web delivery, messaging, filesystems, clocks, service clients, networks, devices, and vendors as outer-layer details behind ports, gateways, presenters, mappers, or adapters. +- Inner layers own the interfaces they need; outer layers implement them. Object construction and concrete wiring belong in the composition root or other outer-layer main component. +- Keep adapters humble. Controllers, endpoints, presenters, gateway adapters, service listeners, and hardware adapters translate external formats to use-case calls and back; they do not own business decisions. +- Organize by use case, feature, or business capability before generic technical buckets. The structure should reveal domain intent and application actions. +- Choose boundaries by volatility, policy importance, substitution value, testability, and cost. Use the lightest enforceable boundary, including partial boundaries, when full deployment or runtime separation is too expensive. +- Do not merge unrelated use cases or eliminate duplication when sharing would couple actors, change reasons, team ownership, deployment needs, or release pressure. +- Use structured code, dependency inversion, role-sized interfaces, substitutable implementations, controlled mutation, acyclic components, and stability-directed dependencies to protect policy from volatile details. +- Enforce boundaries with package structure, dependency rules, build constraints, tests, visibility, or narrow APIs. A diagram, service split, package name, or shared `common` folder is not enough. +- Test entities, use cases, and boundary contracts first, without the real framework, database, network, external service, or target hardware. Test adapters separately at the seams. +- Preserve behavior while improving dependency direction. Prefer incremental boundary extraction over rewrites, and call out architectural debt when it cannot be fixed safely now. + +## Trigger rules + +- When urgent delivery would skip architecture, state the future change, test, replacement, or operational cost before accepting the shortcut. +- When framework annotations, request/response objects, serializers, ORM rows, schemas, vendor SDKs, config, environment reads, device registers, or transport formats enter core policy, move translation outward. +- When controllers, jobs, handlers, views, presenters, gateways, repositories, SQL, service listeners, scripts, or hardware adapters contain business branching or validation, move the rule inward. +- When a use case instantiates infrastructure, calls a volatile dependency directly, or depends on a concrete implementation, introduce a policy-owned port and wire the concrete detail at the edge. +- When a `*Service`, utility folder, shared module, base package, or generic `core` package becomes an escape hatch, split by use case, role, or ownership and restore dependency direction. +- When an adapter bypasses a use case, a presenter reads persistence directly, or infrastructure is both imported by and importing inward code, restore the intended boundary. +- When service boundaries, process boundaries, remote calls, deployment boundaries, or embedded hardware appear, still verify source dependencies, data ownership, I/O cost, and policy independence. +- When tests need the framework, database, network, service, or hardware to verify business rules, move tests to use cases/entities with fakes or add a stable boundary contract. +- When a compromise is unavoidable, keep it at the outermost layer possible, document the violation, avoid normalizing it, and preserve a path to separation. + +## Final checklist + +- Business rules independent from frameworks, databases, UI, services, devices, and vendors? +- Dependencies point inward, with ports owned by inner policy and concrete details outside? +- Entities guard invariants and focused use cases orchestrate one application action? +- Boundaries explicit and enforced in code, tests, packages, or build rules? +- Controllers, presenters, gateways, service listeners, and adapters humble? +- Structure reveals use cases and business capabilities instead of generic technical buckets? +- Core tests run fast without real delivery, persistence, network, external service, or hardware? +- Details remain replaceable without rewriting business rules? diff --git a/programming-principles/references/clean-code.full.md b/programming-principles/references/clean-code.full.md new file mode 100644 index 0000000..7a07f89 --- /dev/null +++ b/programming-principles/references/clean-code.full.md @@ -0,0 +1,297 @@ +# OBEY Clean Code by Robert C. Martin + +This file defines mandatory working rules for this repository. Follow these instructions before making any code, test, refactor, review, or documentation change. + +## Priority and behavior + +- Treat every unqualified rule in this file as `MUST`; treat `Prefer` as `SHOULD`; treat `Do not`, `Avoid`, and `Never` as `MUST NOT` unless the user explicitly overrides it. +- Prefer readability, maintainability, correctness, and safe change over cleverness or speed hacks. +- Optimize for the next human reader. +- When trade-offs exist, choose the option that reduces long-term complexity. +- Never preserve bad structure just because it already exists. +- Apply the Boy Scout Rule: leave touched code cleaner than you found it. + +## Core clean code principles + +- Write code primarily for humans, not just for execution. +- Keep code simple, direct, and easy to modify. +- Avoid accidental complexity. +- Avoid surprising behavior. +- Prefer explicit intent over implicit magic. +- Prefer local reasoning: a reader should understand code with minimal jumping across files. +- Reduce technical debt instead of moving it around. + +## Naming rules + +- Use intention-revealing names. +- Names must explain purpose, role, or behavior without requiring extra comments. +- Avoid misleading names, overloaded meanings, and visually confusable identifiers. +- Make distinctions meaningful. Do not create names that differ only cosmetically. +- Use pronounceable, searchable names. +- Avoid abbreviations unless they are established domain or platform standards. +- Avoid encodings in names, including type prefixes, implementation hints, and Hungarian notation. +- Avoid unnecessary context in identifiers. +- Add context through modules, classes, namespaces, or types when that is cleaner than longer names. +- Use one word per concept across the codebase. +- Do not use multiple synonyms for the same operation or concept. +- Do not reuse a familiar word for a different meaning. +- Class, type, and module names should be nouns or noun phrases. +- Function and method names should be verbs or verb phrases. +- Use problem-domain names for domain concepts. +- Use solution-domain names for technical concepts. +- Do not use cute, funny, cryptic, or private-joke names. + +## Function rules + +- Keep functions small. +- Each function must do one thing. +- A function should have one clear reason to change. +- Keep each function at one level of abstraction. +- Organize code top-down so readers see the high-level story before details. +- Prefer descriptive names over short names. +- Minimize the number of parameters. +- Avoid boolean flag parameters. Split behavior into separate functions instead. +- Avoid output parameters unless language conventions make them necessary. +- Eliminate hidden side effects. +- Separate commands from queries. +- A function that answers a question should not also mutate state. +- Prefer exceptions or explicit result types over ad hoc error codes, according to project language norms. +- Isolate error handling from main logic. +- Eliminate duplication aggressively. +- Prefer straightforward control flow over clever control flow. +- Refactor deep nesting into clearer structure. + +## Comment rules + +- Do not use comments to compensate for bad naming or bad structure. +- First improve the code, then decide whether a comment is still needed. +- Prefer self-explanatory code. +- Use comments only when they add information the code cannot express well. +- Good comment categories include: + - legal or licensing requirements + - non-obvious intent + - important warnings or constraints + - rationale for a surprising decision + - clarification of external behavior or protocol assumptions +- Remove redundant, obsolete, obvious, noisy, and misleading comments. +- Do not narrate the code line by line. +- Keep comments precise and maintain them when code changes. +- Avoid TODO comments unless they are actionable, specific, and necessary. + +## Formatting and structure + +- Use consistent formatting across the repository. +- Format code to reveal structure and intent. +- Keep related concepts close together. +- Keep files, classes, and functions reasonably small. +- Use vertical ordering to tell the story from higher level to lower level. +- Use indentation to clarify scope, not to hide complexity. +- Avoid excessive line length when it hurts readability. +- Avoid decorative alignment that is brittle during edits. +- Preserve a layout that supports fast scanning. + +## Objects, modules, and data structures + +- Separate behavior-rich objects from plain data carriers intentionally. +- Do not mix data containers and business behavior arbitrarily. +- Hide implementation details behind clear interfaces. +- Expose behavior, not representation. +- Use DTO-like structures as simple carriers when appropriate. +- Avoid train-wreck call chains and unnecessary knowledge of internal structure. +- Respect loose coupling and local boundaries. +- Keep persistence, framework, and third-party details from obscuring business behavior or core logic. + +## Class and module design + +- Keep classes and modules small. +- Each class or module should have one primary responsibility. +- Favor high cohesion. +- Split classes that accumulate unrelated behavior. +- Organize code so likely changes remain local. +- Public APIs should be small, obvious, and hard to misuse. +- Prefer composition over complex inheritance unless inheritance is clearly the simpler and more stable model. +- Keep constructors and setup logic from overwhelming domain behavior. + +## Error handling + +- Design error handling deliberately. +- Keep the happy path easy to read. +- Provide enough context in error messages for diagnosis. +- Use error types or exception classes that support caller decisions. +- Do not return `null` or equivalent absence sentinels when a safer model exists. +- Do not pass `null` or equivalent invalid states unless the API explicitly models that case. +- Prefer exceptions, special cases, empty objects, or explicit optionality according to the codebase's language and conventions. +- Make resource cleanup and shutdown paths correct and visible. + +## Boundaries and external dependencies + +- Isolate third-party libraries behind local adapters or wrappers when practical. +- Avoid coupling core logic directly to unstable external APIs. +- Create narrow interfaces around dependencies. +- Add learning tests or focused integration tests for tricky external behavior. +- When a dependency does not exist yet, define interfaces from local needs, not from guesses about future implementations. + +## System construction rules + +- Separate constructing a system from using it. +- Keep object graph assembly, dependency injection, factories, and framework bootstrapping out of ordinary business behavior. +- Put startup wiring in an explicit main or composition area. +- Use factories when construction policy is meaningful or complex. +- Do not let cross-cutting concerns obscure ordinary code flow. +- Use standards, frameworks, proxies, or AOP-style mechanisms only when they add demonstrable value. +- Test-drive architectural decisions with executable slices, not only diagrams or configuration. +- Use domain-specific languages only when they make system intent clearer than general-purpose code. + +## Tests + +- Treat tests as production-quality code. +- Keep tests clean, readable, deterministic, and maintainable. +- A test should communicate one main idea. +- Prefer simple setup and clear assertions. +- Avoid brittle tests coupled to irrelevant implementation details. +- Tests should be fast when possible. +- Tests should be isolated and order-independent. +- Tests should be self-checking. +- Add or update tests for behavior changes, bug fixes, and significant refactors. +- Do not ship code changes without proportionate validation. +- When fixing a bug, add a test that would have caught it, when feasible. + +## TDD and clean test rules + +- Prefer writing a failing test before production code when the behavior can be specified clearly. +- Do not write production behavior beyond what a failing test or explicit requirement justifies. +- Keep tests small enough that a failure names one behavior or one concept. +- Prefer one assert or one conceptual assertion per test when that improves clarity. +- Use test names and test data that reveal the business or technical behavior under test. +- Build a small testing vocabulary or helper DSL when repeated setup hides intent. +- Keep test code clean; dirty tests reduce the ability to change production code safely. +- Avoid tests that require multiple manual steps to run. +- Use coverage patterns to find untested risk, not as a substitute for meaningful assertions. +- Treat ignored, flaky, or skipped tests as unresolved questions. + +## Concurrency and async work + +- Do not introduce concurrency unless it provides a real benefit. +- Prefer simpler sequential code when it is sufficient. +- Minimize shared mutable state. +- Prefer immutability, message passing, or clear ownership boundaries. +- Keep synchronized or locked sections as small as possible. +- Be explicit about shutdown, cancellation, timeouts, and cleanup. +- Test concurrent behavior carefully where it matters. +- Know the execution model before changing concurrent code. +- Avoid dependencies between synchronized methods. +- Get non-concurrent behavior correct before adding threading. +- Make threaded code pluggable and tunable when its policy or concurrency level may vary. +- Run concurrency-sensitive tests under varied thread counts, schedules, and platforms where practical. +- Treat spurious failures as possible concurrency defects until evidence says otherwise. + +## Refactoring rules + +- Refactor in small, safe steps. +- Preserve behavior while improving structure. +- First make it work, then make it right. +- Remove duplication, dead code, misleading abstractions, and special-case clutter. +- Rename aggressively when names are weak. +- Extract code when doing so improves cohesion and clarity. +- Inline abstractions that no longer earn their cost. +- Prefer the simplest design that passes all relevant tests. + +## Emergent design and successive refinement + +- Prefer designs that run all relevant tests, remove duplication, express intent, and use the fewest necessary classes and methods. +- Refine code through working drafts rather than expecting the first version to be clean. +- When code starts rough, keep improving names, structure, and tests until intent is clear. +- Do not start a grand redesign when incremental refinement can recover the design safely. +- Use the Boy Scout Rule on touched code, but keep cleanup proportional to the task. + +## Smells to detect and eliminate + +Actively look for and fix these issues when touching code: + +- vague or misleading names +- duplicated logic +- oversized functions +- oversized classes or modules +- mixed abstraction levels +- hidden side effects +- boolean control flags +- long parameter lists +- deep nesting +- excessive conditionals that should be isolated or polymorphic +- comment-heavy code that should be refactored instead +- dead code and unused abstractions +- fragile tests +- environment-dependent tests without need +- unnecessary indirection +- accidental complexity +- coupling that spreads change broadly +- build or tests requiring more than one manual step +- code at the wrong level of abstraction +- base classes depending on derivatives +- transitive navigation through object internals +- artificial coupling between unrelated concepts +- hidden logical dependencies +- unimplemented obvious behavior +- incorrect boundary behavior +- overridden safeties +- magic numbers without named meaning +- negative conditionals that obscure intent +- ignored tests and insufficient boundary tests +- functions that require readers to understand an algorithm before they can trust the name + +## Change Process + +For every non-trivial task: + +1. Understand the intent and affected behavior. +2. Identify the simplest correct change. +3. Improve names before adding comments. +4. Keep edits localized when possible. +5. Add or update tests as needed. +6. Run relevant validation. +7. Review the diff for readability, duplication, and unnecessary complexity. +8. Ensure the final code is cleaner than before. + +## Implementation preferences + +- Prefer explicit, boring, maintainable solutions. +- Prefer standard library and existing project patterns over new dependencies. +- Do not add a dependency unless it clearly reduces overall complexity. +- Reuse established project conventions unless they conflict with these rules or the user explicitly asks otherwise. +- Keep interfaces small. +- Keep state transitions obvious. +- Avoid premature optimization. +- Optimize only when there is evidence or a known requirement. + +## Review checklist + +Before finishing, verify all of the following: + +- Names reveal intent. +- Functions are small and focused. +- Classes and modules have clear responsibilities. +- Comments are necessary and accurate. +- Error handling is explicit and useful. +- Duplication was removed where reasonable. +- Tests cover the changed behavior appropriately. +- The code reads cleanly from top to bottom. +- The design is simpler or at least not more complex than before. +- The change follows existing project conventions. + +## Output Expectations + +When making changes: + +- Briefly explain what changed. +- State what tests or checks were run. +- Call out any unresolved risk, assumption, or trade-off. +- If a requested change conflicts with these rules, follow the user request but mention the conflict explicitly. + +## Hard rules + +- Do not introduce misleading names. +- Do not keep duplicated logic without a strong reason. +- Do not add comments where better code would remove the need. +- Do not mix querying with mutation without a strong reason. +- Do not silently broaden scope beyond the requested task. +- Do not leave touched code less readable than before. diff --git a/programming-principles/references/clean-code.mini.md b/programming-principles/references/clean-code.mini.md new file mode 100644 index 0000000..26d8320 --- /dev/null +++ b/programming-principles/references/clean-code.mini.md @@ -0,0 +1,47 @@ +# OBEY Clean Code by Robert C. Martin + +## When to use + +Use when readability, local reasoning, and maintainable code shape are the main concerns, especially during everyday implementation and review. + +## Primary bias to correct + +Working code is not automatically clean code. + +## Decision rules + +- Treat cleanliness as part of delivery. Preserve behavior, leave touched code cleaner within scope, and do not add mess because the schedule is tight or a rewrite is promised. +- Write for local reasoning. A reader should understand the path without reconstructing hidden state, wide jumps, or naming trivia. +- Use precise names and one term per concept. Rename code when vocabulary hides intent, overloads meaning, or forces comments to compensate. +- Keep functions small, focused, and at one level of abstraction. Tell the story top-down so intent appears before detail. +- Keep parameters few and meaningful. Avoid boolean flags, output parameters, and grab-bag argument lists; model the concept instead. +- Separate commands from queries and eliminate hidden side effects. A function that answers should not also mutate behind the reader's back. +- Keep the happy path readable. Isolate error handling, invalid-state handling, and cleanup; prefer explicit optionality or typed results over null-like sentinel flow when the language supports it. +- Expose behavior rather than raw representation. Avoid train-wreck access, utility dumping grounds, and classes or modules with mixed responsibilities. +- Keep construction, framework, persistence, transaction, security, and vendor details outside business behavior. +- Make public APIs small, explicit, and hard to misuse. Encode boundary logic, required order, and likely changes where readers can see them. +- Use comments only for rationale, constraints, warnings, or external contracts. Do not narrate code instead of improving it. +- Treat tests as production code: readable, deterministic, aligned with the behavior or contract they protect, and backed by proportionate validation before calling the change done. +- Let design emerge through tests, duplication removal, expressiveness, and minimal structure; do not add needless abstractions or infrastructure. +- When touching code, remove the smell that most increases change cost, but do not silently broaden the task beyond the smallest cleanup that makes the requested change safe. + +## Trigger rules + +- When a function mixes setup, validation, computation, and side effects, split the phases. +- When a comment explains control flow, simplify names or structure before keeping the comment. +- When a function both mutates and answers, or hides a mode switch behind a flag, separate the responsibilities. +- When duplication, repeated switches, or primitive clusters appear, name the concept with an argument object, polymorphism, special case, or other small abstraction. +- When a boundary leaks framework, vendor, or persistence quirks inward, add or strengthen a local adapter. +- When async or concurrency enters, isolate threading policy, minimize shared mutable state, define shutdown, and test timing-sensitive behavior. +- When fixing a bug or changing behavior, add or update the test that protects the intended contract. +- When cleanup starts spreading into unrelated areas, cut back to the smallest refactor that keeps the requested change safe and readable. + +## Final checklist + +- Can a reader follow the change locally? +- Are names and APIs carrying the meaning without narration? +- Is mutation explicit and the happy path still clear? +- Did framework, persistence, vendor, and construction details stay behind boundaries? +- Did I remove at least one smell from the touched area? +- Do tests protect the changed behavior or contract? +- Did I actually run the relevant tests or checks for this change? diff --git a/programming-principles/references/code-assessment-workflow.md b/programming-principles/references/code-assessment-workflow.md new file mode 100644 index 0000000..cdb2065 --- /dev/null +++ b/programming-principles/references/code-assessment-workflow.md @@ -0,0 +1,104 @@ +# Principles-Based Code Assessment Workflow + +A reusable workflow for auditing a codebase (default branch + one open PR) against +programming-principles books and producing structured, deduplicated, prioritized findings. + +## When to use + +- User asks for a "code review", "code assessment", "architecture review", or + "code quality audit" referencing books like Clean Code, APoSD, WELC, Release It!, DDIA. +- User wants findings that go beyond surface lint — structural, design, reliability, + data-consistency, and architectural observations. +- The task involves a repo with existing issues and PRs that must be checked for overlap. + +## Workflow + +### 1. Load the evaluation frameworks + +Load the relevant skills in parallel: + +```markdown +skill_view(name='programming-principles') +skill_view(name='codebase-inspection') +``` + +The `programming-principles` skill provides the per-book rule catalogs +(Clean Code, APoSD, WELC, Release It!, DDIA) and cross-cutting principles. +The `codebase-inspection` skill provides the LOC metrics and the systematic +audit bug-class catalog. + +### 2. Understand the codebase + +- If the repo is local, use `find`/`git log`/`git branch -a` to map the tree. +- If remote, use `gh repo clone` or `web_extract`. +- Get a directory tree: `find -type f | grep -v '.git/' | head -100`. +- Read every source file. Read key ones first, but do not skip small files — + bugs cluster in overlooked files (configs, scripts, templates). + +### 3. Read the GitHub context + +This is critical for deduplication. Before writing findings: + +- `gh pr view --json title,body,files,additions,deletions,state` — for each + open PR. Understand what the PR changes and what it does not change. +- `gh issue list --state open --json number,title,body --limit 10` — list all + open issues. Read each one to understand what is already tracked. +- Cross-reference: a finding that overlaps an existing issue or PR is + **not** a new finding. Note the overlap and move on. + +### 4. Apply the evaluation framework + +For each finding, identify: + +| Attribute | What | +|-----------|------| +| **File** | Exact path, line range | +| **Function/context** | Named function or logical block | +| **Book principle** | Which book(s) the finding violates. Use the task-to-book mapping from the programming-principles skill. | +| **Principle quote** | The specific rule from the book (e.g., "Functions should do one thing" — Clean Code) | +| **Impact** | Data loss, silent failure, incorrect behavior, maintainability drag | +| **Priority** | P1 (data loss/security/runtime failure), P2 (structural/maintainability), P3 (design concern, notification) | +| **Merits issue?** | Yes/No — an issue describes the problem and suggests a fix. Not everything needs an issue. | +| **Already covered?** | Check against open issues and PRs. If yes, exclude from the deliverable. | + +### 5. Organize findings + +Group by priority, then by book. Each finding should have: + +``` +### N. Title +**File:** `path/to/file.mjs`, lines X–Y +**Book:** Book Name (principle) +**Priority:** P1/P2/P3 + +Description of the problem, evidence from the code, and why it matters. + +🟡 Merits issue #N: `tag: short description of fix` +``` + +### 6. Maintain an exclusion log + +For things you explicitly chose not to flag, note why: + +| Topic | Why excluded | +|-------|-------------| +| Missing X | Already covered by issue #N | +| Y pattern | Below actionable threshold for an audit | + +This shows the reader you considered the space systematically. + +## Common pitfalls + +- **Don't stop after one file.** Read *every* source file — bugs hide in small scripts, + test helpers, and configuration templates. +- **Don't skip the PR branch.** The PR is the "planned implementation" the user asked + about. Read its new files from a worktree or diff to understand what changes. +- **Don't flag what's already tracked.** Reading issues and PRs first prevents wasted + effort and shows the user respect for their existing tracking. +- **Don't over-file.** A filing for every code smell produces noise. Issue findings when + the problem has real impact (data loss, security, structural friction that blocks + future changes). Minor things get a note but not an issue. +- **Don't forget to validate findings against both branches.** A finding may exist on + main but be fixed in the PR, or be introduced by the PR. Call out the delta. +- **Prefer exact line numbers.** Vague "file X has a problem" is not actionable. + Every finding needs exact file + function + line range. diff --git a/programming-principles/references/code-complete.full.md b/programming-principles/references/code-complete.full.md new file mode 100644 index 0000000..9636fda --- /dev/null +++ b/programming-principles/references/code-complete.full.md @@ -0,0 +1,354 @@ +# OBEY Code Complete by Steve McConnell + +## Purpose + +This repository follows **Code Complete** in the sense of Steve McConnell: +apply disciplined software construction practices that reduce defects, improve readability, and produce robust code under real-world constraints. + +All code generation, edits, and reviews must optimize for: +- low-defect construction +- readable and intention-revealing code +- controlled complexity +- defensive programming where appropriate +- strong routine and class design +- practical correctness over style theater + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Construction quality is not accidental. + +When uncertain, choose the option that: +1. lowers defect probability +2. makes the code easier to inspect and reason about +3. reduces control-flow complexity +4. uses data and routines clearly +5. protects the program against invalid states and misuse + +Do not optimize for cleverness, minimal keystrokes, or fashionable idioms at the cost of clarity. + +--- + +## Foundational Construction Rules + +1. Write code primarily for human readers. +2. Favor clarity, locality, and explicitness over trickiness. +3. Keep control flow simple and visible. +4. Make correctness easier to achieve than incorrectness. +5. Use conventions consistently. + +--- + +## Construction Prerequisites and Decisions + +1. Do not treat construction as isolated typing; verify that requirements, architecture, major risks, and coding conventions are clear enough for the change. +2. Resolve major construction decisions before large implementation work: language constraints, error policy, data representation, reuse strategy, integration approach, and testing approach. +3. Use upstream uncertainty as a reason to build a small validated slice, not as an excuse for speculative code. +4. Keep the software metaphor or design model only if it helps make concrete construction decisions. +5. Measure twice before cutting when an early decision will be expensive to reverse. + +--- + +## Pseudocode Programming Process + +1. For complex routines, sketch the routine in precise pseudocode or comments before filling in details. +2. Refine pseudocode until it names the real steps at a consistent abstraction level. +3. Convert clear pseudocode into code and keep only comments that still add intent, constraints, or rationale. +4. Do not use pseudocode as a substitute for understanding the algorithm. + +--- + +## Routine Design Rules + +1. Routines should have one clear purpose. +2. The routine name should describe the result or action precisely. +3. Keep the interface as small as practical. +4. Avoid long parameter lists and flag arguments. +5. Separate setup, validation, computation, and side effects when they are conceptually different. +6. Return values should be meaningful and hard to misuse. +7. Prefer guard clauses and straightforward structure over deeply nested logic. + +Anti-patterns (MUST NOT): +- routines that do several unrelated things +- routines whose names describe implementation detail instead of purpose +- many hidden side effects +- boolean parameters that switch routine mode + +--- + +## Variable and Data Rules + +1. Use names that reveal purpose and meaning. +2. Keep variable scope as small as practical. +3. Initialize variables deliberately. +4. Prefer named constants or stable values where a variable is not meant to change. +5. Avoid magic numbers and unexplained sentinel values. +6. Use stronger data types when primitives hide meaning. + +Anti-patterns (MUST NOT): +- reused loop/index/temp variables beyond their purpose +- long-lived mutable locals carrying many meanings +- values whose units or semantics are unclear + +--- + +## Data Type Rules + +1. Choose data types that make invalid or ambiguous values harder to represent. +2. Name constants for magic values, units, bounds, and sentinel meanings. +3. Use booleans only for true binary meanings; replace flag fields with clearer states when needed. +4. Use enumerations or named alternatives when a value belongs to a closed set. +5. Use arrays, records, maps, and tables only where their shape communicates the data meaning. +6. Encapsulate unusual data structures behind routines or types that reveal purpose. +7. Keep units, ranges, precision, encoding, and ownership visible near the data they affect. + +--- + +## Control Flow Rules + +1. Prefer the simplest control flow that expresses the logic. +2. Keep nesting shallow when possible. +3. Replace complicated boolean logic with named predicates or clearer structure. +4. Use case/switch constructs only when they improve clarity. +5. Eliminate impossible paths and dead branches. +6. Avoid surprising exits unless they clarify the routine. + +Anti-patterns (MUST NOT): +- deeply nested conditionals +- complicated loop exits with hidden state changes +- control flow dependent on side effects in expressions +- clever one-liners that obscure the logic + +--- + +## Statement, Conditional, and Loop Rules + +1. Organize straight-line code so dependencies appear before use and related statements stay together. +2. Keep conditionals positive and direct when possible. +3. Put the normal path where readers can find it quickly. +4. Use loops with clear initialization, termination, and update rules. +5. Keep loop bodies focused; extract work when a loop hides several responsibilities. +6. Avoid unusual control structures unless they are clearer than ordinary alternatives. +7. Use table-driven methods when repeated branching is stable and the table can be validated. + +--- + +## Defensive Programming Rules + +1. Validate inputs at trust boundaries. +2. Use assertions or invariant checks where programmer assumptions matter. +3. Distinguish between recoverable conditions and programming errors. +4. Fail in a way that preserves diagnosability. +5. Do not silently continue from corrupted or impossible state. + +Anti-patterns (MUST NOT): +- assuming all callers are correct +- burying invalid state until it causes distant failures +- swallowing exceptions without context + +--- + +## Error Handling Rules + +1. Handle errors at the right level of abstraction. +2. Preserve useful context. +3. Do not let error handling dominate the normal path. +4. Standardize similar failure handling. +5. Prefer explicit, well-understood failure semantics over ad hoc conventions. + +--- + +## Table-Driven and Data-Driven Rules + +1. Prefer data-driven logic over long repeated condition chains when the mapping is stable and explicit. +2. Use tables or maps for configuration-like decisions. +3. Keep the structure obvious and validated. +4. Do not hide complex logic in inscrutable data encodings. + +--- + +## Class and Module Design Rules + +1. Each class or module should own a focused responsibility. +2. Separate interface from implementation. +3. Hide representation and incidental detail. +4. Keep classes cohesive. +5. Reduce coupling through clear contracts and limited knowledge of internals. + +Anti-patterns (MUST NOT): +- god classes +- modules with mixed persistence, formatting, business logic, and integration concerns +- public surfaces that expose internal bookkeeping + +--- + +## Complexity Management Rules + +1. Treat rising complexity as a defect risk. +2. Prefer simple code over clever code. +3. Break apart large or tangled routines and modules. +4. Remove duplication that multiplies maintenance effort. +5. Choose designs that reduce the amount a maintainer must keep in working memory. + +--- + +## Construction with Preconditions and Postconditions + +1. Be explicit about routine assumptions. +2. Encode important invariants close to the code they protect. +3. Keep contracts simple and testable. +4. Use assertions for programmer mistakes, validation for external input, and domain errors for expected business failures. + +--- + +## Comment Rules + +1. Comments should explain intent, rationale, contracts, and non-obvious facts. +2. Do not comment obvious code instead of improving it. +3. Keep comments accurate or delete them. +4. Prefer self-documenting structure first, comments second. + +--- + +## Coding Standards Rules + +1. Be consistent within the codebase. +2. Use formatting, naming, and file structure to support readability. +3. Standardize common idioms so readers do not need to relearn style per module. +4. Prefer a shared convention over local personal taste. + +--- + +## Incremental Construction Rules + +1. Build in small, verifiable increments. +2. Integrate frequently enough to surface conflicts and misunderstanding early. +3. Keep partial work from rotting in long-lived isolation. +4. Review and improve code as part of construction, not only after it. + +--- + +## Quality, Collaboration, Debugging, and Refactoring + +1. Use reviews, inspections, pair work, tests, and static checks according to the risk of the code. +2. Treat debugging as diagnosis: reproduce, isolate, explain, fix, and verify rather than guessing. +3. Fix the root cause when practical, not only the symptom. +4. Add tests around defects so the same failure is easier to detect next time. +5. Refactor when structure hides intent, duplicates knowledge, or raises defect probability. +6. Keep refactoring separate from behavior changes when that improves reviewability. + +--- + +## Performance, Integration, Tools, and Craftsmanship + +1. Do not tune performance until the requirement and evidence justify it. +2. When tuning is justified, measure before and after, and keep clarity unless the tradeoff is explicit. +3. Integrate frequently enough to expose construction conflicts early. +4. Use programming tools, scripts, debuggers, profilers, editors, and build automation to reduce error-prone manual work. +5. Keep layout and style consistent enough that readers can focus on meaning. +6. Prefer self-documenting code, but add documentation where the code cannot express intent, constraints, or usage. +7. Treat personal discipline, curiosity, and ability to withstand careful review as part of construction quality. + +--- + +## Review Rules + +When reviewing code, actively look for: +- unclear names +- weak routine boundaries +- long parameter lists +- unnecessary nesting +- hidden side effects +- poor defensive checks at trust boundaries +- duplicated logic +- confusing control flow +- god classes or mixed responsibilities +- comments compensating for poor structure + +--- + +## Forbidden Patterns + +### Cleverness over Clarity +- dense tricks that are hard to inspect +- compressed expressions that save lines but increase interpretation cost + +### Routine Bloat +- one routine doing several phases and concerns +- long signatures with many unrelated parameters + +### Defensive Vacuum +- no validation at trust boundaries +- no checks around critical assumptions +- silent fallback from impossible state + +### Comment-as-Crutch +- obvious comments over bad code +- stale comments that mislead + +### Consistency Neglect +- arbitrary naming and formatting changes +- module-specific mini dialects inside one codebase + +--- + +## Code Generation Rules + +When generating code, default to: +1. clear names +2. focused routines +3. explicit data meaning +4. simple control flow +5. defensive checks at boundaries +6. cohesive classes/modules +7. consistent style + +Avoid by default: +- dense clever code +- broad god objects +- fragile hidden assumptions +- unnecessary complexity in loops and conditionals +- comments where better names or decomposition would do + +--- + +## Testing Rules + +1. Test routine behavior around normal, boundary, and invalid inputs. +2. Test defensive checks where boundary validation matters. +3. Keep tests aligned with routine contracts. +4. Test complex data-driven logic with representative tables and edge cases. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Are names clear and intention-revealing? +- Are routines focused and reasonably small? +- Is control flow straightforward? +- Are trust boundaries defended? +- Are contracts and invariants explicit enough? +- Did we reduce or at least not increase complexity? +- Are classes/modules cohesive? +- Did we avoid cleverness that harms inspection? +- Are comments used only where they add value? +- Is the style consistent with the rest of the codebase? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the option that: +1. lowers defect risk +2. improves readability +3. simplifies control flow +4. strengthens defensive correctness +5. keeps the code easier to inspect and maintain + +Write code that would stand up to careful review. diff --git a/programming-principles/references/code-complete.mini.md b/programming-principles/references/code-complete.mini.md new file mode 100644 index 0000000..f886e0d --- /dev/null +++ b/programming-principles/references/code-complete.mini.md @@ -0,0 +1,56 @@ +# OBEY Code Complete by Steve McConnell + +## When to use + +Use when implementing, changing, reviewing, debugging, refactoring, or tuning production code where construction discipline must reduce defects and keep code easy to inspect. + +## Primary bias to correct + +Construction quality is not accidental. Do not treat typing code, making it work once, or using a clever idiom as complete construction; choose the option that lowers defect risk and makes the code easier to reason about. + +## Decision rules + +- Before large construction work, verify that requirements, architecture, major risks, coding conventions, language constraints, error policy, data representation, reuse, integration, and testing approach are clear enough. +- When upstream uncertainty remains, build a small validated slice instead of speculative code, and make expensive-to-reverse decisions deliberately. +- Optimize first for human readers: clarity, locality, explicitness, visible control flow, consistent conventions, and practical correctness over cleverness, minimal keystrokes, or fashion. +- For complex routines, sketch precise pseudocode or intent comments at a consistent abstraction level, then convert them into code and keep only comments that still explain intent, constraints, contracts, or rationale. +- Keep routines cohesive, precisely named, small at the interface, and hard to misuse. Separate setup, validation, computation, and side effects when they are conceptually different. +- Make variable and data meaning explicit through purpose-revealing names, small scope, deliberate initialization, named constants, stronger types, and visible units or sentinel meanings. +- Choose data types that make invalid or ambiguous values harder to represent; use booleans only for true binary meanings, enumerations for closed sets, and records/maps/tables only when their shape communicates meaning. +- Keep control flow simple enough to verify: shallow nesting, named predicates for complex conditions, clear normal path, clear loop initialization/termination/update, and no side-effect-dependent expressions or clever one-liners. +- Use table-driven or data-driven logic for stable explicit mappings only when the table is clearer, obvious, synchronized with the rules, and validated; do not hide complex behavior in inscrutable encodings. +- Validate input at trust boundaries. Use assertions, invariant checks, and simple contracts for programmer assumptions; use validation or domain errors for expected external or business failures. +- Handle errors at the right abstraction, preserve diagnostic context, standardize similar failures, keep the normal path readable, and never silently continue from corrupted or impossible state. +- Keep classes and modules focused, cohesive, and bounded by clear contracts; hide representation and internal bookkeeping, and avoid mixed persistence, formatting, business, and integration concerns. +- Treat rising complexity as defect risk: split tangled routines or modules, remove duplication that multiplies maintenance effort, and reduce what a maintainer must keep in working memory. +- Build in small, verifiable increments; integrate often enough to expose conflicts, keep partial work from rotting, and review and improve code during construction. +- Match reviews, inspections, pair work, tests, static checks, and regression tests to defect risk. Debug by reproducing, isolating, explaining, fixing, and verifying root causes rather than guessing. +- Refactor when structure hides intent, duplicates knowledge, or raises defect probability, and keep refactoring separate from behavior change when that improves reviewability. +- Tune performance only when requirements and evidence justify it; measure before and after, and keep clarity unless an explicit measured tradeoff warrants the cost. +- Use tools, scripts, debuggers, profilers, editors, and build automation to reduce error-prone manual work, not to replace understanding. +- Use layout, comments, documentation, and coding standards to lower reader effort. Prefer self-documenting structure first; comments should explain intent, assumptions, constraints, limitations, usage, or non-obvious facts. + +## Trigger rules + +- When coding starts from a proposed solution, restate the requirement, architecture fit, risks, conventions, and success constraints before implementation. +- When a routine is hard to name, mixes phases, has flag arguments, long parameters, or hidden side effects, redesign the interface or split the routine. +- When readers must decode units, ranges, precision, encoding, ownership, status, magic values, or primitive flags, move that meaning into names, constants, types, or structures. +- When input crosses a user, file, network, external-system, or other trust boundary, decide what is validated, rejected, recovered from, asserted, and kept diagnosable. +- When branches, loops, recursion, exits, or exception paths become hard to verify, simplify before adding logic. +- When repeated branching maps stable categories, ranges, conversions, validation, dispatch, or configuration-like rules, consider a validated table. +- When a class or module exposes representation, grows into a god object, or mixes unrelated responsibilities, restore the abstraction boundary. +- When tests cover only the happy path, add normal, boundary, invalid-input, defensive-check, routine-contract, and data-driven edge cases. +- When debugging begins from a guess, first make the failure repeatable, collect evidence, isolate the path, and explain the cause. +- When refactoring poorly understood or risky code, add tests or analysis first and keep behavior changes separate. +- When performance work begins, set a target, measure the current behavior, change one thing, remeasure, and document any clarity tradeoff. +- When comments restate obvious mechanics or go stale, rewrite the code or delete the comment; when code cannot express intent, constraints, or usage, add a close accurate comment. +- When local style starts to diverge, follow shared formatting, naming, file-structure, and idiom conventions instead of creating a module-specific dialect. + +## Final checklist + +- Requirements, architecture fit, risks, conventions, and construction approach are clear enough. +- Names, routines, data, classes, layout, comments, and standards reduce reader effort. +- Inputs, errors, assertions, contracts, invariants, impossible states, and trust boundaries are deliberate. +- Control flow, loops, tables, recursion, exits, and exception paths are simple enough to inspect. +- Tests, reviews, debugging, refactoring, integration, tooling, and tuning are evidence-based. +- The change is small enough to verify and would stand up to careful review. diff --git a/programming-principles/references/designing-data-intensive-apps.full.md b/programming-principles/references/designing-data-intensive-apps.full.md new file mode 100644 index 0000000..1642e3f --- /dev/null +++ b/programming-principles/references/designing-data-intensive-apps.full.md @@ -0,0 +1,393 @@ +# OBEY Designing Data-Intensive Applications by Martin Kleppmann + +## Purpose + +This repository follows **Designing Data-Intensive Applications** in the sense of Martin Kleppmann: +design systems around explicit trade-offs in reliability, scalability, maintainability, consistency, and data flow. + +All code generation, edits, and reviews must optimize for: +- explicit data and consistency semantics +- idempotent and replay-safe processing +- clear ownership of truth +- durable boundaries between storage, messaging, and computation +- schema evolution awareness +- realistic distributed systems assumptions + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Data systems are defined by trade-offs. +When uncertain, make those trade-offs explicit instead of hiding them behind vague abstractions. + +Always ask: +1. what is the source of truth? +2. what are the consistency expectations? +3. what happens on retries, duplicates, reordering, and partial failure? +4. how does the data evolve over time? +5. where is state durable, cached, derived, or ephemeral? + +Do not design distributed behavior as if everything were local, ordered, and exactly once. + +--- + +## Reliability Rules + +1. Treat crashes, partial writes, duplicate work, timeouts, and stale reads as normal design inputs. +2. Make write acknowledgment semantics explicit. +3. Avoid hidden assumptions about durable success. +4. Design for restart, replay, and partial failure recovery. + +Anti-patterns (MUST NOT): +- side effects that cannot be retried safely +- no distinction between accepted, persisted, and applied +- assuming one successful response means all downstream effects succeeded + +--- + +## Scalability and Maintainability Rules + +1. Describe load with concrete parameters before changing architecture. +2. Describe performance with latency, throughput, percentiles, and tail behavior where they matter. +3. Do not claim scalability from node count alone; identify the bottleneck, access pattern, and contention point. +4. Keep operability, simplicity, and evolvability as first-class design goals. +5. Prefer designs that make production behavior inspectable and changeable over opaque clever mechanisms. +6. Avoid accidental complexity from unnecessary distribution, premature heterogeneity, or hidden coupling. + +--- + +## Data Model and Storage Rules + +1. Choose storage shape based on access patterns, consistency needs, and update behavior. +2. Do not force one storage pattern onto all workloads. +3. Keep the ownership of each dataset explicit. +4. Distinguish primary data from indexes, caches, projections, and search copies. + +### Source of Truth +For every important piece of data, identify: +- primary owner +- derived copies +- replication path +- update path +- consistency expectation + +Anti-patterns (MUST NOT): +- many writable copies with no ownership +- cache quietly becoming the real source of truth +- denormalized copies with no repair strategy + +--- + +## Query Model and Data Shape Rules + +1. Choose relational, document, graph, key-value, or analytical models according to relationships, query needs, update locality, and evolution pressure. +2. Do not use a document model when many-to-one or many-to-many relationships require awkward duplication or application-side joins. +3. Do not force a relational shape when data is naturally self-contained and usually accessed together. +4. Use declarative query languages where they make intent clearer and leave optimization to the engine. +5. Use graph models when relationships are first-class and traversal is central. +6. Treat Cypher, SPARQL, Datalog, SQL, MapReduce, and application code as different expression choices with different maintainability and optimization tradeoffs. + +--- + +## Storage Engine and Indexing Rules + +1. Match indexing strategy to write pattern, read pattern, range scans, update cost, and recovery needs. +2. Use log-structured storage, SSTables, and LSM-tree style approaches when write throughput and sequential writes are the dominant fit. +3. Use B-tree style indexes when ordered access, point lookups, and mature transactional behavior fit the workload. +4. Treat secondary indexes as separate data structures with write amplification, partitioning, and consistency costs. +5. Distinguish OLTP access from analytical workloads; do not force one layout to serve both well. +6. Use column-oriented storage, compression, sort order, materialized views, or cubes only when analytical access patterns justify them. +7. Keep in-memory assumptions explicit; memory residency is a performance strategy, not a durability model. + +--- + +## Consistency Rules + +1. Be explicit about read-after-write expectations. +2. Be explicit about staleness tolerance. +3. Be explicit about conflict handling. +4. Use strong consistency only where the product truly requires it. +5. Use eventual consistency intentionally, not accidentally. + +### Write Semantics +Document or encode: +- when a write is durable +- when it is visible +- whether readers may see stale data +- how conflicts are detected or resolved + +Anti-patterns (MUST NOT): +- “eventual consistency” used as a slogan instead of a contract +- stale-read bugs blamed on infrastructure with no product decision behind them +- no conflict model for concurrent updates + +--- + +## Idempotency and Replay Rules + +1. Handlers of commands, jobs, and events must tolerate retries where delivery or acknowledgment is uncertain. +2. Prefer deduplication keys or naturally idempotent state transitions. +3. Design processing to survive replay after crashes. +4. Never assume exactly-once delivery unless the system boundary truly provides it and the design proves it. + +Anti-patterns (MUST NOT): +- duplicate billing/order/send on retry +- handlers with non-repeatable side effects and no guard +- event processors depending on “it probably won't happen twice” + +--- + +## Ordering Rules + +1. Do not assume global order in distributed systems. +2. Require only the minimum ordering guarantees the business logic actually needs. +3. When ordering matters, define its scope: + - per key + - per stream + - per partition + - per record or entity whose history is being updated +4. Keep ordering-sensitive logic close to the key or stream that defines the order. + +Anti-patterns (MUST NOT): +- implicit reliance on total ordering +- out-of-order events corrupting state because no versioning or sequence policy exists +- parallel consumers updating the same key with no ordering plan + +--- + +## Event, Log, and Stream Rules + +1. Distinguish commands, events, and materialized views clearly. +2. Events describe facts that happened; commands request action. +3. Logs and streams are durable histories, not merely transport pipes. +4. Consumers must tolerate lag, duplicates, restart, and replay. +5. Derived projections must be rebuildable where feasible. + +### Event Design +- use stable identifiers +- include enough metadata for correlation and replay +- version payloads carefully +- keep semantics explicit + +Anti-patterns (MUST NOT): +- event payloads tied to one serializer or internal object layout +- projections that cannot be rebuilt +- assuming consumers keep up forever + +--- + +## Schema Evolution Rules + +1. Schemas will change; plan for it. +2. Version contracts intentionally. +3. Prefer backward- and forward-compatible changes where possible. +4. Keep old readers and writers in mind during rollout. +5. Distinguish internal refactors from contract changes. + +Anti-patterns (MUST NOT): +- breaking payloads or DB semantics without migration strategy +- reusing fields with new meaning +- silently changing enum or status semantics across services + +--- + +## Encoding and Data Flow Rules + +1. Choose encoding formats by compatibility needs, schema guarantees, readability, size, and language independence. +2. Do not rely on language-specific serialization for long-lived or cross-service data. +3. Treat JSON, XML, binary encodings, Thrift, Protocol Buffers, and Avro as contract choices with different schema-evolution tradeoffs. +4. Define reader and writer compatibility during rolling upgrades. +5. Keep database writes, service calls, and asynchronous messages explicit about who reads old and new formats during migration. +6. Avoid RPC designs that hide network failure, version skew, latency, or partial failure behind local-call syntax. + +--- + +## Partitioning and Locality Rules + +1. Keep data and work colocated by the key that most often drives consistency or aggregation. +2. Partition by a workload-relevant key, not by convenience alone. +3. Be explicit about hot-key risk and skew. +4. Design cross-partition operations carefully. + +Anti-patterns (MUST NOT): +- partitioning that makes every common query cross-node +- no plan for skew or hotspots +- requiring cross-partition transactions for ordinary operations + +--- + +## Replication Rules + +1. Choose leader-follower, multi-leader, or leaderless replication according to write topology, failure tolerance, latency, and conflict handling. +2. Be explicit about synchronous and asynchronous replication tradeoffs. +3. Define behavior during node outages, follower catch-up, failover, and reconfiguration. +4. Preserve read-your-writes, monotonic reads, and consistent prefix reads only when the product or workflow requires them and the design provides them. +5. Do not rely on quorum formulas without checking stale reads, sloppy quorums, hinted handoff, and concurrent writes. +6. Make conflict detection and resolution explicit for concurrent writes. + +--- + +## Transaction Rules + +1. Use local transactions where they solve a real consistency problem cleanly. +2. Avoid distributed transactions as a default coordination strategy. +3. When cross-boundary coordination is required, define the commit, recovery, reconciliation, and failure semantics explicitly. +4. Make atomicity scope explicit. + +### Isolation and Invariants +- Know whether read committed, snapshot isolation, serial execution, two-phase locking, or serializable snapshot isolation is required for the invariant. +- Protect against lost updates, write skew, and phantoms where application correctness depends on them. +- Do not accept weaker isolation for correctness-critical invariants without a deliberate design that preserves the invariant another way. + +Anti-patterns (MUST NOT): +- multi-system two-phase coordination by default +- side effects emitted outside transactional boundaries with no repair path +- pretending asynchronous side effects are atomic because they “usually happen” + +--- + +## Derived Data Rules + +1. Treat indexes, search copies, caches, and read models as derived data unless they are explicitly authoritative. +2. Derived data must be repairable, rebuildable, or re-syncable. +3. Know how lag affects user-visible behavior. +4. Keep derivation pipelines observable. + +Anti-patterns (MUST NOT): +- no way to rebuild projections +- no lag visibility +- mixing primary writes directly into derived stores with no ownership model + +--- + +## Distributed Fault, Clock, and Consensus Rules + +1. Treat network delay, packet loss, partitions, duplicated messages, and arbitrary pauses as normal distributed-system risks. +2. Do not infer remote failure or success from timeout alone. +3. Use monotonic clocks for measuring elapsed time; do not use wall clocks for ordering unless clock assumptions are explicit and safe. +4. Do not rely on synchronized clocks for correctness unless uncertainty bounds and failure behavior are part of the design. +5. Treat majority decisions, leases, locks, and leadership as assumptions that need a fault model. +6. Use linearizability only where a single up-to-date value is required and the availability/latency cost is acceptable. +7. Use total order broadcast, atomic commit, or consensus only when the coordination problem truly requires it. +8. Make membership and coordination-service dependencies explicit; they are part of the system design, not invisible plumbing. + +--- + +## Batch and Stream Processing Rules + +1. Design batch jobs so inputs, outputs, and intermediate state can be recomputed or recovered. +2. Keep external side effects out of replayable jobs unless idempotency is explicit. +3. Use MapReduce-style, dataflow, or high-level batch APIs according to join strategy, intermediate materialization, and operational needs. +4. Distinguish event time, processing time, and ingestion time in stream processing. +5. Define windowing, late data, joins, state storage, checkpoints, and fault tolerance for streams that affect correctness. +6. Treat change data capture, event sourcing, and log-based synchronization as ways to derive and propagate data, not as magic consistency. +7. Define at-most-once, at-least-once, or exactly-once processing guarantees for each source-to-sink path. + +--- + +## API and Service Boundary Rules + +1. Service boundaries must reflect data ownership and update semantics. +2. Do not split one tightly consistent business concept across many services casually. +3. Avoid chatty cross-service joins on hot paths. +4. Contracts must encode identifiers, versions, and failure semantics clearly. + +--- + +## Review Rules + +When reviewing code, actively look for: +- hidden assumptions about ordering +- hidden assumptions about exactly-once delivery +- lack of idempotency +- no source-of-truth ownership +- broken schema evolution practices +- no versioning or sequencing where concurrency matters +- side effects that cannot be repaired +- write paths that update several stores with unclear guarantees +- projections that cannot be rebuilt +- partitioning blind to locality or hotspots + +--- + +## Forbidden Patterns + +### Exactly-Once Wishful Thinking +- assuming a broker or queue magically prevents all duplicates +- writing non-idempotent handlers without safeguards + +### Hidden Consistency Contract +- readers and writers disagreeing on freshness requirements +- stale or conflicting behavior treated as incidental instead of product design + +### Uncoordinated Multi-Writes +- writing to several authorities in one operation with no atomicity or repair strategy +- side effects sent before durable state with no recovery path + +### Schema Drift by Accident +- changing payload meaning without versioning +- reusing fields for new concepts +- no rollout compatibility strategy + +--- + +## Code Generation Rules + +When generating code, default to: +1. explicit identifiers and ownership +2. explicit idempotency where retries or duplicates can happen +3. explicit versioning or conflict strategy where ordering matters +4. explicit distinction between authoritative and derived data +5. repairable or rebuildable downstream state +6. compatibility-aware schema changes +7. observability for lag, retries, and failures + +Avoid by default: +- assuming strict global order +- exactly-once promises with no proof +- writing the same fact into several places as if they were one transaction +- treating streams and queues as fire-and-forget + +--- + +## Testing Rules + +1. Test duplicate delivery handling. +2. Test out-of-order event or message handling where applicable. +3. Test replay safety. +4. Test conflict resolution or optimistic concurrency behavior. +5. Test schema compatibility when contracts evolve. +6. Test rebuild or repair of derived views where that capability exists. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Is the source of truth explicit? +- Are consistency expectations explicit? +- Is the code safe under retry or duplicate delivery? +- Is ordering dependency explicit and scoped? +- Can derived data be rebuilt or repaired? +- Is schema evolution considered? +- Is atomicity scope honest? +- Did we avoid exactly-once wishful thinking? +- Are service boundaries aligned with data ownership? +- Are lag and failure observable? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, prefer the design that: +1. makes data ownership explicit +2. makes consistency semantics explicit +3. survives retries, duplicates, and replay +4. supports evolution without silent breakage +5. treats distributed systems trade-offs honestly + +Do not hide distributed complexity behind local-looking code. diff --git a/programming-principles/references/designing-data-intensive-apps.mini.md b/programming-principles/references/designing-data-intensive-apps.mini.md new file mode 100644 index 0000000..85be022 --- /dev/null +++ b/programming-principles/references/designing-data-intensive-apps.mini.md @@ -0,0 +1,55 @@ +# OBEY Designing Data-Intensive Applications by Martin Kleppmann + +## When to use + +Use for systems where correctness depends on data ownership, consistency, durability, replication, partitioning, schema evolution, event flow, replay, or derived-data maintenance. + +## Primary bias to correct + +Do not design distributed data behavior as if every write, read, queue, cache, replica, clock, and downstream side effect were local, ordered, fresh, and exactly once. + +## Decision rules + +- Make core trade-offs explicit: source of truth, consistency expectation, retry behavior, duplicate and reordered work, partial failure, data evolution, and whether state is durable, cached, derived, or ephemeral. +- Treat crashes, partial writes, duplicate work, timeouts, stale reads, and unknown downstream success as normal inputs. Distinguish accepted, persisted, applied, and durable success. +- Describe load and performance with concrete request rates, data volume, access patterns, latency, throughput, percentiles, bottlenecks, contention, and tail behavior before changing architecture. +- Choose data models, query models, and ownership boundaries from relationships, access patterns, consistency needs, update locality, evolution pressure, and whether data is primary or derived. +- Match storage engines, indexes, and analytical layouts to write patterns, read patterns, range scans, recovery needs, write amplification, OLTP-vs-analytics separation, and memory-vs-durability assumptions. +- Treat indexes, caches, search copies, read models, materialized views, and denormalized copies as derived data with explicit propagation, lag, observability, repair, and rebuild paths. +- Define write semantics: when a write is durable, when it is visible, whether stale reads are allowed, which conflicts can happen, and how conflicts are detected or resolved. +- Make commands, jobs, events, batch jobs, and stream processors safe under retry and replay with deduplication keys, naturally idempotent transitions, or an explicit transactional recovery contract. +- Preserve only the ordering the business logic actually needs. Scope it per key, stream, partition, record, entity history, or stronger contract, and keep ordering-sensitive logic close to that scope. +- Separate commands, events, durable logs, streams, and materialized views. Events describe facts; consumers must tolerate lag, duplicates, restart, replay, stable identifiers, correlation metadata, and versioned payloads. +- Design schemas, encodings, APIs, messages, events, and database changes as evolving contracts across old readers, old writers, old data, in-flight messages, rolling upgrades, and cross-service formats. +- Choose replication topology from write topology, latency, failure tolerance, lag, failover, reconfiguration, conflict handling, read-your-writes, monotonic-read, consistent-prefix, quorum, and convergence needs. +- Partition by workload-relevant locality and consistency keys, with hot-key, skew, routing, secondary-index, rebalancing, and cross-partition-operation costs explicit. +- Match transactions and isolation to invariants. Make atomicity scope, commit behavior, recovery, reconciliation, lost-update, write-skew, phantom, and side-effect repair semantics explicit. +- Treat network delay, packet loss, partitions, duplicate messages, pauses, stale leaders, timeouts, wall-clock uncertainty, leases, locks, majorities, and leadership as assumptions needing a fault model. +- Use linearizability, total order broadcast, atomic commit, or consensus only where the coordination problem truly requires agreement and the availability or latency cost is acceptable. +- Make batch and stream processing recomputable and recoverable: define inputs, outputs, intermediate state, checkpoints, external side effects, event time, processing time, ingestion time, windows, late data, joins, and source-to-sink guarantees. +- Align service boundaries with data ownership and update semantics. Do not casually split one tightly consistent business concept across services or put chatty cross-service joins on hot paths. + +## Trigger rules + +- When changing a write path, state the source of truth, consistency boundary, durability point, visibility point, downstream effects, rollback or repair path, and behavior after timeout or unknown success. +- When adding or changing a cache, index, projection, search copy, read model, warehouse, or denormalized field, define ownership, propagation, staleness, write cost, lag visibility, rebuild, and repair. +- When changing a schema, API, message, event, enum, status, or payload meaning, plan compatibility for old readers, old writers, old stored data, old messages, new writers, rollout, and migration. +- When adding retries, jobs, consumers, queues, CDC, event sourcing, stream processors, or replayable batch work, prove duplicate, replay, ordering, retention, side-effect, and recovery safety. +- When routing reads to replicas or using asynchronous replication, identify read-your-writes, monotonic-read, consistent-prefix, staleness, catch-up, failover, and conflict expectations before allowing the read. +- When partitioning data or work, test the ordinary query path for locality, skew, hot keys, routing metadata, rebalancing cost, secondary-index behavior, and cross-partition coordination. +- When choosing transaction isolation or weakening consistency, map each anomaly to the invariant it can break and add serializable isolation, locks, compare-and-set, versioning, reconciliation, or another compensating design where needed. +- When using timestamps, leases, locks, leadership, majority decisions, coordination services, or consensus-like mechanisms, define the clock assumption, quorum/session semantics, stale-authority behavior, and fencing. +- When reviewing or testing data-intensive code, look specifically for hidden source-of-truth ownership, missing idempotency, accidental exactly-once assumptions, unscoped ordering, schema drift, unrebuildable projections, unclear multi-writes, and unobservable lag or failure. + +## Final checklist + +- Source of truth and derived representations are explicit. +- Consistency expectations, durability points, visibility points, staleness, and conflict rules are concrete. +- Retries, duplicate delivery, replay, reordering, timeouts, crashes, and unknown success are handled. +- Schemas, encodings, APIs, messages, events, enums, and statuses evolve safely across mixed versions. +- Storage, indexing, replication, partitioning, routing, and analytical layouts match the actual workload. +- Transaction isolation and coordination choices protect the named invariants. +- Events, logs, streams, batch jobs, and projections are replayable or have explicit repair paths. +- Service boundaries follow data ownership and update semantics. +- Lag, retries, failures, rebuilds, and repair paths are observable. +- The design avoids exactly-once wishful thinking and hidden distributed-system contracts. diff --git a/programming-principles/references/domain-driven-design-distilled.full.md b/programming-principles/references/domain-driven-design-distilled.full.md new file mode 100644 index 0000000..9d52e0a --- /dev/null +++ b/programming-principles/references/domain-driven-design-distilled.full.md @@ -0,0 +1,317 @@ +# OBEY Domain-Driven Design Distilled by Vaughn Vernon + +## Purpose + +This repository follows **Domain-Driven Design Distilled**: +use the smallest effective set of DDD practices to model the business meaning clearly and deliver results quickly. + +All code generation, edits, and reviews must optimize for: +- clear business language +- explicit bounded contexts +- focus on core domain complexity +- selective use of tactical DDD patterns +- practical implementation over ceremony +- collaboration between model and software design + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Use DDD where it clarifies complex business software. +Do not turn DDD into ritual. + +When uncertain: +1. identify the business capability or subdomain +2. decide whether it is core, supporting, or generic +3. define the bounded context +4. use the local ubiquitous language +5. apply only the tactical patterns that actually earn their cost + +Reject both extremes: +- no modeling when the domain is complex +- full-blown DDD ceremony when the problem is simple + +--- + +## Adoption Fit and Modeling Investment + +1. Use DDD when domain complexity, language ambiguity, business differentiation, or integration risk justify the modeling effort. +2. Do not apply full tactical DDD to simple CRUD, generic subdomains, or problems whose complexity is mainly technical. +3. Let business drivers decide where modeling effort goes. +4. Reassess the model when the core business concern drifts, terms stop matching code, or supporting complexity hides the core. +5. Use scenarios and acceptance tests to validate that the model expresses real business behavior. + +--- + +## Strategic Rules + +### Start with Subdomains +Classify major areas as: +- core domain +- supporting subdomain +- generic subdomain + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Invest the most design effort in the core domain. +2. Keep supporting and generic subdomains simpler unless complexity proves otherwise. +3. Do not waste the best modeling effort on commodity concerns. + +### Define Bounded Contexts Early +1. Every meaningful model lives inside a bounded context. +2. A bounded context owns its language, rules, and model semantics. +3. The same term may mean different things in different contexts. +4. Code structure must reflect context boundaries. + +### Use Context Mapping +1. Make context relationships explicit. +2. Translate where meanings differ. +3. Own integration contracts deliberately. +4. Protect the local model from foreign language. + +Anti-patterns (MUST NOT): +- one model reused across billing, identity, catalog, fulfillment, and support +- shared domain classes with subtly different meanings +- context boundaries documented but ignored in code + +--- + +## Context Relationship Rules + +Choose context relationships deliberately: + +- USE Partnership only when teams can coordinate closely and share planning burden. +- USE Shared Kernel only for a small stable overlap with joint ownership and tests. +- USE Customer/Supplier when the upstream team can plan for downstream needs. +- USE Conformist when adopting the upstream model is cheaper and safer than translation. +- USE Anticorruption Layer when a foreign model would corrupt the local language. +- USE Open Host Service when many clients need a stable protocol into one context. +- USE Published Language when multiple systems need a documented interchange model. +- USE Separate Ways when integration cost is higher than shared capability value. +- TREAT Big Ball of Mud as a context to contain and translate around, not as a model to spread. + +Anti-patterns (MUST NOT): +- claiming independent modeling while conforming silently +- using Shared Kernel without governance +- calling integration an anticorruption layer when no translation exists + +--- + +## Integration Style Rules + +1. Use RPC only when request/response coupling, latency, versioning, and failure semantics are acceptable. +2. Use REST resources as application-facing representations, not as leaked aggregate internals. +3. Use messaging when asynchronous coordination fits the business process and consumers can handle lag, duplicates, and ordering limits. +4. Decide whether domain events should carry enough information for consumers or require query-back. +5. Keep integration contracts separate from internal models. +6. Test translations at context boundaries. + +--- + +## Ubiquitous Language Rules + +1. Use domain terms from the current bounded context in code, tests, commands, events, and conversations. +2. One concept gets one term. +3. One term must not carry multiple meanings inside one context. +4. Rename code when understanding improves. +5. Prefer domain names over technical placeholders. + +Avoid: +- technical placeholders that conceal a business concept +- names imported from another bounded context without translation +- generic helper or utility names that end up carrying domain decisions + +--- + +## Tactical Pattern Rules + +### Entities +Use entities when identity and lifecycle matter. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Entities must have explicit identity. +2. Entities must protect meaningful state transitions. +3. Do not expose unrestricted state changes by default. + +### Value Objects +Use value objects aggressively when a primitive hides meaning. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Value objects are immutable by default. +2. They validate themselves. +3. They make code read in domain language. + +### Aggregates +Use aggregates only where invariants require a consistency boundary. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Keep aggregates small. +2. Protect invariants through the aggregate root. +3. Reference other aggregates by identity. +4. Avoid loading large object graphs. + +### Domain Events +Use domain events for meaningful facts. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Name events in the past tense. +2. Use events when they clarify collaboration or integration. +3. Do not publish trivial noise for every field change. + +## Aggregate Minimalism Rules + +1. Do not create large aggregates to make navigation convenient. +2. Default to smaller boundaries. +3. Default to eventual consistency across aggregates. +4. Use IDs for references across aggregate boundaries. +5. One transaction should usually change one aggregate. + +Anti-patterns (MUST NOT): +- aggregate designed around a screen +- one request loading and mutating a whole graph +- aggregate roots exposing mutable children directly + +--- + +## Application Service Rules + +1. Application services coordinate use cases. +2. They load aggregates, call domain behavior, save results, and trigger integration work. +3. They must not become the real domain model. +4. They should stay thin enough that the model still carries meaning. + +Anti-patterns (MUST NOT): +- all business decisions in application services +- controllers duplicating application orchestration +- application services shaped only by transport + +--- + +## Architecture and Infrastructure Rules + +1. Infrastructure is a detail. +2. Keep frameworks, persistence mechanics, REST resources, transport formats, and other technology concerns out of the domain model. +3. Persist aggregates without letting persistence define the model. +4. Translate transport and integration data at the boundary. + +Anti-patterns (MUST NOT): +- persistence-first modeling +- reusing transport objects as domain objects +- domain methods depending on framework types + +--- + +## Collaboration Rules + +1. Keep names close to real business terms. +2. Prefer code that teaches the model to a reader. +3. Make domain assumptions explicit in names, tests, and events. +4. Where a concept is fuzzy, do not hide the ambiguity behind technical abstractions. + +Anti-patterns (MUST NOT): +- generic code that could belong to any business +- unexplained status codes and flags with domain meaning +- enums and booleans where a richer concept is needed + +--- + +## Practicality Rules + +1. Use the least expensive pattern that honestly models the problem. +2. Simple domains may use simple services and data structures. +3. Once invariants, lifecycle, and language complexity rise, strengthen the model. +4. Prefer incremental improvement over massive design overhauls. + +Anti-patterns (MUST NOT): +- dismissing DDD because not every module needs it +- over-modeling a generic subsystem +- introducing aggregates and events before knowing why + +--- + +## Accelerated Modeling and Project Rules + +1. Use event storming or similar collaborative modeling when workflow, events, commands, policies, or team language are unclear. +2. Timebox modeling work so it improves implementation instead of becoming detached analysis. +3. Use modeling spikes to reduce uncertainty before committing to a model shape. +4. Track modeling debt when code and language are known to be imperfect but intentionally deferred. +5. Involve domain experts in scenario walkthroughs, terminology decisions, and acceptance criteria. +6. Estimate DDD work by modeling uncertainty, integration risk, and implementation cost, not only by feature count. +7. Treat team skill and access to domain experts as constraints on how much DDD ceremony the project can sustain. + +--- + +## Code Generation Rules + +When generating code, use this default order: +1. identify the subdomain +2. identify the bounded context +3. write names in the local ubiquitous language +4. decide whether a concept is entity, value object, aggregate, service, repository, or event +5. choose the smallest tactical pattern that fits +6. isolate infrastructure at boundaries +7. keep context translation explicit + +Default avoidance: +- giant shared domain packages +- service-centric fake DDD +- technical names replacing domain language +- full tactical DDD in trivial modules + +--- + +## Review Rules + +When reviewing code, actively look for: +- missing subdomain classification +- missing bounded context ownership +- context bleeding +- no context translation where meanings differ +- primitive obsession +- anemic entities +- no value objects where concepts repeat +- aggregates too large +- services containing all behavior +- excessive ceremony in simple modules +- no modeling at all in complex modules + +--- + +## Testing Rules + +1. Domain tests must read in the ubiquitous language. +2. Test value objects for validation and behavior. +3. Test entities and aggregates for valid and invalid transitions. +4. Test application services for orchestration, not for all domain rules. +5. Test context translation where external or foreign models exist. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Is the subdomain/core importance understood? +- Is the bounded context explicit? +- Is the ubiquitous language visible in the code? +- Did we use only the tactical patterns that genuinely help? +- Are aggregates small and focused on invariants? +- Are value objects used where they clarify meaning? +- Are application services coordinating rather than owning all domain logic? +- Did we keep infrastructure out of the model? +- Did we avoid DDD theater and over-modeling? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the option that: +1. sharpens the business language +2. clarifies the bounded context +3. models real complexity honestly +4. avoids unnecessary ceremony +5. keeps the design practical enough to deliver + +Use DDD selectively, but seriously. diff --git a/programming-principles/references/domain-driven-design-distilled.mini.md b/programming-principles/references/domain-driven-design-distilled.mini.md new file mode 100644 index 0000000..07da43f --- /dev/null +++ b/programming-principles/references/domain-driven-design-distilled.mini.md @@ -0,0 +1,36 @@ +# OBEY Domain-Driven Design Distilled by Vaughn Vernon + +## When to use + +Use as a lightweight introduction to DDD when you need the benefits of strategic and tactical DDD without excessive ceremony or when onboarding a team to DDD concepts quickly. + +## Primary bias to correct + +DDD is not all or nothing. Start with strategic design — bounded contexts, ubiquitous language, subdomains — and apply tactical patterns only where the complexity lives. + +## Decision rules + +- Start with strategic design before tactical patterns. Name the Bounded Context, establish the Ubiquitous Language, and identify Core, Supporting, and Generic subdomains. +- A Bounded Context owns its model; no other context shares it. Translate between contexts with explicit mapping. +- Use Context Mapping to document relationships between contexts: Partnership, Shared Kernel, Customer-Supplier, Conformist, Anticorruption Layer, Open Host Service, Published Language, Separate Ways, Big Ball of Mud. +- Keep Aggregates small. One Aggregate per transaction. Reference by identity. Design around consistency boundaries, not data relationships. +- Use Domain Events for integration across aggregates or contexts. Keep them past-tense and meaningful to the business. +- Apply tactical patterns (Entity, Value Object, Aggregate, Repository, Domain Service, Domain Event) only where the complexity warrants them. Simple CRUD does not need DDD. +- Use Application Services to coordinate use cases; keep them thin. Domain decisions stay in the domain model. +- Repositories are for Aggregate persistence; they return domain objects. Do not expose table structures or ORM queries. +- Value Objects are immutable, self-validating, and compare by value. Replace primitives for meaningful domain concepts. +- Use Core Domain to focus the richest modeling. Spend less on Generic and Supporting subdomains. + +## Trigger rules + +- When a term means different things in different parts of the system, define a Bounded Context boundary. +- When two contexts exchange data, define the context mapping relationship and translation mechanism. +- When a transaction spans multiple aggregates, challenge the aggregate boundaries or use eventual consistency. +- When business logic appears in services or controllers, push it into the domain model. + +## Final checklist + +- Bounded Context named and documented? +- Ubiquitous Language established and used in code? +- Aggregates small, one per transaction, referenced by identity? +- Core Domain identified and getting richer modeling? diff --git a/programming-principles/references/domain-driven-design.full.md b/programming-principles/references/domain-driven-design.full.md new file mode 100644 index 0000000..5de1f8b --- /dev/null +++ b/programming-principles/references/domain-driven-design.full.md @@ -0,0 +1,979 @@ +# OBEY Domain-Driven Design by Eric Evans + +## Purpose + +This repository follows **Domain-Driven Design**. +All code generation, modification, and review must optimize for: +- a precise model of the domain +- a shared ubiquitous language +- explicit bounded contexts +- rich domain behavior where complexity exists +- disciplined aggregate design +- protection of invariants +- clear separation between domain model and supporting infrastructure + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +When uncertain, prefer the option that makes the **domain model clearer**. + +Do not optimize primarily for: +- fewer files +- generic reuse +- CRUD convenience +- object-relational mapping convenience +- delivery-layer convenience +- framework conventions +- short-term speed at the cost of model clarity + +The model must serve the business meaning first. + +--- + +## What DDD Means in This Repository + +DDD here does **not** mean: +- adding layers for ceremony +- renaming service classes to sound sophisticated +- wrapping CRUD in verbose abstractions +- creating entities with only fields and setters +- turning every concept into an aggregate +- introducing every DDD pattern everywhere +- overengineering simple subdomains + +DDD here **does** mean: +- building code around business concepts +- expressing rules in domain language +- making context boundaries explicit +- protecting invariants with the model +- modeling identity, value, lifecycle, and consistency deliberately +- translating explicitly across context boundaries +- simplifying aggressively outside the core domain + +--- + +## Knowledge Crunching and Deep Models + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Treat the model as discovered, not invented from technical structure. +2. Before adding abstractions, identify what domain experts would call the concept. +3. When requirements are ambiguous, look for missing domain distinctions instead of forcing generic names. +4. Let awkward code, contradictory language, and repeated conditionals trigger deeper modeling. +5. Update names and boundaries when new domain insight appears. + +### Required behavior (MUST) +- Ask what business rule, policy, lifecycle, or invariant the code is expressing. +- Prefer a deeper model that clarifies behavior over a shallow model that merely stores data. +- Capture newly discovered concepts in names, tests, APIs, and modules. +- Treat refactoring as part of model discovery, not just code cleanup. + +### Anti-patterns (MUST NOT) +- Starting from database tables and calling the result the domain model +- Preserving vague names after discovering sharper domain language +- Hiding domain complexity behind `type`, `status`, or `metadata` fields +- Treating the first model as final + +--- + +## Model-Driven Design + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. The implemented design must reflect the model used in discussion. +2. If the model cannot guide code, refine the model or the code until they align. +3. Domain objects must represent behavior and meaning, not just persistence state. +4. Keep modelers close to implementation. Do not separate analysis from coding so far that the model becomes theoretical. + +### Required behavior (MUST) +- Make important model concepts visible in classes, functions, modules, tests, and interfaces. +- Prefer executable examples and tests over disconnected documentation. +- Keep diagrams and documents lightweight, current, and tied to code. +- Use explanatory models only to teach or reason; do not confuse them with the implementation model unless they are intended to drive code. + +### Anti-patterns (MUST NOT) +- A design document that uses different names than the code +- Analysts producing models that developers cannot or do not implement +- Code that follows framework conventions while ignoring the domain model +- Diagrams that become authoritative after the code and domain understanding have changed + +--- + +## Breakthrough and Deeper Insight + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Expect useful models to change after deeper insight. +2. Treat a breakthrough model as a candidate for deliberate refactoring, not as churn. +3. When a better model appears, compare its explanatory power against migration cost. +4. Preserve working behavior while moving toward the deeper model in safe steps. + +### Required behavior (MUST) +- Look for concepts that simplify many special cases at once. +- Prefer changes that make future business rules easier to express. +- Use awkwardness, contradictions, and repeated failed attempts as signals that the model is shallow. +- Keep focus on the basics when the model becomes too elaborate. + +### Anti-patterns (MUST NOT) +- Rejecting a better model only because the current one already works +- Big-bang rewrites when incremental migration is possible +- Elaborate abstractions that do not improve domain insight + +--- + +## Making Implicit Concepts Explicit + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Listen for domain language that is not represented in code. +2. Scrutinize awkward APIs, repeated branches, and contradictory names. +3. Read domain references, policies, regulations, and prior art when available. +4. Try multiple model shapes before settling on one for complex concepts. + +### Required behavior (MUST) +- Promote hidden constraints, policies, and processes into explicit domain concepts. +- Name the concept before choosing the implementation form. +- Prefer clear domain objects over anonymous helpers when behavior has business meaning. + +### Anti-patterns (MUST NOT) +- Burying business rules in comments +- Treating contradictions as edge cases instead of modeling signals +- Keeping vague technical flags after discovering the real concept + +--- + +## Ubiquitous Language + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Use the exact business terms used by domain experts inside a bounded context. +2. One concept must have one name inside a bounded context. +3. One name must not mean different concepts inside a bounded context. +4. Method names, test names, and modules must use the same vocabulary as the domain. +5. Rename code when the domain understanding improves. + +### Required behavior (MUST) +- Prefer names from the active bounded context; in a shipping model, terms such as `Cargo`, `Itinerary`, `Handling Event`, and `Route Specification` should appear directly. +- Prefer operation names that express the domain action, such as changing a cargo destination, adding a handling event, checking allocation, or applying an overbooking policy. +- Avoid technical placeholders when a precise domain term exists. +- Avoid names imported from another bounded context without translation. + +### Anti-patterns (MUST NOT) +- Using technical names where the business has precise names +- Using synonyms for the same concept in the same context +- Reusing the same term for different meanings because it is convenient +- Keeping bad names because they already exist in the database + +--- + +## Communication Artifacts + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Documents and diagrams must support the ubiquitous language. +2. Written design material must be short enough to stay maintained. +3. Executable tests are preferred for rules that can be verified. +4. Diagrams should emphasize boundaries, relationships, invariants, and lifecycle over class inventory. + +### Required behavior (MUST) +- Use examples and scenario tests as living documentation. +- Keep glossary-like explanations close to the bounded context they describe. +- Update documents when terminology or context boundaries change. + +### Anti-patterns (MUST NOT) +- Long design documents that drift away from code +- Diagrams that show every class but hide the model's meaning +- Documentation that introduces vocabulary not used by code or tests + +--- + +## Scenario Walkthroughs + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Validate the model by walking through real application scenarios. +2. Use scenarios to test whether entities, value objects, aggregates, repositories, and factories collaborate naturally. +3. When a scenario feels procedural or awkward, look for missing model concepts or wrong boundaries. +4. Revisit aggregate and module boundaries after scenario walkthroughs reveal pressure. + +### Required behavior (MUST) +- Prefer examples that exercise real business decisions, not only CRUD paths. +- Use scenarios to verify object creation, lifecycle transitions, and cross-context translation. +- Let performance tuning follow model clarity; do not distort the model prematurely for optimization. + +### Anti-patterns (MUST NOT) +- Designing model elements only in isolation +- Treating scenario code as an afterthought after infrastructure is complete +- Optimizing persistence paths before the model expresses the business correctly + +--- + +## Layered Architecture and Smart UI + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Keep the domain layer as the place where the model and business rules live. +2. Separate presentation, application coordination, domain behavior, and infrastructure when the domain is complex enough to need model-driven design. +3. Let application code coordinate tasks without owning domain decisions. +4. Keep infrastructure services and framework concerns outside domain objects. +5. Use Smart UI only for simple applications where rich domain abstraction, reuse, integration, and deep business rules are not important. + +### Anti-patterns (MUST NOT) +- UI screens, database tables, or framework annotations defining the domain vocabulary +- UI, application coordination, jobs, or scripts carrying domain rules while domain objects stay passive +- choosing Smart UI when the business behavior needs reuse or abstraction + +--- + +## Bounded Contexts + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Every substantial domain area must belong to a clearly identified bounded context. +2. A model is valid only inside its own bounded context. +3. Concepts from another context must not be imported directly as if they were native. +4. Translation across contexts must be explicit. +5. Shared models across multiple contexts are forbidden unless intentionally governed as a shared kernel. + +### Required behavior (MUST) +- Keep package, module, or namespace ownership explicit. +- Model `Customer` separately in different contexts if meanings differ. +- Prefer context-specific contracts, IDs, published language, or anticorruption layers over shared classes. + +### Anti-patterns (MUST NOT) +- One giant company-wide domain model +- A `shared/domain` package that erases boundaries +- Copying foreign terms into the local model without translation +- Reusing one aggregate type across unrelated contexts + +--- + +## Strategic Design + +### Core Domain +1. Invest the most care in the core domain. +2. Protect the core domain from foreign models, vendor schemas, and generic abstractions. +3. Keep the core domain expressive even if supporting areas are simpler. + +### Supporting and Generic Subdomains +1. Do not over-model commodity concerns. +2. Use simpler models where business complexity is low. +3. Save the richest modeling effort for the parts that matter strategically. + +### Context Mapping +1. Integration relationships must be visible in code. +2. Ownership of translation must be explicit. +3. Upstream and downstream influence must be reflected in adapters and contracts. + +### Anti-patterns (MUST NOT) +- Spending more design effort on plumbing than on the core domain +- Modeling authentication utilities more richly than the pricing engine +- Allowing a legacy system vocabulary to dominate the core model + +--- + +## Model Integrity Patterns + +### Continuous Integration Within a Context +1. A bounded context must keep one internally consistent model. +2. Team members working in the same context must integrate terminology and model changes continuously. +3. Conflicting meanings inside one context must be resolved quickly through naming, tests, and refactoring. + +### Context Relationships +Use context relationship patterns intentionally: +- `Shared Kernel` only for a small, jointly governed model subset. +- `Customer/Supplier` when an upstream team commits to downstream needs. +- `Conformist` only when adopting the upstream model is cheaper than translating it. +- `Anticorruption Layer` when protecting the local model from a foreign or legacy model. +- `Separate Ways` when integration cost is higher than shared capability value. +- `Open Host Service` when a context exposes a stable integration protocol. +- `Published Language` when contexts need a documented exchange language. + +### Context Transformations +1. Move from Separate Ways to Shared Kernel only when the overlap is small, valuable, and worth coordination. +2. Move from Shared Kernel to Continuous Integration only when teams are ready to share one model frequently. +3. Phase out legacy systems by protecting the new model and replacing responsibilities incrementally through translations. +4. Evolve Open Host Service toward Published Language when interchange stability is needed beyond one service. + +### Required behavior (MUST) +- Make context maps visible in package structure, integration adapters, documentation, or tests. +- Name adapters after the relationship they implement when that improves clarity. +- Keep foreign model terms out of the local core unless deliberately accepted as conformist. + +### Anti-patterns (MUST NOT) +- Accidental shared kernels with no ownership rules +- Calling every integration an anticorruption layer without translation +- Letting upstream APIs silently define downstream domain language +- Treating context mapping as architecture documentation only, not code structure + +--- + +## Distillation + +### Core Domain +1. Identify the part of the model that creates strategic advantage. +2. Put the strongest modeling effort and cleanest design into that core. +3. Do not bury the core under generic mechanisms, infrastructure, or broad shared abstractions. + +### Distillation Patterns +Use these patterns when they clarify priority and investment: +- `Domain Vision Statement` for a short statement of the core model's purpose. +- `Highlighted Core` to mark the most important elements inside a larger model. +- `Generic Subdomain` for commodity capabilities that do not deserve rich custom modeling. +- `Cohesive Mechanism` for technical mechanisms that can be separated from domain policy. +- `Segregated Core` when the core is tangled with supporting concerns. +- `Abstract Core` when related specialized models need a stable conceptual foundation. + +### Required behavior (MUST) +- Make the core domain easy to find in code. +- Keep supporting and generic subdomains simpler unless their complexity is real. +- Choose refactoring targets based on strategic importance, not just local messiness. + +### Anti-patterns (MUST NOT) +- Spending equal modeling effort on every subsystem +- Letting technical mechanisms dominate the core model +- Hiding the core behind generic shared packages +- Refactoring peripheral code while the core remains unclear + +--- + +## Large-Scale Structure + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Large-scale structure should help people understand the system, not freeze it. +2. Let structure evolve as the model evolves. +3. Use a guiding structure only when it reduces cognitive load across contexts. +4. Keep the structure minimally restrictive. + +### Patterns +Use these patterns deliberately: +- `Evolving Order` when structure must emerge through iterative modeling. +- `System Metaphor` only when it genuinely clarifies the model. +- `Responsibility Layers` when responsibilities naturally stratify across the system. +- `Knowledge Level` when rules or policies must be represented explicitly and changed by configuration or data. +- `Pluggable Component Framework` when variation points are stable and worth formalizing. + +### Required behavior (MUST) +- Combine bounded contexts, distillation, and large-scale structure into one coherent strategy. +- Revisit structure when it no longer fits the model. +- Prefer communication and self-discipline over heavy structural machinery where possible. + +### Anti-patterns (MUST NOT) +- A master plan that blocks model learning +- A metaphor that sounds clever but misleads design decisions +- Overly restrictive layers that fight the domain +- Framework architecture masquerading as domain structure + +--- + +## Strategic Decision Making + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Assess the current model and context map before prescribing a strategic structure. +2. Decide who owns strategic design choices explicitly. +3. Let application development inform strategy; do not impose strategy detached from implementation feedback. +4. Architecture teams must stay customer-focused and model-focused, not framework-focused. +5. Strategic decisions must remain revisable as domain understanding changes. + +### Required behavior (MUST) +- Combine bounded contexts with distillation and large-scale structure when system complexity requires it. +- Make strategy visible enough that teams can coordinate without a rigid master plan. +- Keep technical frameworks subordinate to the domain strategy. +- Treat strategic design as team decision-making, not just diagram production. + +### Anti-patterns (MUST NOT) +- A top-down master plan that ignores model learning +- Strategy owned by people disconnected from implementation +- Technical architecture decisions presented as domain strategy +- Context maps, core-domain decisions, and large-scale structures that are never revisited + +--- + +## Entities + +### Use entities when +- identity matters over time +- lifecycle matters +- continuity matters beyond current attributes +- business rules depend on “which one” rather than only “what value” + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Entities must have explicit identity. +2. Entities must protect their own valid state transitions. +3. Entities must expose intention-revealing behavior, not arbitrary state changes. +4. Entities must not be treated as passive records in behavior-rich domains. + +### Required behavior (MUST) +- Prefer methods that tell an entity what domain action to perform. +- In a shipping model, express destination changes and handling-event additions as model operations rather than procedural data edits. +- Hide direct state changes behind methods that encode domain meaning. +- Keep identity stable and explicit. + +### Anti-patterns (MUST NOT) +- Public setters for every field +- Application services manually editing all entity state +- UI or application code deciding which transitions are valid +- Entities used only as persistence shells + +--- + +## Value Objects + +### Use value objects when +- a concept is defined by attributes rather than identity +- the concept has validation rules +- the concept has behavior +- passing a primitive would hide meaning + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Value objects must be immutable by default. +2. Construction must guarantee validity. +3. Equality must be by value, not by identity. +4. Validation for the concept should live inside the value object. +5. Replace primitive obsession aggressively where the concept matters. + +### Required behavior (MUST) +- Use value objects for descriptive concepts whose attributes together carry domain meaning. +- Name value objects after the domain concept, not the primitive representation. +- Keep validation and side-effect-free operations for the value near the value itself. +- Replace raw primitives when a named quantity, range, code, measurement, or descriptive whole value matters to the model. + +### Anti-patterns (MUST NOT) +- Repeating the same value validation across handlers +- Passing raw primitives for named domain quantities, ranges, codes, or measurements +- Passing raw strings for meaningful identifiers +- Letting invalid values exist temporarily without an explicit model for incompleteness + +--- + +## Associations and Modules + +### Associations +1. Model associations only when they support behavior or meaning. +2. Prefer simpler, more navigable associations over fully connected object graphs. +3. Reduce bidirectional associations unless the domain requires them. +4. Reference other aggregates by identity unless direct object traversal is part of an invariant boundary. + +### Modules +1. Modules must communicate domain concepts and bounded context ownership. +2. Organize modules around model meaning, not only technical layers. +3. Keep related concepts together when they change together. +4. Avoid infrastructure-driven packaging that hides the domain. + +### Required behavior (MUST) +- Use package names that match the ubiquitous language. +- Keep model concepts discoverable from the directory structure. +- Split modules when different concepts evolve independently. + +### Anti-patterns (MUST NOT) +- `models`, `services`, `utils`, and `helpers` as the dominant structure +- Associations created only because the persistence mechanism supports them +- Object graphs that make aggregate boundaries invisible +- Modules grouped by technical artifact while domain concepts are scattered + +--- + +## Aggregates + +### Purpose +Aggregates are **consistency boundaries**, not just object graphs. + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Design aggregates around invariants that must be consistent immediately. +2. Keep aggregates as small as possible. +3. All modifications that affect aggregate invariants must go through the aggregate root. +4. Reference other aggregates by identity unless stronger consistency is truly required. +5. Keep transactional boundaries aligned with invariants; do not expand transactions across aggregates merely for convenience. + +### Required behavior (MUST) +- Put invariant-protecting methods on the aggregate root. +- Keep internal members encapsulated. +- Handle consistency across aggregate boundaries deliberately when the invariant does not belong inside one aggregate. +- Model transactional boundaries deliberately. + +### Anti-patterns (MUST NOT) +- Large graph aggregates built for object-relational mapping convenience +- Aggregate roots exposing internal collections for arbitrary external state changes +- Transactions modifying many aggregates because object references make it easy +- Confusing parent-child object structure with aggregate boundaries + +--- + +## Domain Services + +### Use a domain service only when +- the behavior is domain-significant +- the behavior does not naturally belong on one entity or value object +- the operation still belongs to the ubiquitous language + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. A domain service must express a domain concept, not a technical convenience. +2. If behavior clearly belongs to an entity or value object, keep it there. +3. Do not move behavior into services merely to keep entities thin. + +### Required behavior (MUST) +- Domain services should sound like the business. +- Domain services should coordinate domain concepts, not infrastructure details. + +### Anti-patterns (MUST NOT) +- a single `*Service` containing all rules for a model area +- a service containing dozens of unrelated policies +- “Domain services” that are only wrappers for repositories or external technical clients +- Extracting behavior from entities prematurely + +--- + +## Explicit Concepts and Specifications + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Make implicit constraints explicit in the model. +2. Model domain processes as domain objects when the process has business meaning. +3. Use specifications for named, combinable business rules that answer whether something satisfies a criterion. +4. Keep specifications in domain language, not query language. + +### Required behavior (MUST) +- Extract repeated conditionals into named domain concepts. +- Prefer named concepts such as route specifications, overbooking policies, or allocation rules over anonymous boolean expressions. +- Keep persistence querying concerns separate from domain specifications unless the project deliberately provides translation. +- Use specifications to clarify policy, validation, selection, and compatibility rules. + +### Anti-patterns (MUST NOT) +- Complex business conditions duplicated across services +- Boolean flags that hide a named domain rule +- Specifications that are just persistence query builders +- Processes represented only as scripts or transaction handlers when the business treats them as concepts + +--- + +## Repositories + +### Purpose +Repositories provide access to aggregates as part of the model. + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Repositories exist for aggregate roots, not for every table. +2. Repository interfaces must be defined by the domain or application model that uses them. +3. Repositories must return domain objects or domain-oriented results. +4. Repository contracts must reflect intent where useful. +5. Repositories must not become universal query utilities. + +### Required behavior (MUST) +- Use repositories to reconstitute and persist aggregates. +- Keep infrastructure mapping hidden behind the repository implementation. +- Prefer focused repository methods over giant generic CRUD interfaces when domain intent matters. +- Keep reconstitution paths separate from normal creation paths when that protects invariants. +- Make client code independent of repository implementation details, while repository implementers understand those details. +- Express query criteria as specifications or model concepts when the criteria are domain rules. +- Return domain objects or collections without exposing database structure. + +### Anti-patterns (MUST NOT) +- Generic repository abstractions that erase domain meaning +- Returning persistence records directly into the domain +- Putting business rules into repository implementations +- Creating one repository per table with no relation to aggregate design +- Letting relational database design dictate object identity, associations, or aggregate boundaries + +--- + +## Factories + +### Use factories when +- creation is complex +- construction has business rules +- valid creation requires multiple collaborating values +- the creation itself has domain meaning + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Factories must create valid objects. +2. Factories must encode domain creation rules, not technical object assembly. +3. Clients and mappers must not contain business construction logic. +4. Choose the factory site where creation ownership fits the model. +5. Use constructors directly when creation is simple, intention-revealing, and does not expose complex invariants. +6. Treat reconstitution from storage separately from new-object creation. + +### Anti-patterns (MUST NOT) +- Building invalid objects first and fixing them later +- Letting endpoints stitch together aggregates directly +- Using a factory only to hide a trivial constructor + +## Application Layer + +### Purpose +The application layer coordinates application tasks. +It does not replace the domain model. + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Application services load aggregates, call domain behavior, persist results, and coordinate side effects. +2. Application services must not hold core business invariants that belong in the domain. +3. Application services must speak the ubiquitous language. +4. Application services may coordinate transactions and integration publication, but should not become procedural god classes. + +### Required behavior (MUST) +- Keep each application operation focused on one application action. +- Let domain objects make domain decisions. +- Keep orchestration distinct from business rules. + +### Anti-patterns (MUST NOT) +- Application services containing all branching business logic +- Application services manipulating entity internals directly +- Repositories, UI handlers, and application services all implementing overlapping rules + +--- + +## Infrastructure + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Infrastructure is subordinate to the model. +2. Object-relational mappings, serializers, external technical clients, delivery mechanisms, messaging details, caches, and framework types must stay out of the domain model. +3. Infrastructure must adapt to the model, not the reverse. +4. Persistence shape must not define the domain shape. + +### Anti-patterns (MUST NOT) +- Naming domain concepts after database tables +- Designing aggregates around lazy loading +- Adding methods to entities only because the persistence mechanism needs them +- Letting transport representations become domain objects + +--- + +## Translation at Boundaries + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Translation is mandatory at context boundaries. +2. Translation is usually mandatory between domain objects and transport or persistence representations. +3. Anti-corruption layers must preserve the local model rather than mirror foreign models. +4. Foreign terms must not silently invade the local ubiquitous language. + +### Required behavior (MUST) +- Translate external IDs, statuses, and vocabularies explicitly. +- Map transport representations to local commands or domain inputs. +- Keep persistence models and integration models outside the core domain. + +### Anti-patterns (MUST NOT) +- Passing external API models deep into the domain +- Reusing one representation as delivery input, persistence record, domain object, and integration message +- Adopting vendor status codes as native domain terminology + +--- + +## Supple Design + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Design interfaces that reveal intention in domain language. +2. Prefer side-effect-free functions for calculations and queries. +3. Make assertions and invariants explicit in the model. +4. Shape objects around conceptual contours, not arbitrary technical convenience. +5. Use standalone classes where a concept can be understood without unnecessary dependencies. +6. Favor operations that are closed under meaningful domain types when that improves clarity. +7. Use declarative design when it makes rules easier to read, combine, and verify. +8. Combine specifications with AND, OR, or NOT only while each component meaning remains readable. +9. Use subsumption when one specification or category includes another and that relationship matters. + +### Required behavior (MUST) +- Name methods after what the business is trying to accomplish. +- Separate commands from queries where side effects would surprise readers. +- Put invariant checks where invalid states enter the model. +- Look for cohesive concepts hidden inside long methods, conditionals, or parameter groups. +- Consider a domain-specific language only when it simplifies real domain expression. + +### Anti-patterns (MUST NOT) +- Technically named APIs that hide intent +- Methods that both ask a question and mutate domain state +- Invariants expressed only in comments or UI/application validation +- Declarative frameworks that obscure rather than clarify business rules + +--- + +## Analysis and Model Patterns + +### Analysis Patterns +1. Use prior domain modeling knowledge when it fits the current domain. +2. Do not force an analysis pattern when local language contradicts it. +3. Adapt patterns to the bounded context rather than importing them wholesale. +4. Search domain literature, prior art, and established formalisms when the team lacks concepts for a good model. +5. Use exploration teams for hard modeling problems only when their findings are tested in code and returned to the main team. + +### Design Patterns in the Model +Use design patterns only when they express the domain model: +- `Strategy` or `Policy` for interchangeable domain policies. +- `Composite` for part-whole structures that domain experts recognize. +- Avoid patterns that optimize implementation while hiding model meaning. + +### Required behavior (MUST) +- Reach for established formalisms when the domain already has mature concepts. +- Make pattern names subordinate to domain names. +- Prefer domain-specific names over generic pattern names in public APIs. + +### Anti-patterns (MUST NOT) +- Applying design patterns because they are familiar rather than because the model needs them +- Naming domain objects after patterns instead of business concepts +- Importing a reference model without validating it against local domain language + +--- + +## Code Generation Rules + +When generating code, always do the following in order. + +### 1. Identify the domain concept first +Before writing code, identify: +- the bounded context +- the domain term +- whether the concept is an entity, value object, aggregate, domain service, repository, factory, or specification +- which invariants matter + +Do not start from: +- delivery code +- the persistence model +- the database schema +- the REST shape +unless the task is purely infrastructural. + +### 2. Prefer modeling over generic plumbing +If the domain contains real rules: +- put behavior in the model +- introduce value objects +- define aggregate boundaries +- use domain language in APIs + +Do not default to procedural services operating on passive records. + +### 3. Use primitives only when they truly carry no domain meaning +Wrap primitives when meaning, validation, unit semantics, or invariants matter. + +### 4. Protect invariants at the model boundary +Do not rely on UI validation, application validation, or repository validation as the primary protection for business rules. + +### 5. Keep the model persistence-ignorant +Do not shape types or boundaries primarily for object-relational mapping convenience. + +### 6. Keep bounded contexts visible in structure +Prefer feature or context ownership in modules and packages. +Avoid architecture that hides business boundaries behind generic folders. + +### 7. Translate foreign models explicitly +Whenever another context, system, transport layer, or persistence format is involved, create translation rather than leakage. + +### 8. Look for implicit concepts +When conditionals, flags, validation blocks, or repeated calculations express business meaning, extract named concepts such as value objects, policies, specifications, or domain services. + +### 9. Preserve strategic priorities +Give the core domain more modeling care than supporting or generic subdomains. Keep context relationships and distillation choices visible when they affect code. + +--- + +## Review Rules + +When reviewing or modifying code, actively look for: + +### Language problems +- vague technical names replacing business terms +- synonyms for one concept +- one term used with multiple meanings + +### Model problems +- passive entities +- missing value objects +- invalid construction +- missing invariants +- domain logic spread across delivery handlers, application coordination, or repositories + +### Boundary problems +- bounded context bleeding +- foreign models leaking inward +- shared “common domain” abstractions destroying language clarity +- missing context relationship strategy +- implicit shared kernels with no governance + +### Aggregate problems +- oversized aggregates +- aggregate roots exposing internal state changes +- direct object references across aggregates where identity should be used +- transactions spanning many aggregates by default + +### Service problems +- domain services that are really technical helpers +- god services +- application services replacing the whole domain model + +### Infrastructure problems +- persistence-first modeling +- transport shapes defining the domain +- persistence rules embedded in business logic + +### Strategic problems +- core domain hidden behind generic infrastructure +- supporting subdomains over-modeled while core logic remains weak +- no visible distillation or large-scale structure where the system complexity requires one +- context map decisions undocumented in code or tests + +--- + +## Testing Rules + +### Domain tests first +Prioritize tests for: +- entity invariants +- value object validity +- aggregate behavior +- domain services +- specifications and explicit constraints +- application-layer orchestration +- context translation and anticorruption layers + +### Rules (MUST unless marked SHOULD or MUST NOT) +1. Tests must read in the ubiquitous language. +2. Tests must verify allowed and forbidden state transitions. +3. Tests must verify that invalid objects cannot be created through supported paths. +4. Tests must verify translation behavior where anticorruption layers exist. +5. Infrastructure tests must stay separate from domain tests. +6. Tests for the core domain should read like executable examples of the model. +7. Test context boundaries so translations preserve intended meaning. +8. Test that one context does not silently break another context's assumptions. + +### Anti-patterns (MUST NOT) +- Tests named in transport or delivery vocabulary instead of domain vocabulary +- Tests that verify persistence details instead of domain meaning +- Missing tests for invalid transitions and invariant protection +- Tests that validate generic plumbing while leaving core policy untested + +--- + +## Forbidden Patterns + +Do not generate or keep these patterns unless explicitly required and justified. + +### Passive Domain Model +- entities with fields and setters but no real behavior in a complex domain +- all rules living in application services or UI handlers + +### Smart UI +- UI or application code making domain decisions +- request handlers enforcing core invariants + +### Persistence-Driven Design +- aggregate boundaries chosen for persistence convenience +- entities shaped around table structures +- domain types depending on persistence mechanics + +### Primitive Obsession +- raw strings, ints, decimals, and datetimes everywhere for meaningful concepts +- repeated validation logic for the same primitive concept + +### Shared Model Everything +- one giant shared domain model across contexts +- common abstractions that erase business distinctions + +### God Services +- single `*Service` classes containing many unrelated policies and workflows +- procedural orchestration replacing domain behavior + +### Invalid Construction +- partially initialized aggregates +- public state changes that bypass invariants +- allowing impossible states because later code will fix them + +### Fake DDD +- renaming CRUD layers without changing the model +- adding repositories, factories, and services without real domain need +- over-modeling simple supporting subdomains + +### Context Map Blindness +- integrations with no explicit relationship strategy +- foreign models imported directly into the local core +- shared code treated as neutral when it actually carries another context's language + +### Pattern-Driven Obscurity +- design patterns that make the domain language harder to see +- frameworks or DSLs that make simple rules harder to verify +- large-scale structures that prevent model evolution + +--- + +## Refactoring Rules + +When changing existing code: + +1. Recover the ubiquitous language. +2. Move business rules into entities, value objects, aggregates, or domain services where appropriate. +3. Introduce value objects where primitives hide meaning. +4. Redraw aggregate boundaries where invariants are unclear or transactional scope is too large. +5. Separate bounded contexts that are currently bleeding together. +6. Add translation layers where foreign models leak into the domain. +7. Break up god services into focused application services plus richer domain behavior. +8. Remove persistence and transport assumptions from the model. +9. Extract explicit constraints, specifications, and policies from repeated conditionals. +10. Clarify context relationships when integration code is ambiguous. +11. Distill the core domain out of supporting mechanisms when strategic logic is buried. +12. Preserve behavior while improving the model incrementally. + +Do not rewrite everything at once unless explicitly required. + +--- + +## Output Expectations + +When asked to implement a feature, default to producing: +- bounded context ownership +- domain terms first +- entities or value objects where justified +- aggregates when invariants require a consistency boundary +- specifications or policies when named rules must be evaluated or combined +- repositories for aggregate persistence +- factories when creation is non-trivial +- application services for orchestration +- explicit translation at external boundaries +- visible context relationship choices when integrating with other models +- simpler supporting or generic subdomain designs when rich modeling is not justified + +When asked to review code: +- identify model-language mismatch +- identify missing value objects +- identify passive data-structure model symptoms +- identify bad aggregate boundaries +- identify context leakage +- identify infrastructure-driven modeling +- identify missing explicit constraints or specifications +- identify weak core-domain distillation +- identify context map and large-scale structure problems +- propose concrete DDD refactorings + +When asked to modify code: +- improve the model first where safe +- do not deepen technical shortcuts that weaken the domain language +- keep business meaning more explicit after the change than before it +- preserve the model's strategic priorities + +--- + +## Review Checklist + +Before finalizing any change, verify: + +- Is the bounded context clear? +- Does the code use the ubiquitous language consistently? +- Are important concepts modeled explicitly? +- Are value objects used where primitives hide meaning? +- Are entities protecting valid transitions? +- Are aggregate boundaries clear and small enough? +- Are cross-aggregate references by identity unless stronger consistency is required? +- Are repositories aligned to aggregates rather than tables? +- Are specifications, policies, or explicit constraints used where repeated rules need names? +- Are application services orchestrating rather than owning all rules? +- Is the domain model protected from transport, persistence, and vendor models? +- Are context boundaries translated explicitly? +- Is the context map relationship clear for integrations? +- Is the core domain visible and protected from generic mechanisms? +- Are large-scale structures helping rather than freezing the model? +- Did we avoid god services? +- Did we avoid passive domain objects where the domain is complex? +- Did we avoid over-modeling where the domain is simple? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the option that: +1. makes the domain language sharper +2. protects invariants inside the model +3. keeps bounded contexts explicit +4. reduces primitive obsession +5. keeps infrastructure subordinate to domain meaning + +Reject changes that make the code more generic but the domain less clear. diff --git a/programming-principles/references/domain-driven-design.mini.md b/programming-principles/references/domain-driven-design.mini.md new file mode 100644 index 0000000..3275e25 --- /dev/null +++ b/programming-principles/references/domain-driven-design.mini.md @@ -0,0 +1,43 @@ +# OBEY Domain-Driven Design by Eric Evans + +## When to use + +Use when designing or reviewing software for complex domains where business rules, invariants, lifecycle, and domain language must drive the model rather than database tables, frameworks, or generic CRUD habits. + +## Primary bias to correct + +The most critical business behavior does not live in a schema, service, controller, or DTO. It needs a model that speaks the domain language and guards its invariants. + +## Decision rules + +- Model the domain, not the data schema. The model expresses the business language, rules, and invariants before storage, UI, or transport constraints shape it. +- Use the Ubiquitous Language in code: one concept, one term, shared across developers and domain experts. Refactor the model when language drifts. +- Place each model in a Bounded Context. Define context boundaries explicitly; do not force one company-wide model. Translate between contexts. +- Use Entities for objects with identity and lifecycle continuity. Their responsibilities protect state transitions that domain rules require. +- Use Value Objects for objects whose identity is based on their attributes — immutable, self-validating, replaceable. Replace primitive obsession with meaningful domain types. +- Use Services only for operations that fit naturally in no Entity or Value Object. Keep services thin — they orchestrate, the model decides. +- Make Aggregates small. Each transaction modifies one Aggregate. Reference other Aggregates by identity, not by object graph. +- Use Repositories to retrieve Aggregates. Keep fact-finding queries in a separate mechanism when the domain does not need them. +- Use Domain Events for significant business occurrences when other parts of the model or other contexts must react. +- Use Factories for complex creation logic when object construction should not be in the client. Keep creation atomic with valid initial state. +- Keep infrastructure, framework, and technical cross-cutting concerns outside the domain model. Use dependency injection to provide domain-layer services when needed. +- Let strategic design drive tactical design: first find the Core Domain, then apply Entity, Value Object, Aggregate, Service, Repository, and Event where the complexity lives. +- Protect the Core Domain from the Generic and Supporting subdomains. Invest the best modeling effort in the core. + +## Trigger rules + +- If a name is ambiguous or means different things to different team members, start a Bounded Context discussion before modeling further. +- If terms live only in database column names, API contracts, UI labels, or conversation but not in code, bring them into the model. +- If business rules or validation exist in controllers, views, services, or SQL rather than in domain objects, move them inward. +- If a model has no invariants or behavior beyond getters/setters, question whether you need a rich Domain Model or can use a simpler pattern. +- If one transaction touches multiple aggregates, re-evaluate the aggregate boundaries or use eventual consistency with domain events. +- If a primitive is used for an important concept (money, currency, email, range, quantity) and validation or behavior is duplicated, replace it with a Value Object. +- If services accumulate logic that belongs in entities or value objects, push it down. + +## Final checklist + +- Does the code speak the Ubiquitous Language? +- Are Bounded Contexts explicit? +- Do Aggregates protect invariants and remain small? +- Are Value Objects used for meaningful domain concepts? +- Does infrastructure stay outside the domain model? diff --git a/programming-principles/references/implementing-domain-driven-design.full.md b/programming-principles/references/implementing-domain-driven-design.full.md new file mode 100644 index 0000000..065c00d --- /dev/null +++ b/programming-principles/references/implementing-domain-driven-design.full.md @@ -0,0 +1,337 @@ +# OBEY Implementing Domain-Driven Design by Vaughn Vernon + +## Purpose + +This repository follows **Implementing Domain-Driven Design** in the practical style of Vaughn Vernon: +apply DDD operationally, with explicit bounded contexts, disciplined aggregates, and implementation patterns that survive real systems. + +All code generation, edits, and reviews must optimize for: +- explicit bounded contexts +- local ubiquitous language +- small aggregate boundaries +- identities over object graph coupling +- eventual consistency where appropriate +- context mapping instead of shared muddled models +- practical DDD implementation instead of theory theater + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Model the domain in a way that can actually be implemented and evolved. + +When uncertain: +1. identify the bounded context +2. use the local ubiquitous language +3. define the aggregate consistency boundary conservatively +4. reference other aggregates by identity +5. keep infrastructure outside the model +6. integrate across contexts through explicit translation + +Reject designs that sound like DDD but behave like generic CRUD plus renamed classes. + +--- + +## Strategic Design Rules + +### Bounded Context Is Mandatory +1. Every substantial domain area must belong to a named bounded context. +2. A model is valid only inside its bounded context. +3. Terms may change meaning across contexts; that is normal and must be respected. +4. Do not share model classes across contexts by default. + +### Context Mapping Is a Design Artifact +1. Every context interaction must have an explicit relationship. +2. Translation responsibility must be visible. +3. Upstream/downstream influence matters. +4. External models, partner systems, and legacy systems must not define the local model. + +### Core Domain Protection +1. Protect the core domain from generic abstractions and vendor terms. +2. Spend the richest modeling effort where competitive or operational complexity truly lives. +3. Keep supporting subdomains simpler. + +Anti-patterns (MUST NOT): +- one global company model +- shared domain package crossing all contexts +- context boundaries existing only in diagrams but not in code +- context integration via direct imports of each other's domain classes + +--- + +## Ubiquitous Language Rules + +1. Use business terms exactly as they are understood in the current bounded context. +2. One concept gets one term inside the context. +3. One term must not carry multiple meanings inside the same context. +4. Rename code when understanding improves. +5. Tests, events, commands, repositories, and application services must all speak the same language. + +Required behavior: +- use local domain terms in class, method, event, and package names +- remove technical placeholders when a real domain term exists + +--- + +## Aggregate Rules of Thumb + +### Aggregates Are Consistency Boundaries +1. Design aggregates around invariants that must hold immediately. +2. Keep aggregates as small as possible. +3. Small aggregates scale better in both understanding and throughput. +4. Large object graphs are not evidence of good modeling. + +### Aggregate Root Discipline +1. Only the aggregate root may be referenced directly from outside. +2. All invariant-changing operations must go through the root. +3. Internal members must not be mutated directly by external code. +4. Expose intention-revealing behavior, not arbitrary setters. + +### Reference Other Aggregates by Identity +1. Prefer IDs over direct object references across aggregate boundaries. +2. Avoid loading large connected graphs by default. +3. Cross-aggregate coordination should usually be eventual, not transactional. + +### One Aggregate per Transaction by Default +1. Modify one aggregate in one transaction unless there is a compelling reason not to. +2. Do not stretch transactions across many aggregates out of convenience. +3. Use events, policies, or process coordination when consistency can be eventual. + +Anti-patterns (MUST NOT): +- aggregates sized to fit ORM navigation +- transactions updating many aggregates by default +- aggregate roots exposing mutable child collections +- direct cross-aggregate navigation baked into the model + +--- + +## Entity and Value Object Rules + +### Entities +1. Use entities where identity and lifecycle matter. +2. Entities must protect meaningful state transitions. +3. Entity methods must express domain behavior, not generic state changes. +4. Entities must not be passive ORM containers in behavior-rich domains. + +### Value Objects +1. Use value objects aggressively where primitives hide meaning. +2. Value objects must be immutable by default. +3. Validation belongs in value object construction. +4. Equality is by value, not identity. + +Required behavior: +- model local value concepts explicitly instead of passing raw primitives for meaningful identifiers, quantities, ranges, names, or descriptive whole values +- keep invariant enforcement near the concept itself + +--- + +## Domain and Transformation Service Rules + +1. Use a domain service for a domain-significant operation that requires multiple domain objects and fits no single entity or value object. +2. Name domain services in the ubiquitous language. +3. Use transformation services when domain information must be transformed without assigning behavior to the wrong object. +4. Keep technical transformation, serialization, transport, and persistence mapping outside the domain model. + +Anti-patterns (MUST NOT): +- moving behavior into services to avoid modeling entities or value objects +- hiding technical mapping behind a domain-sounding service name + +--- + +## Repository Rules + +1. Repositories exist for aggregate roots. +2. Repository interfaces must be defined by the domain or application code that needs them. +3. Repositories reconstitute and persist aggregates. +4. Repository APIs should reflect aggregate access needs, not generic table CRUD. +5. Repositories must return domain objects or domain-oriented results, not ORM rows. + +Anti-patterns (MUST NOT): +- giant generic repository abstractions +- repository per table without aggregate thinking +- business rules inside repository implementations +- repositories returning persistence-layer entities into the domain + +--- + +## Domain Event Rules + +1. Publish domain events for meaningful business facts. +2. Event names must be in the past tense. +3. Domain events are part of the model, not transport mechanics. +4. Use events to coordinate across aggregates or contexts when immediate consistency is not required. +5. Keep event payloads meaningful and local to the model. + +### Event Sourcing +1. Use event sourcing only when storing the sequence of domain events is the right persistence model for the aggregate. +2. Keep event streams consistent with aggregate identity and versioning. +3. Rebuild state from events deterministically. +4. Version events and upcasters or translators when event meaning evolves. +5. Do not choose event sourcing just because domain events exist. + +Anti-patterns (MUST NOT): +- using events for every property change +- event names that describe commands instead of facts +- domain events carrying framework request objects or persistence artifacts +- using events to compensate for missing aggregate design + +--- + +## Application Service Rules + +1. Application services coordinate use cases. +2. They load aggregates, invoke domain behavior, persist results, and publish resulting events. +3. Application services must not contain the domain model's core decision logic. +4. Application services must be thin enough that the model still matters. +5. Application services may own transaction boundaries and integration coordination. + +Anti-patterns (MUST NOT): +- application services containing all branching business rules +- controllers duplicating application service orchestration +- repositories and application services both implementing the same invariants + +--- + +## Module and Package Rules + +1. Packages/modules must reflect bounded contexts first. +2. Within a context, organize around domain and use-case ownership, not only technical layers. +3. Avoid a giant `shared` or `common` package for domain concepts. +4. Keep the model visible in the structure. + +Preferred structure examples: +- `identity/domain` +- `identity/application` +- `identity/infrastructure` +- `identity/interfaces` + +--- + +## Context Integration Rules + +### Anticorruption Layer +Use when integrating with legacy systems or foreign models. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Translate foreign language into the local context's language. +2. Keep foreign schemas and statuses out of local domain objects. +3. Own the translation explicitly. + +### Identity Across Contexts +1. Use explicit identifiers and integration messages. +2. Do not pass local aggregates directly across context boundaries. +3. Keep contract models separate from local models. + +Anti-patterns (MUST NOT): +- importing another context's domain package +- shared enums across contexts with different semantics +- direct DB coupling between contexts + +--- + +## Client Representation and Scope Discipline + +1. Use DTOs, projections, use-case queries, rendition adapters, or mediators when client needs differ from aggregate shape. +2. Expose REST resources as application-facing representations rather than aggregate internals. +3. Tailor representations for different clients without changing the domain model for each client. +4. Compose multiple bounded contexts at the application or integration layer, not by merging their models. +5. Keep command behavior separate from query models when consistency, performance, or representation needs justify the split. +6. Keep scope identifiers explicit where context or ownership affects invariants or access. + +--- + +## Practical Simplicity Rule + +1. Not every subdomain needs full-blown DDD ceremony. +2. Use richer modeling where complexity is real. +3. Use simpler patterns in supporting areas. +4. However, once invariants and lifecycle complexity appear, model them honestly. + +Anti-patterns (MUST NOT): +- using DDD vocabulary without changing design +- over-modeling trivial CRUD subdomains +- refusing to model real complexity because “simple services are enough” + +--- + +## Code Generation Rules + +When generating code, follow this order: +1. identify the bounded context +2. state the ubiquitous language term(s) +3. determine whether the concept is entity, value object, aggregate root, domain event, repository, or application service +4. define aggregate boundary conservatively +5. reference other aggregates by ID +6. place invariants on the aggregate root or local model +7. define repositories around aggregate access +8. define application services around use cases +9. define translation layers for context or infrastructure boundaries + +Avoid by default: +- direct cross-context model reuse +- ORM-shaped aggregates +- all-powerful application services +- generic repositories +- one transaction touching many aggregates +- shared domain packages across contexts + +--- + +## Review Rules + +When reviewing or modifying code, actively look for: +- missing bounded context ownership +- context bleeding +- shared models across different contexts +- foreign vocabularies polluting the local context +- oversized aggregates +- aggregate roots not protecting invariants +- external code mutating aggregate internals +- cross-aggregate references by object instead of identity +- events that are really commands +- repository contracts shaped like table CRUD + +--- + +## Testing Rules + +1. Test aggregate invariants directly. +2. Test valid and invalid state transitions. +3. Test value object validation and behavior. +4. Test domain events as outcomes of domain behavior. +5. Test repositories as infrastructure separately from aggregate rules. +6. Test anticorruption and translation layers explicitly. +7. Test application services for orchestration, not for all domain decisions. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Is the bounded context explicit? +- Is the local ubiquitous language used consistently? +- Are aggregates small and centered on immediate invariants? +- Are cross-aggregate references by identity? +- Does one transaction usually modify one aggregate? +- Are repository interfaces aggregate-oriented? +- Are domain events facts rather than commands? +- Are application services orchestrating rather than owning the model? +- Are foreign models translated explicitly? +- Did we avoid shared-model shortcuts across contexts? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the option that: +1. protects the bounded context +2. keeps aggregates small +3. keeps identities explicit +4. preserves local language +5. moves cross-boundary coordination toward events and translation rather than shared object graphs + +Reject DDD theater and model the real operational domain. diff --git a/programming-principles/references/implementing-domain-driven-design.mini.md b/programming-principles/references/implementing-domain-driven-design.mini.md new file mode 100644 index 0000000..bbded09 --- /dev/null +++ b/programming-principles/references/implementing-domain-driven-design.mini.md @@ -0,0 +1,46 @@ +# OBEY Implementing Domain-Driven Design by Vaughn Vernon + +## When to use + +Use when DDD implementation choices affect bounded contexts, language, aggregates, repositories, events, application services, package structure, or cross-context integration. + +## Primary bias to correct + +Practical DDD is not renamed CRUD. Model the operational domain inside an explicit Bounded Context, with local language, small invariant boundaries, identity references across Aggregates, and explicit translation across context and infrastructure boundaries. + +## Decision rules + +- Name the Bounded Context before interpreting terms, modules, services, repositories, events, APIs, persistence, or integrations; never force one global company model. +- Use the local Ubiquitous Language consistently: one concept gets one term inside the context, one term must not carry multiple meanings, and code, tests, events, commands, repositories, services, and packages must speak that language. +- Protect the Core Domain from generic abstractions and vendor terms; spend richer modeling where competitive or operational complexity lives, keep supporting subdomains simpler, and avoid DDD ceremony for trivial CRUD. +- Make every context interaction explicit: show the relationship, translation responsibility, and upstream/downstream influence before sharing data, terms, models, or integration code. +- Translate foreign, legacy, partner, external, and infrastructure models into the local language; keep foreign schemas, statuses, contract models, and aggregates out of local domain objects. +- Treat Aggregates as immediate consistency boundaries: keep them small, expose one root, route invariant-changing behavior through the root, hide mutable internals, and expose intention-revealing behavior instead of arbitrary setters. +- Reference other Aggregates by identity, avoid large connected object graphs, and default to one Aggregate per transaction; use events, policies, or process coordination when consistency can be eventual. +- Use Entities when identity and lifecycle matter, and make their methods protect meaningful state transitions rather than generic state changes. +- Use immutable Value Objects for meaningful descriptive concepts; validate at construction, compare by value, and replace raw primitives for meaningful identifiers, quantities, ranges, names, and whole values. +- Use Domain Services only for domain-significant operations that require multiple domain objects and fit no Entity or Value Object; keep technical transformation, serialization, transport, and persistence mapping outside the domain model. +- Provide Repositories for Aggregate Roots, not tables; define interfaces by domain or application needs, return domain objects or domain-oriented results, and keep business rules out of repository implementations. +- Publish Domain Events only for meaningful completed business facts; name them in the past tense, keep payloads local to the model, and do not use events for every property change or to hide poor Aggregate design. +- Use Event Sourcing only when the event sequence is the right persistence model; streams must match Aggregate identity and versioning, replay must be deterministic, and event meaning changes need versioning, upcasters, or translators. +- Keep Application Services as use-case coordinators: load Aggregates, invoke domain behavior, persist results, publish resulting events, own transaction or integration coordination, and keep core decisions in the domain model. +- Organize modules by Bounded Context first and by domain or use-case ownership within the context; avoid giant `shared` or `common` packages for domain concepts. +- Keep command behavior separate from query models when consistency, performance, or representation needs justify it. +- Test domain behavior and boundaries directly: Aggregate invariants, valid and invalid state transitions, Value Object validation, Domain Events as outcomes. + +## Trigger rules + +- When a term is ambiguous, reused across contexts, or drifting into a technical placeholder, qualify, split, or rename it before coding further. +- When code wants to import another context's domain package, share enums across contexts, or couple through another context's database, add explicit translation instead. +- When legacy, vendor, partner, API, transport, persistence, or UI shape appears in local domain code, add an Anticorruption Layer. +- When an Aggregate boundary changes or one transaction wants multiple Aggregates, list the immediate invariants that require it. +- When a Repository becomes generic CRUD, reshape it around Aggregate access. + +## Final checklist + +- Bounded Context explicit before interpreting terms? +- One local term per concept across all code? +- Aggregates small, root-protected, one per transaction? +- Entities behavior-bearing? Value Objects immutable, validated? +- Repositories Aggregate-root access points, not ORM leaks? +- Application Services coordinate, domain model decides? diff --git a/programming-principles/references/patterns-of-eaa.full.md b/programming-principles/references/patterns-of-eaa.full.md new file mode 100644 index 0000000..2f08f32 --- /dev/null +++ b/programming-principles/references/patterns-of-eaa.full.md @@ -0,0 +1,404 @@ +# OBEY Patterns of Enterprise Application Architecture by Martin Fowler + +## Purpose + +This repository follows the practical style of **Patterns of Enterprise Application Architecture**: +structure enterprise software around clear responsibilities for domain logic, persistence, transaction boundaries, integration, and presentation. + +All code generation, edits, and reviews must optimize for: +- explicit separation of concerns +- predictable business logic placement +- appropriate enterprise patterns instead of accidental coupling +- manageable persistence and transaction boundaries +- clear mapping between in-memory objects and external systems +- code that is maintainable under real enterprise constraints + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Enterprise software is not improved by inventing architecture from scratch for every feature. +Prefer a small number of well-understood structural patterns, applied deliberately. + +When uncertain, choose the design that makes these responsibilities explicit: +- presentation and transport +- application workflow +- domain logic +- data source interaction +- transaction management +- concurrency control +- integration boundaries + +Do not let one class or layer own all of them. + +--- + +## Architectural Baseline + +### Layering +Use layering as a default organizing principle: +1. presentation and delivery +2. application coordination +3. domain logic +4. data source and integration access + +Rules (MUST unless marked SHOULD or MUST NOT): +- upper layers may depend on lower ones +- lower layers must not reach into presentation concerns +- each layer must earn its existence by reducing coupling or clarifying responsibility + +Anti-patterns (MUST NOT): +- controllers reaching directly into SQL and transaction details +- domain logic embedded in views or handlers +- repositories returning HTTP-shaped DTOs +- fake layers that only pass calls through + +--- + +## Choosing the Business Logic Pattern + +### Transaction Script +Use when: +- logic is simple +- each request or use case is mostly independent +- rich domain modeling is unnecessary + +Rules (MUST unless marked SHOULD or MUST NOT): +- scripts must remain short and use-case focused +- do not let transaction scripts become dumping grounds for all business logic +- if duplication, lifecycle, or invariant complexity grows, escalate to a stronger pattern + +### Table Module +Use when: +- logic is naturally organized around tabular data sets +- calculations are set-oriented +- object identity is not the key organizing force + +Rules (MUST unless marked SHOULD or MUST NOT): +- keep behavior centered on the table abstraction +- do not fake entities if the real model is fundamentally tabular +- isolate tabular logic from presentation and transport + +### Domain Model +Use when: +- domain complexity is significant +- business rules, invariants, and lifecycles matter +- identity, collaboration, and behavior belong in the model + +Rules (MUST unless marked SHOULD or MUST NOT): +- rich domain logic belongs in model objects +- keep application coordination separate from domain decisions +- avoid anemic models in behavior-rich domains + +--- + +## Application Workflow Rules + +### Service Layer +Use a service layer to define application operations. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Application services coordinate use cases. +2. They define transaction boundaries and orchestration. +3. They must not absorb all domain logic by default. +4. They should expose an application-oriented API, not UI mechanics. + +### Remote Facade +Use when: +- the boundary is remote +- coarse-grained APIs reduce chatty calls +- transport shape differs from internal design + +Rules (MUST unless marked SHOULD or MUST NOT): +- expose coarse remote operations +- translate between remote contracts and internal model +- keep remote transport concerns at the boundary + +### Data Transfer Object +Use when: +- data crosses process or layer boundaries +- batching values reduces remote or serialization cost +- translation protects the internal model + +Rules (MUST unless marked SHOULD or MUST NOT): +- DTOs are transport structures, not domain models +- keep mapping explicit +- do not move business behavior into DTOs + +--- + +## Persistence Pattern Rules + +### Repository +Use repositories to present a collection-like interface over domain object access. + +Rules (MUST unless marked SHOULD or MUST NOT): +1. Repositories must speak in domain terms. +2. Repository interfaces should reflect domain access needs rather than table shape. +3. Repository implementations hide query, mapping, and storage details. +4. Repositories must not become generic “everything” gateways. + +### Data Mapper +Use when: +- the domain model should remain decoupled from the database structure +- object-relational mismatch is real +- persistence logic deserves isolation + +Rules (MUST unless marked SHOULD or MUST NOT): +- mapping code belongs outside the domain objects +- do not force domain objects to know SQL, record formats, or mapping mechanics +- keep mapping explicit and testable + +### Row Data Gateway +Use when behavior is simple and record-oriented. + +### Table Data Gateway +Use when operations are naturally table-oriented and one table interface can clearly centralize access. + +### Active Record +Use only when domain logic is simple and persistence coupling is acceptable. +Do not default to Active Record for complex domains. + +--- + +## Identity, Caching, and Unit-of-Work Rules + +### Identity Map +- preserve one in-memory representation per identity per scope where needed +- avoid duplicate object instances fighting each other inside one logical unit of work + +### Unit of Work +- make transactional write coordination explicit +- commit work as one logical unit +- keep unit-of-work scope understandable + +### Lazy Load +- use deliberately, not everywhere +- know where lazy loading may trigger remote/database chatter +- avoid lazy loading surprises in loops and serialization paths + +Anti-patterns (MUST NOT): +- invisible N+1 behavior everywhere +- hidden auto-persistence with surprising write timing +- saving each object ad hoc from random callers + +--- + +## Object-Relational Mapping Pattern Index + +- USE Identity Field when in-memory objects need stable database identity; keep identity mapping explicit. +- USE Foreign Key Mapping when object references map to relational keys; avoid hiding expensive joins behind innocent traversal. +- USE Association Table Mapping when many-to-many relationships need a separate relational table. +- USE Dependent Mapping when child objects have no independent database identity outside their owner. +- USE Embedded Value when a small value object can live inside the owning row without independent lifecycle. +- USE Serialized LOB only when querying inside the value is not required and serialization versioning is controlled. +- USE Single Table Inheritance when one table with nullable columns is simpler than multiple joins. +- USE Class Table Inheritance when normalized subtype data is worth join complexity. +- USE Concrete Table Inheritance when each concrete type can own its table without excessive duplication. +- USE Inheritance Mappers to isolate inheritance persistence decisions from domain logic. +- USE Metadata Mapping when mapping rules are regular enough to centralize safely; avoid it when metadata obscures exceptional behavior. +- USE Query Object when query construction needs a composable object model instead of scattered SQL strings. + +--- + +## Concurrency and Transaction Rules + +### Optimistic Offline Lock +Use when conflicts are possible but uncommon. + +Rules (MUST unless marked SHOULD or MUST NOT): +- detect conflicting concurrent updates +- fail safely and explicitly +- surface conflict resolution or merge semantics intentionally + +### Pessimistic Locking +Use only when contention is expected and the cost is justified. + +### Transaction Boundaries +1. Transaction boundaries must be explicit in application workflow. +2. Avoid transactions that span remote calls when possible. +3. Keep transactions short. +4. Do not bury transaction ownership deep in helper classes. + +### Additional Offline Concurrency Patterns +- USE Coarse-Grained Lock when related objects must be locked together to preserve a user-level edit. +- USE Implicit Lock only when lock acquisition is reliably hidden without making concurrency invisible to maintainers. +- Do not let implicit locking make transaction ownership or contention impossible to diagnose. + +--- + +## Presentation Layer Rules + +1. Presentation code handles input, rendering, and transport concerns. +2. Business rules must not live in controllers or views. +3. Presentation models may differ from domain models. +4. Formatting, pagination, and UI interaction state belong outside domain logic. + +Choose Page Controller vs Front Controller pragmatically, but keep routing concerns out of business logic. + +### Presentation Pattern Index +- USE Model View Controller to separate domain model, view, and controller responsibilities. +- USE Page Controller when each page/action can be handled independently. +- USE Front Controller when centralized request handling, authentication, routing, or dispatch is valuable. +- USE Template View when server-side templates clearly express the response. +- USE Transform View when transforming data into output is clearer than embedding logic in templates. +- USE Two Step View when shared presentation structure should be separated from page-specific content. +- USE Application Controller when flow and navigation decisions need a dedicated coordinator. + +--- + +## Offline and Integration Rules + +1. External systems must be accessed through clear boundaries. +2. Translate partner formats into internal concepts. +3. Integration events/messages are not domain objects. +4. Do not allow integration convenience to dominate internal design. + +Anti-patterns (MUST NOT): +- internal code shaped around partner API payloads +- direct vendor DTOs used across the application +- business logic embedded in serialization code + +--- + +## Session State and Base Pattern Index + +- USE Client Session State only when client storage is acceptable and integrity/security implications are handled. +- USE Server Session State when server-managed session data is needed and scaling/cleanup costs are explicit. +- USE Database Session State when session durability or server-farm sharing outweighs database load. +- USE Gateway to isolate access to an external resource or subsystem. +- USE Mapper to move data between objects or layers while keeping both sides independent. +- USE Layer Supertype only when shared layer behavior is real and stable. +- USE Separated Interface when clients should depend on an interface in a different package from implementation details. +- USE Registry sparingly for well-known objects; avoid turning it into global hidden dependency. +- USE Value Object for small values where equality by value and immutability simplify code. +- USE Money for currency amounts so rounding, currency, and arithmetic rules stay explicit. +- USE Special Case to replace repeated null or exceptional handling with a named object. +- USE Plugin when implementations must be selected or extended without changing core code. +- USE Service Stub to test or run without a real remote service. +- USE Record Set when tabular data is the natural interchange shape and object behavior is not needed. + +--- + +## Distribution Rules + +1. Do not distribute objects or services remotely by default. +2. Remote boundaries must be coarse-grained. +3. Separate local object design from remote contract design. +4. Budget explicitly for latency, serialization, versioning, and partial failure. + +Anti-patterns (MUST NOT): +- chatty remote object interfaces +- assuming local method-call semantics over a network +- leaking domain internals through remote endpoints + +--- + +## Code Generation Rules + +When generating code, apply this order: +1. identify the business logic pattern that actually fits the complexity +2. place use-case coordination in an application/service layer +3. place domain decisions in the domain model when the domain is rich +4. place persistence behind repositories, mappers, or gateways +5. define transaction boundaries explicitly +6. define DTOs or remote facades only at boundaries +7. keep presentation and transport concerns at the edge + +Default choices: +- simple CRUD -> transaction script or simple service layer +- rich invariants -> domain model + repository + mapper +- table-oriented calculations -> table module or table gateway +- remote boundary -> remote facade + DTO + +Avoid by default: +- using domain model everywhere regardless of complexity +- generic repository for everything +- exposing persistence models directly to callers +- placing transactions, validation, rendering, and SQL in one class + +--- + +## Review Rules + +When reviewing code, actively look for: +- domain logic in controllers or views +- repositories returning transport shapes +- lower layers knowing presentation details +- trivial CRUD wrapped in excessive modeling +- complex rules trapped in transaction scripts +- SQL spread through business code +- missing transaction boundaries +- hidden unit-of-work behavior +- accidental N+1 loading +- chatty remote APIs +- no translation between internal and remote models + +--- + +## Forbidden Patterns + +### Layering Theater +- five layers that only forward method calls + +### Generic Repository Everywhere +- one CRUD abstraction for all domain and data access +- repository APIs shaped by tables instead of use cases + +### ORM-Driven Everything +- all design decisions dictated by ORM convenience +- aggregates, services, and DTOs collapsed into one persistence model + +### Controller-Centric Enterprise App +- request handlers coordinating transactions, SQL, domain rules, and external calls + +### Distributed Object Fantasy +- pretending network calls are normal method calls + +### Unclear Transaction Ownership +- random save calls across layers +- no clear transaction owner +- long-running workflows treated as one immediate transaction + +--- + +## Testing Rules + +1. Test domain logic independently from presentation and persistence when possible. +2. Test repositories, mappers, and gateways separately as data-access infrastructure. +3. Test service/application workflows for transaction and orchestration behavior. +4. Test concurrency behaviors where optimistic or pessimistic locking matters. +5. Test DTO and remote facade mapping at the boundaries. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Did we choose the right business logic pattern for the actual complexity? +- Are presentation, workflow, domain logic, and persistence responsibilities distinct? +- Is transaction ownership explicit? +- Are repositories or gateways shaped by use cases or aggregates rather than raw tables? +- Is mapping isolated from domain logic? +- Are remote boundaries coarse-grained and translated explicitly? +- Did we avoid generic repository overreach? +- Did we avoid controller-centric design? +- Did we avoid layering theater? +- Did we keep concurrency and loading assumptions visible? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the smallest set of enterprise patterns that makes: +1. business logic placement obvious +2. persistence and mapping explicit +3. transaction boundaries clear +4. remote boundaries coarse and safe +5. the application easier to evolve under real enterprise constraints + +Reject fashionable complexity and accidental coupling alike. diff --git a/programming-principles/references/patterns-of-eaa.mini.md b/programming-principles/references/patterns-of-eaa.mini.md new file mode 100644 index 0000000..b0d73b4 --- /dev/null +++ b/programming-principles/references/patterns-of-eaa.mini.md @@ -0,0 +1,37 @@ +# OBEY Patterns of Enterprise Application Architecture by Martin Fowler + +## When to use + +Use when designing or reviewing enterprise application code that crosses presentation, application workflow, domain logic, persistence, transactions, concurrency, integration, session state, or remote boundaries. + +## Primary bias to correct + +Enterprise applications are not improved by inventing architecture for every feature. Use a small set of well-understood patterns to make responsibilities and boundaries explicit. + +## Decision rules + +- Make responsibility ownership explicit: presentation, workflow, domain logic, data source, transaction management, and concurrency control must not collapse into one class. +- Use layering as the default, but every layer must earn its cost. Reject pass-through layering theater. +- Choose business logic pattern by force: Transaction Script for simple flows, Table Module for table-centered set logic, Domain Model for significant rules and invariants. +- Use a Service Layer to define application operations and coordinate use cases. Do not absorb all domain logic into services. +- At remote boundaries, use Remote Facade and DTOs. DTOs are transport structures, not domain models. +- Choose persistence deliberately: Repositories speak domain terms, Data Mappers keep SQL out of domain objects, Active Record only for simple domains. +- Use Identity Map for one object per identity per scope, Unit of Work for one logical commit, Lazy Load only where hidden database calls won't surprise readers. +- Design concurrency in the application workflow: optimistic locks detect conflicts, pessimistic locks require justified contention, transactions stay short. +- Keep presentation focused on input/rendering/routing. Business rules stay out of controllers, views, and templates. +- Access external systems through boundaries. Translate partner formats. Do not let vendor payloads shape internal design. +- Choose session state deliberately: client, server, or database storage accounts for integrity, scaling, cleanup. + +## Trigger rules + +- If domain behavior appears in controllers, views, handlers, SQL, DTOs, or framework glue, move it to the owning layer. +- If a class coordinates rendering, validation, SQL, transactions, domain rules, and external calls, split it. +- If a Transaction Script accumulates duplicated decisions, revisit Domain Model. +- If a layer only forwards calls, treat it as a forbidden-pattern review blocker. + +## Final checklist + +- Presentation, workflow, domain, persistence separated intentionally? +- Business logic pattern matches actual complexity? +- Transaction ownership explicit and short? +- Remote boundaries coarse, translated, failure-aware? diff --git a/programming-principles/references/refactoring-guru.full.md b/programming-principles/references/refactoring-guru.full.md new file mode 100644 index 0000000..874aefb --- /dev/null +++ b/programming-principles/references/refactoring-guru.full.md @@ -0,0 +1,765 @@ +# OBEY Refactoring.Guru + +## Source and Scope + +This rule set is derived from the public Refactoring.Guru refactoring material: + +- +- +- +- +- +- +- +- + +The crawl intentionally excluded example code, images, premium course pages, design pattern pages, legal pages, and non-refactoring navigation. + +This file is not a copy of the site. It is an operational rule set for AI coding agents, paraphrased from the refactoring process, code-smell catalog, and refactoring technique catalog. + +This file is binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Purpose + +Refactoring is the controlled process of improving code structure without adding new functionality. + +Use these rules to: + +- identify code smells before and during a change +- decide whether refactoring is justified now +- select the smallest technique that addresses the smell +- keep each transformation behavior-preserving +- leave code simpler, clearer, and cheaper to change + +Clean code in this rule set means code that: + +- is obvious to other programmers +- avoids duplicated knowledge and duplicated control flow +- has a minimal number of moving parts +- passes the relevant tests +- is easier and cheaper to maintain than the code it replaced + +--- + +## Primary Directive + +When changing existing code, first diagnose the smell that makes the change hard. + +Then choose the smallest refactoring that removes or isolates that smell while preserving behavior. + +Never treat refactoring as a vague cleanup pass. Every refactoring MUST have: + +- a specific smell, friction, or maintenance cost it addresses +- a bounded transformation +- a verification path +- no hidden feature change + +--- + +## Refactoring Process + +### Keep Refactoring Separate + +- MUST NOT mix direct feature development and refactoring in one indistinguishable edit. +- SHOULD separate refactoring and behavior changes at least by commit, patch section, or clearly labeled step. +- MUST preserve existing behavior during refactoring. +- MUST call out any behavior change as feature work or bug fixing, not as refactoring. +- SHOULD refactor before feature work when dirty code blocks understanding or makes the feature awkward. +- SHOULD refactor after feature work when the feature leaves new duplication, awkward names, or unnecessary structure. + +### Work in Small Steps + +- MUST apply refactoring as a sequence of small changes. +- MUST keep the program in working order after each meaningful step when practical. +- SHOULD run relevant tests after each risky structural change. +- MUST stop and reduce scope when a refactoring becomes too large to reason about locally. +- SHOULD prefer several named transformations over one broad rewrite. +- MUST NOT use refactoring as cover for uncontrolled redesign. + +### Verify Continuously + +- MUST identify the relevant test or check before risky refactoring. +- MUST run all relevant existing tests after refactoring. +- If tests fail, MUST decide whether the refactoring changed behavior or the tests were too coupled to implementation details. +- MUST fix refactoring mistakes before continuing. +- SHOULD replace or lift brittle low-level tests when they block behavior-preserving structure changes. +- MUST NOT delete failing tests to make a refactoring appear successful. + +### Keep the Result Cleaner + +- Refactoring is successful only if the code becomes cleaner in the area touched. +- MUST NOT perform a refactoring that leaves the code just as unclear, duplicated, or bloated. +- SHOULD pause and re-diagnose when a chain of small edits is not improving clarity. +- SHOULD consider a planned rewrite only when the code is extremely sloppy, tests exist or are added first, and enough time is explicitly allocated. + +--- + +## When to Refactor + +### Rule of Three + +- MAY implement a first occurrence directly. +- SHOULD tolerate a second similar occurrence when the abstraction is still uncertain. +- MUST consider refactoring on the third similar occurrence. +- MUST NOT abstract coincidental similarity before the repeated responsibility is clear. + +### While Adding a Feature + +- SHOULD refactor first when existing code is too dirty to understand the change safely. +- SHOULD reshape the local structure so the feature becomes straightforward. +- MUST keep preparatory refactoring separate from the feature behavior. +- SHOULD use the feature request as an opportunity to pay down the specific debt that blocks it. + +### While Fixing a Bug + +- SHOULD inspect the area around the bug for hidden complexity, duplication, and unclear ownership. +- SHOULD clean the structure that allowed the bug to hide when the cleanup is small and local. +- MUST preserve the observed bug fix as a separate behavior change from the supporting refactor. + +### During Code Review + +- SHOULD use review as the last chance to catch smells before code becomes public. +- SHOULD fix simple smells immediately when review context and ownership allow it. +- SHOULD estimate and isolate larger smells instead of smuggling them into the reviewed change. +- SHOULD collaborate with the author when a smell needs judgment about intent. + +--- + +## Technical Debt Rules + +- Treat technical debt as a cost that compounds by slowing future development. +- MUST NOT justify patches, kludges, missing tests, or unclear structure as harmless if they make later changes slower or riskier. +- SHOULD expose the debt source when it comes from business pressure, missing tests, weak modularity, delayed refactoring, poor documentation, isolated branches, or inconsistent standards. +- MUST prioritize debt that affects current change speed, correctness, or team understanding. +- SHOULD reduce debt incrementally through ordinary feature and bug work. +- MUST NOT delay all refactoring until a future cleanup project unless the current change cannot safely absorb it. + +--- + +## Smell Detection Process + +When touching existing code, scan in this order: + +1. Bloaters: code grew too large to understand or change. +2. Object-orientation abusers: inheritance, type codes, or conditionals are misusing the object model. +3. Change preventers: one change forces edits in too many places, or one class changes for unrelated reasons. +4. Dispensables: code exists without earning its maintenance cost. +5. Couplers: classes know too much about each other or delegate so much that responsibility disappears. +6. Library gaps: external classes force duplicated workarounds. + +For each smell: + +- identify the symptom +- identify why it makes change harder +- choose the matching treatment +- check whether the suggested treatment creates worse coupling or unnecessary abstraction +- apply the smallest useful refactoring + +### Diagnose, Treat, Verify, Stop + +Use this workflow for every non-trivial refactoring: + +1. Diagnose the smell: + - name the visible symptom + - name the maintenance cost it creates + - identify whether the smell is local, repeated, or architectural + - check whether the smell is real or only a style preference +2. Choose treatment: + - pick the catalog technique that directly addresses the smell + - prefer a smaller technique before a larger structural move + - note the expected cleaner end state before editing + - reject a treatment if its own tradeoff is worse than the smell +3. Verify behavior: + - identify existing tests, characterization checks, type checks, or manual checks before moving code + - run the relevant check after each risky step + - if behavior changes, stop treating the change as refactoring and isolate the behavior change +4. Decide the stop condition: + - stop when the named smell is gone or materially reduced + - stop when the next improvement requires a different smell diagnosis + - stop when the refactoring would cross ownership, public API, or feature scope without explicit approval + - stop when the code is cleaner enough for the requested change and further cleanup is speculative + +MUST NOT continue refactoring just because another smell was discovered. Record the next smell separately unless it blocks the current change. + +### Smell Exception Rules + +- MUST NOT treat every smell mechanically; confirm that the treatment improves clarity for this codebase. +- MAY leave a simple conditional alone when replacing it with polymorphism would obscure a direct rule. +- MAY leave duplicate fragments separate when the shared abstraction would be less obvious than the duplication. +- MAY keep comments that explain why, external constraints, or algorithms that have already resisted simpler structure. +- MAY keep a small class when it communicates a real extension point or boundary. +- MAY keep behavior separate from data when the design intentionally supports interchangeable behavior. +- MAY keep long parameter lists temporarily when removing parameters would create stronger unwanted dependencies. +- MUST document or report intentional non-treatment when a visible smell is left in touched code. + +--- + +## Bloaters + +### Long Method + +- Trigger: a method is long enough that understanding it requires scrolling, comments, or mental bookkeeping. +- MUST ask questions once a method is noticeably long; ten lines is a warning threshold, not a mechanical limit. +- SHOULD extract a method when a code fragment needs a comment to explain what it does. +- SHOULD extract loops, conditional branches, and coherent phases into named methods. +- SHOULD use `Replace Temp with Query`, `Introduce Parameter Object`, or `Preserve Whole Object` when locals block extraction. +- SHOULD use `Replace Method with Method Object` when extraction is blocked by many locals or a tightly coupled algorithm. +- MUST NOT avoid extraction only because a method call might have negligible performance cost. + +### Large Class + +- Trigger: a class has too many fields, methods, responsibilities, or lines to understand as one concept. +- SHOULD split a class that wears multiple functional hats. +- SHOULD use `Extract Class` when a subset of fields and methods forms a separate responsibility. +- SHOULD use `Extract Subclass` when rare or variant behavior bloats the main class. +- SHOULD use `Extract Interface` when clients need only a stable subset of behavior. +- SHOULD move GUI-held domain data into domain classes when interface objects are carrying business state. +- MUST NOT split a class only because it is large if the extracted part has no stable responsibility. + +### Primitive Obsession + +- Trigger: primitives, strings, numbers, constants, or arrays are standing in for meaningful concepts. +- SHOULD replace repeated primitive values with small objects that carry meaning and validation. +- SHOULD use `Replace Data Value with Object` for a single primitive that has domain behavior or constraints. +- SHOULD use `Replace Type Code with Class`, subclasses, or state/strategy when codes control behavior. +- SHOULD use `Replace Array with Object` when array positions have named meaning. +- SHOULD use `Replace Magic Number with Symbolic Constant` when a literal value carries domain meaning. +- MUST NOT wrap primitives in new types when the wrapper adds no name, validation, behavior, or error prevention. + +### Long Parameter List + +- Trigger: a method needs more than three or four parameters, or callers must memorize argument order. +- SHOULD replace derived arguments with `Replace Parameter with Method Call`. +- SHOULD pass an existing object with `Preserve Whole Object` when the callee needs several values from it. +- SHOULD introduce a parameter object when parameters form a recurring concept. +- MUST NOT remove parameters if doing so creates an unwanted dependency between classes. + +### Data Clumps + +- Trigger: the same group of values appears in multiple fields, signatures, or calls. +- SHOULD test whether the values still make sense if one member is removed; if not, model the group. +- SHOULD use `Extract Class` for repeated field groups. +- SHOULD use `Introduce Parameter Object` for repeated parameter groups. +- SHOULD pass the whole object when methods keep receiving pieces of the same concept. +- SHOULD move behavior that uses the clump onto the new object when appropriate. +- MUST NOT pass a whole object if that creates an undesirable dependency on a much larger collaborator. + +--- + +## Object-Orientation Abusers + +### Switch Statements + +- Trigger: complex `switch` or repeated `if` chains branch on type, mode, or category. +- SHOULD suspect missing polymorphism when adding a new case requires edits in multiple switch sites. +- SHOULD extract and move switch logic to the class that owns the decision. +- SHOULD replace type-code branching with subclasses or state/strategy when behavior varies by type. +- SHOULD replace conditional dispatch with polymorphism once the structure is explicit. +- SHOULD use explicit methods instead of polymorphism when there are only a few simple parameter variations. +- SHOULD use a null object when a branch exists only for null handling. +- MUST NOT replace a simple honest conditional or factory selection with unnecessary polymorphism. + +### Temporary Field + +- Trigger: fields are meaningful only in special circumstances and are empty or invalid otherwise. +- SHOULD extract the algorithm and its temporary state into a separate class. +- SHOULD use a method object when a method needs temporary fields only to carry many intermediate values. +- SHOULD use a null object when conditional checks around absent state dominate the code. +- MUST NOT normalize half-initialized objects as ordinary design. + +### Refused Bequest + +- Trigger: a subclass inherits behavior or data that it does not use or cannot honor. +- SHOULD push unused methods or fields down to the subclasses that actually need them. +- SHOULD replace inheritance with delegation when the subclass relationship is misleading. +- SHOULD preserve inheritance only when the unused inherited behavior is harmless and does not confuse clients. + +### Alternative Classes with Different Interfaces + +- Trigger: two classes do the same job but expose different method names or signatures. +- SHOULD align names with `Rename Method`. +- SHOULD align signatures with `Move Method`, `Add Parameter`, or `Parameterize Method`. +- SHOULD extract a superclass when duplicated behavior is only partial but real. +- SHOULD delete one alternative after the common interface and behavior make it redundant. +- MAY leave alternatives separate when they live in separate external libraries and unification is impractical. + +--- + +## Change Preventers + +### Divergent Change + +- Trigger: one class must change for many unrelated reasons. +- SHOULD split unrelated responsibilities with `Extract Class`. +- SHOULD separate product behavior, display behavior, persistence behavior, and integration behavior when they evolve independently. +- SHOULD use superclass or subclass extraction only when the shared behavior is genuine. + +### Shotgun Surgery + +- Trigger: one conceptual change forces many small edits across many classes. +- SHOULD centralize the scattered responsibility. +- SHOULD move methods and fields to the owner of the changing concept. +- SHOULD inline or extract classes to put related changes in one place. +- MUST NOT leave knowledge scattered after the pattern is visible. + +### Parallel Inheritance Hierarchies + +- Trigger: adding a subclass in one hierarchy requires adding a matching subclass in another. +- SHOULD merge the duplicated hierarchy pressure by moving methods and fields so one hierarchy owns the variation. +- SHOULD collapse or replace parallel structures when they exist only to mirror each other. +- MUST avoid creating new parallel hierarchies during extension work. + +--- + +## Dispensables + +### Comments + +- Trigger: comments explain what unclear code does rather than why it exists. +- SHOULD replace explanatory comments with better names, extracted variables, extracted methods, or assertions. +- SHOULD keep comments for rationale, non-obvious constraints, external contracts, and algorithms that resisted simplification. +- MUST NOT use comments as deodorant for confusing structure. + +### Duplicate Code + +- Trigger: two fragments are identical or perform the same job under slightly different wording. +- SHOULD use `Extract Method` for duplicates in the same class. +- SHOULD use pull-up or extract-superclass techniques for duplicates across sibling classes. +- SHOULD use `Extract Class` when duplicate behavior belongs to a separate concept. +- SHOULD remove accidental duplication even when the fragments are not textually identical. +- MAY leave duplication when merging would make the code less intuitive or create the wrong abstraction. +- MUST NOT merge duplicates that are only coincidentally similar and likely to diverge for different reasons. + +### Lazy Class + +- Trigger: a class no longer does enough to justify its maintenance cost. +- SHOULD inline a near-useless class. +- SHOULD collapse a hierarchy when subclasses or superclasses no longer carry distinct behavior. +- MAY keep a small class when it clearly communicates an intended extension point and earns that clarity. + +### Data Class + +- Trigger: a class only stores data and exposes crude getters or setters while clients perform the behavior. +- SHOULD encapsulate public fields. +- SHOULD encapsulate collections rather than exposing mutable collection internals. +- SHOULD move client behavior onto the data class when the behavior operates on that data. +- SHOULD remove broad setters or accessors after meaningful behavior exists. + +### Dead Code + +- Trigger: unused variables, parameters, fields, methods, classes, files, or unreachable branches. +- SHOULD use IDE and compiler feedback to find dead code. +- MUST delete unused code and files when no compatibility reason remains. +- SHOULD inline or collapse empty classes or hierarchies before deletion when needed. +- SHOULD remove unused parameters from methods. +- MUST NOT delete public, serialized, reflected, or plugin-facing code without checking external compatibility. + +### Speculative Generality + +- Trigger: abstractions, parameters, hooks, fields, or classes exist only for imagined future needs. +- SHOULD inline unused abstractions. +- SHOULD remove unused parameters, methods, fields, and classes. +- SHOULD collapse unused hierarchies. +- MAY keep framework extension points only when real users need them. +- MUST check tests before deleting a member that exists only for test access. + +--- + +## Couplers + +### Feature Envy + +- Trigger: a method uses another object's data more than its own. +- SHOULD move behavior to the class that owns the data it mainly uses. +- SHOULD extract the envying fragment before moving it when only part of a method envies another object. +- SHOULD split a method across owners when it uses several data sources for separable purposes. +- MAY keep behavior separate when the separation is intentional, such as interchangeable strategy-like behavior. + +### Inappropriate Intimacy + +- Trigger: classes rely on each other's internals or spend too much time together. +- SHOULD move methods and fields to reduce private knowledge crossing boundaries. +- SHOULD extract or hide delegates to reduce unnecessary knowledge of collaborator structure. +- SHOULD replace inheritance with delegation when intimacy comes from an overexposed subclass relationship. + +### Message Chains + +- Trigger: client code navigates through a chain of objects to reach data or behavior. +- SHOULD hide the delegate behind the object the client already knows. +- SHOULD move behavior closer to the data instead of making clients navigate structure. +- MUST NOT expose object graph topology as a routine calling convention. + +### Middle Man + +- Trigger: a class mostly forwards calls and adds no policy, coordination, or protection. +- SHOULD remove the middle man when direct collaboration is clearer. +- SHOULD inline a class that exists only as pass-through. +- SHOULD keep a delegating layer when it protects a boundary, hides volatile structure, or provides useful policy. + +### Incomplete Library Class + +- Trigger: an external library class lacks methods you need and cannot be changed directly. +- SHOULD use a foreign method for one or two missing operations. +- SHOULD use a local extension when the missing behavior is substantial. +- MUST NOT scatter repeated library workarounds throughout the codebase. +- MUST NOT fork or wrap a library broadly when one narrow foreign method would solve the gap. + +--- + +## Technique Selection Rules + +### Composing Methods + +- Use `Extract Method` when a fragment has a coherent purpose or needs explanation. +- Use `Inline Method` when a method body is clearer than its name or the indirection adds no value. +- Use `Extract Variable` when an expression needs a name to reveal intent. +- Use `Inline Temp` when a temporary variable obscures a simple expression or blocks another refactoring. +- Use `Replace Temp with Query` when a temporary value should be recomputable by a named query. +- Use `Split Temporary Variable` when one variable is assigned different meanings over time. +- Use `Remove Assignments to Parameters` when a method mutates parameters as local scratch space. +- Use `Replace Method with Method Object` when a method is too entangled with locals to extract cleanly. +- Use `Substitute Algorithm` when a clearer algorithm can replace a confusing one after behavior is protected. + +### Moving Features Between Objects + +- Use `Move Method` when a method uses another class more than its current class. +- Use `Move Field` when a field is used more by another class or concept. +- Use `Extract Class` when one class contains separable responsibilities. +- Use `Inline Class` when a class no longer earns its existence. +- Use `Hide Delegate` when clients know too much about an object's collaborators. +- Use `Remove Middle Man` when delegation no longer hides useful complexity. +- Use `Introduce Foreign Method` when a library class needs a small missing operation. +- Use `Introduce Local Extension` when a library class needs substantial local behavior. + +### Organizing Data + +- Use `Self Encapsulate Field` when direct field access prevents adding behavior around access. +- Use `Replace Data Value with Object` when a primitive needs meaning, validation, or behavior. +- Use `Change Value to Reference` when many equal objects should represent one mutable real-world entity. +- Use `Change Reference to Value` when lifecycle management is not worth it and immutable value semantics fit. +- Use `Replace Array with Object` when array positions have domain meaning. +- Use `Duplicate Observed Data` when GUI-held domain data should be split into domain data with synchronization. +- Use `Change Unidirectional Association to Bidirectional` only when both sides genuinely need navigation. +- Use `Change Bidirectional Association to Unidirectional` when one side does not use the other. +- Use `Replace Magic Number with Symbolic Constant` when a literal carries meaning. +- Use `Encapsulate Field` when a public field exposes representation. +- Use `Encapsulate Collection` when callers can mutate internal collections directly. +- Use `Replace Type Code with Class` when a code needs type safety or behavior. +- Use `Replace Type Code with Subclasses` when type code drives stable variant behavior. +- Use `Replace Type Code with State/Strategy` when runtime state or algorithm variation changes behavior. +- Use `Replace Subclass with Fields` when subclasses differ only by constant data. + +### Simplifying Conditional Expressions + +- Use `Decompose Conditional` when conditions or branches are hard to read. +- Use `Consolidate Conditional Expression` when multiple checks lead to one action. +- Use `Consolidate Duplicate Conditional Fragments` when all branches contain the same code. +- Use `Remove Control Flag` when a variable is used only to break or direct control flow. +- Use `Replace Nested Conditional with Guard Clauses` when special cases obscure the normal path. +- Use `Replace Conditional with Polymorphism` when conditional behavior varies by type. +- Use `Introduce Null Object` when null checks dominate behavior. +- Use `Introduce Assertion` when hidden assumptions about state should be explicit. + +### Simplifying Method Calls + +- Use `Rename Method` when a method name does not reveal behavior. +- Use `Add Parameter` only when a method truly needs additional data and a field would be worse. +- Use `Remove Parameter` when a parameter is unused or no longer affects behavior. +- Use `Separate Query from Modifier` when a method both returns information and changes state. +- Use `Parameterize Method` when several similar methods differ only by values. +- Use `Replace Parameter with Explicit Methods` when a parameter selects distinct behavior. +- Use `Preserve Whole Object` when callers pass several values from one object. +- Use `Replace Parameter with Method Call` when a parameter can be obtained by the callee. +- Use `Introduce Parameter Object` when parameters repeatedly travel together. +- Use `Remove Setting Method` when objects should not be changed after creation or after initialization. +- Use `Hide Method` when public methods are not part of the intended interface. +- Use `Replace Constructor with Factory Method` when construction needs naming, selection, caching, or controlled creation. +- Use `Replace Error Code with Exception` when callers should not manually inspect status codes for exceptional failure. +- Use `Replace Exception with Test` when callers can cheaply check a condition before invoking an operation. + +### Dealing With Generalization + +- Use `Pull Up Field` or `Pull Up Method` when siblings duplicate data or behavior. +- Use `Pull Up Constructor Body` when subclass constructors duplicate setup. +- Use `Push Down Field` or `Push Down Method` when a superclass member is used only by some subclasses. +- Use `Extract Subclass` when a subset of instances has distinct behavior. +- Use `Extract Superclass` when classes share real behavior or data. +- Use `Extract Interface` when clients need only a shared subset of behavior. +- Use `Collapse Hierarchy` when subclass and superclass are no longer meaningfully different. +- Use `Form Template Method` when similar algorithms share structure but vary in steps. +- Use `Replace Inheritance with Delegation` when inheritance creates refused bequest or excess coupling. +- Use `Replace Delegation with Inheritance` only when a delegating class truly is a subtype and delegation is pointless. + +--- + +## Smell-to-Treatment Priority Map + +Use this map after diagnosing the smell. Start with the preferred treatment, move to fallback only when the preferred treatment is blocked, and treat risky options as requiring stronger tests and explicit justification. + +- `Long Method`: prefer `Extract Method`; fallback to `Replace Temp with Query`, `Introduce Parameter Object`, or `Preserve Whole Object` when locals block extraction; risky treatment is `Replace Method with Method Object` because it creates a new object and changes the shape of the algorithm. +- `Large Class`: prefer `Extract Class`; fallback to `Extract Subclass` for rare or variant behavior or `Extract Interface` for client-facing subsets; risky treatment is broad hierarchy extraction before responsibilities are stable. +- `Primitive Obsession`: prefer `Replace Data Value with Object`, `Replace Magic Number with Symbolic Constant`, or `Replace Array with Object`; fallback to type-code refactorings when behavior varies by code; risky treatment is replacing type code with subclasses or state/strategy before variation is stable. +- `Long Parameter List`: prefer `Replace Parameter with Method Call` or `Preserve Whole Object`; fallback to `Introduce Parameter Object`; risky treatment is removing parameters by creating hidden object dependencies. +- `Data Clumps`: prefer `Extract Class` or `Introduce Parameter Object`; fallback to `Preserve Whole Object`; risky treatment is passing a large owner object merely to avoid a parameter list. +- `Switch Statements`: prefer `Extract Method` and `Move Method` to isolate the decision; fallback to type-code replacement; risky treatment is `Replace Conditional with Polymorphism` when the conditional is simple or not based on stable variation. +- `Temporary Field`: prefer `Extract Class` or `Replace Method with Method Object`; fallback to `Introduce Null Object` for absence checks; risky treatment is spreading optional half-state through more conditionals. +- `Refused Bequest`: prefer `Push Down Method` or `Push Down Field`; fallback to `Replace Inheritance with Delegation`; risky treatment is preserving inheritance only to avoid changing callers. +- `Alternative Classes with Different Interfaces`: prefer `Rename Method` and signature alignment; fallback to `Extract Superclass`; risky treatment is merging classes across library or ownership boundaries. +- `Divergent Change`: prefer `Extract Class`; fallback to `Extract Superclass` or `Extract Subclass` for genuine shared behavior; risky treatment is inheritance used to avoid clear responsibility splits. +- `Shotgun Surgery`: prefer `Move Method` and `Move Field` to centralize ownership; fallback to `Inline Class` or `Extract Class`; risky treatment is adding more forwarding layers without reducing edit sites. +- `Parallel Inheritance Hierarchies`: prefer moving methods and fields to collapse mirrored variation; fallback to hierarchy collapse; risky treatment is adding the next paired subclass without redesigning ownership. +- `Comments`: prefer `Extract Variable`, `Extract Method`, or `Rename Method`; fallback to `Introduce Assertion` for hidden state assumptions; risky treatment is deleting comments before the code has become self-explanatory. +- `Duplicate Code`: prefer `Extract Method`; fallback to pull-up or `Extract Superclass` for sibling duplication or `Extract Class` for a separate concept; risky treatment is merging coincidental similarity. +- `Lazy Class`: prefer `Inline Class`; fallback to `Collapse Hierarchy`; risky treatment is keeping a class only because future work might need it. +- `Data Class`: prefer `Encapsulate Field` and `Encapsulate Collection`; fallback to `Move Method` and `Extract Method` to bring behavior to the data; risky treatment is stopping after trivial accessors. +- `Dead Code`: prefer deletion after usage checks; fallback to `Inline Class`, `Collapse Hierarchy`, or `Remove Parameter`; risky treatment is deleting externally reachable API. +- `Speculative Generality`: prefer `Inline Method`, `Inline Class`, `Remove Parameter`, and field deletion; fallback to `Collapse Hierarchy`; risky treatment is removing framework extension points without checking users. +- `Feature Envy`: prefer `Move Method`; fallback to `Extract Method` before moving an envying fragment; risky treatment is moving behavior that was deliberately separated for interchangeable strategy-like use. +- `Inappropriate Intimacy`: prefer `Move Method` and `Move Field`; fallback to `Hide Delegate` or `Replace Inheritance with Delegation`; risky treatment is widening visibility to preserve the intimacy. +- `Message Chains`: prefer `Hide Delegate`; fallback to `Move Method` closer to the data; risky treatment is adding a middle man that merely forwards without reducing knowledge. +- `Middle Man`: prefer `Remove Middle Man`; fallback to `Inline Class`; risky treatment is removing a boundary that hides volatile structure or policy. +- `Incomplete Library Class`: prefer `Introduce Foreign Method` for a narrow missing operation; fallback to `Introduce Local Extension` for repeated substantial missing behavior; risky treatment is broad library wrapping or forking. + +--- + +## Technique Playbook + +Each named technique MUST be applied with a symptom, a use condition, an avoid condition, safe steps, and verification. The entries below are intentionally compact; they are for agent decision-making, not tutorial prose. + +### Composing Methods Playbook + +- `Extract Method`: Symptom: a fragment has a coherent purpose, needs a comment, duplicates another fragment, or blocks local reasoning. Use when a name can explain the fragment better than inline detail. Do not use when the fragment hides required side effects or depends on too much changing local state. Safe steps: identify inputs, outputs, mutated variables, extract, name by purpose, replace old fragment with the call. Verify by running tests around the caller and checking changed state flow. +- `Inline Method`: Symptom: a method name adds no clarity beyond its body. Use when indirection obscures the caller. Do not use when the method is an override point, public contract, or useful concept name. Safe steps: inspect all callers, substitute body, remove only when no caller remains. Verify by checking dispatch/interface usage and tests around callers. +- `Extract Variable`: Symptom: an expression is hard to understand in place. Use when a temporary name reveals intent. Do not use when the variable merely repeats the expression mechanically. Safe steps: introduce an immutable local value close to use, name the concept, keep evaluation order. Verify by tests and by checking no side effect was evaluated earlier or later. +- `Inline Temp`: Symptom: a temporary variable gets in the way of another refactoring or hides a simple expression. Use when the expression is cheap and clear. Do not use when the name explains a non-obvious concept or prevents repeated side effects. Safe steps: replace references with the expression, then delete the temp. Verify evaluation count and order. +- `Replace Temp with Query`: Symptom: a temporary value blocks extraction or repeats a meaningful calculation. Use when a named query can compute the same value without mutation. Do not use when the calculation is expensive, stateful, or order-dependent without caching policy. Safe steps: create query, replace temp reads, remove temp. Verify result equivalence and performance-sensitive paths. +- `Split Temporary Variable`: Symptom: one variable has multiple meanings across assignments. Use when assignments represent separate concepts. Do not use when the variable is an intentional accumulator. Safe steps: create one variable per meaning and update uses. Verify each use points to the intended value. +- `Remove Assignments to Parameters`: Symptom: a parameter is reused as scratch state. Use when mutation obscures caller intent. Do not use when language semantics intentionally model output parameters and callers rely on it. Safe steps: introduce a local variable, replace assignments, keep parameter read-only. Verify caller-visible behavior. +- `Replace Method with Method Object`: Symptom: a method is too tangled with locals to extract smaller methods. Use when a dedicated object can hold algorithm state and enable smaller methods. Do not use for a simple long method that `Extract Method` can handle. Safe steps: create method object, move locals to fields, move algorithm, split internal steps. Verify algorithm output and side effects. +- `Substitute Algorithm`: Symptom: an algorithm is confusing and a clearer equivalent exists. Use only after behavior is well protected. Do not use to change semantics, performance guarantees, or edge-case behavior silently. Safe steps: capture current behavior, replace algorithm, compare results on edge cases. Verify with broad tests around expected and boundary inputs. + +### Moving Features Playbook + +- `Move Method`: Symptom: a method uses another class more than its own. Use when behavior belongs with the data it changes. Do not use when separation is deliberate for interchangeable behavior. Safe steps: inspect data usage, extract partial fragment if needed, add method to target, redirect callers, remove old method. Verify callers and access visibility. +- `Move Field`: Symptom: a field is used more by another class or concept. Use when ownership is clearer elsewhere. Do not use when moving it creates circular knowledge or breaks lifecycle ownership. Safe steps: add field to target, migrate reads/writes, preserve initialization, delete old field. Verify construction, serialization, persistence, and mutation behavior. +- `Extract Class`: Symptom: one class does two jobs. Use when fields and methods form a stable separate responsibility. Do not use for arbitrary size reduction. Safe steps: create class, move data and behavior together, delegate temporarily, update clients gradually. Verify behavior and that responsibility boundaries are clearer. +- `Inline Class`: Symptom: a class no longer earns its maintenance cost. Use when its behavior fits naturally in another class. Do not use when it marks a real boundary or extension point. Safe steps: move members to target, replace references, delete empty class. Verify construction and public API usage. +- `Hide Delegate`: Symptom: clients navigate collaborator structure. Use when the current object can shield clients from that structure. Do not use if it creates a pure pass-through layer without reducing knowledge. Safe steps: add forwarding method with meaningful policy, update clients, keep collaborator private. Verify clients no longer know the path. +- `Remove Middle Man`: Symptom: a class mostly forwards calls. Use when direct collaboration is clearer. Do not use when the middle layer protects volatility or policy. Safe steps: replace forwarding calls with direct calls, remove forwarding methods, then reassess the class. Verify callers still have appropriate dependency. +- `Introduce Foreign Method`: Symptom: a library class lacks one small operation. Use for a narrow missing method you cannot add to the library. Do not use when many operations are missing. Safe steps: create local helper near usage, name it as if it belonged to the library type, replace duplicates. Verify behavior against library edge cases. +- `Introduce Local Extension`: Symptom: a library class repeatedly lacks substantial behavior. Use when a local wrapper/subclass reduces duplicated workarounds. Do not use for one small helper. Safe steps: create extension type, move repeated behavior, migrate callers deliberately. Verify compatibility with library construction and updates. + +### Organizing Data Playbook + +- `Self Encapsulate Field`: Symptom: direct field access prevents controlled access behavior. Use when access may need validation, lazy behavior, or override. Do not use when direct field access is intentionally simple and local. Safe steps: add access methods, replace internal reads/writes, then route future access through methods. Verify no recursive access or initialization breakage. +- `Replace Data Value with Object`: Symptom: a primitive carries domain meaning or validation. Use when behavior or constraints belong with the value. Do not use for a wrapper without added meaning. Safe steps: create value object, migrate construction, move validation/behavior, replace primitive usage. Verify equality, serialization, and boundary conversion. +- `Change Value to Reference`: Symptom: many equal objects should represent one mutable entity. Use when shared identity and current state matter. Do not use for naturally immutable values. Safe steps: introduce factory or repository lookup, return canonical instances, update creation paths. Verify identity sharing and missing-object handling. +- `Change Reference to Value`: Symptom: reference lifecycle is heavier than the object deserves. Use when immutable value semantics fit. Do not use when identity or shared mutation matters. Safe steps: make object immutable, define equality, simplify construction. Verify comparisons and update flows. +- `Replace Array with Object`: Symptom: array indexes have hidden names. Use when positions represent fields. Do not use for true homogeneous sequences. Safe steps: create object with named fields, replace index access, add behavior if needed. Verify all index semantics are preserved. +- `Duplicate Observed Data`: Symptom: GUI classes hold domain data. Use when domain state should live outside the UI with synchronization. Do not use when UI-only state has no domain meaning. Safe steps: create domain object, move domain data, synchronize UI/domain updates. Verify two-way update behavior. +- `Change Unidirectional Association to Bidirectional`: Symptom: both classes genuinely need navigation. Use when reverse lookup is complex or frequent. Do not use for convenience alone. Safe steps: choose dominant owner, add reverse field, centralize association updates. Verify add/remove consistency. +- `Change Bidirectional Association to Unidirectional`: Symptom: one side does not use the other. Use to reduce dependency and maintenance code. Do not use when reverse navigation is required by behavior. Safe steps: replace reads with parameters/lookups if needed, remove update code, delete unused field. Verify navigation callers. +- `Replace Magic Number with Symbolic Constant`: Symptom: a literal has hidden meaning. Use when a name explains the value. Do not use for obvious local literals. Safe steps: introduce named constant near owner, replace uses. Verify no unrelated same-value literals were captured. +- `Encapsulate Field`: Symptom: public field exposes representation. Use when access needs control. Do not stop at trivial accessors if behavior belongs inside. Safe steps: add accessor, migrate reads/writes, make field private. Verify callers and invariants. +- `Encapsulate Collection`: Symptom: callers mutate internal collection directly. Use when owner must preserve invariants. Do not expose a settable mutable collection as a replacement. Safe steps: return read-only view/copy, add add/remove methods, migrate callers. Verify mutation paths. +- `Replace Type Code with Class`: Symptom: a code needs type safety or behavior but not polymorphic variants. Use for meaningful codes. Do not use for trivial constants. Safe steps: create class for code, replace primitives, centralize validation. Verify persistence and comparisons. +- `Replace Type Code with Subclasses`: Symptom: type code drives stable variant behavior. Use when behavior differs by type and type does not change often at runtime. Do not use for volatile states. Safe steps: create subclasses, move variant behavior, replace creation. Verify dispatch and construction. +- `Replace Type Code with State/Strategy`: Symptom: type or state controls behavior and may change at runtime. Use when runtime switching matters. Do not use when a simple class code is enough. Safe steps: create state/strategy objects, move behavior, route transitions explicitly. Verify state transitions. +- `Replace Subclass with Fields`: Symptom: subclasses differ only by constant data. Use when hierarchy adds no behavior. Do not use when subclasses have distinct logic. Safe steps: add fields to superclass, replace subclass construction, remove empty subclasses. Verify type checks and serialization. + +### Conditional and Method Call Playbook + +- `Decompose Conditional`: Symptom: condition or branches require mental parsing. Use when names can clarify condition, then, or else parts. Do not use if extraction hides side effects. Safe steps: extract condition and branches into named methods. Verify branch behavior. +- `Consolidate Conditional Expression`: Symptom: multiple checks lead to one action. Use when checks are side-effect free. Do not use if checks differ in timing or side effects. Safe steps: combine expression, extract named query. Verify truth table. +- `Consolidate Duplicate Conditional Fragments`: Symptom: all branches repeat code. Use when repeated code can move before or after the conditional without changing order. Do not use if branch-specific side effects change ordering. Safe steps: move common fragment, extract if longer. Verify branch outputs. +- `Remove Control Flag`: Symptom: a flag variable only directs loop or branch flow. Use when direct break/return/continue is clearer. Do not use if the flag represents durable domain state. Safe steps: replace flag checks with direct control flow. Verify loop exit behavior. +- `Replace Nested Conditional with Guard Clauses`: Symptom: special cases obscure the normal path. Use when early exits make normal flow obvious. Do not use when nesting communicates required transaction or cleanup scope. Safe steps: identify special cases, move them first, keep normal path last. Verify all branches. +- `Replace Conditional with Polymorphism`: Symptom: behavior varies by stable type/state and conditionals repeat. Use after variation ownership is clear. Do not use for simple one-off conditionals or factory selection. Safe steps: create type/state structure, move variant behavior, replace conditional dispatch. Verify each variant. +- `Introduce Null Object`: Symptom: null checks dominate behavior. Use when a neutral object can obey the same interface. Do not use when absence is an error that should be explicit. Safe steps: create null object, replace null branches, preserve observable absence behavior. Verify absent and present cases. +- `Introduce Assertion`: Symptom: code depends on hidden state assumptions. Use to make invariants explicit. Do not use for normal validation or recoverable user errors. Safe steps: add assertion at boundary of assumption. Verify tests fail clearly when invariant is violated. +- `Rename Method`: Symptom: a method name hides intent. Use when callers should understand behavior without reading the body. Do not use if rename churn is unrelated to the change. Safe steps: rename definition and callers atomically. Verify references and public compatibility. +- `Add Parameter`: Symptom: a method lacks data needed for its job. Use when passing occasional data is better than storing it. Do not use if the method should own or derive the data. Safe steps: add compatible signature, migrate callers, remove old signature when safe. Verify callers. +- `Remove Parameter`: Symptom: a parameter no longer affects behavior. Use after confirming it is unused. Do not use if the parameter is part of public compatibility. Safe steps: remove uses, migrate signatures, preserve compatibility path if needed. Verify callers. +- `Separate Query from Modifier`: Symptom: a method both returns data and mutates state. Use when callers need clear intent. Do not use if atomic read-modify behavior is the public contract. Safe steps: split query and command, update callers. Verify state changes and return values. +- `Parameterize Method`: Symptom: similar methods differ only by values. Use when one method with a parameter keeps intent clear. Do not use when the parameter selects different behavior. Safe steps: create parameterized method, redirect old methods, remove duplicates if safe. Verify all value cases. +- `Replace Parameter with Explicit Methods`: Symptom: a parameter selects distinct behavior. Use when separate names are clearer than flags or modes. Do not use for ordinary data. Safe steps: create explicit methods, route callers, remove selector parameter. Verify each behavior. +- `Preserve Whole Object`: Symptom: callers pass several values from one object. Use when the callee naturally depends on the whole concept. Do not use if it creates an oversized dependency. Safe steps: change signature to object, update field reads, migrate callers. Verify dependency direction. +- `Replace Parameter with Method Call`: Symptom: caller passes data the callee can obtain. Use to reduce redundant caller work. Do not use if it hides an expensive or surprising dependency. Safe steps: move lookup to callee, remove parameter, update callers. Verify lookup behavior. +- `Introduce Parameter Object`: Symptom: parameters repeatedly travel together. Use when they form one concept. Do not use for a random bag of unrelated arguments. Safe steps: create object, migrate signature, move related behavior. Verify construction and validation. +- `Remove Setting Method`: Symptom: a field should not change after initialization. Use when immutability or lifecycle clarity matters. Do not use when mutation is valid domain behavior. Safe steps: set through constructor/factory, remove setter, update initialization. Verify object creation. +- `Hide Method`: Symptom: public method is not intended for clients. Use to reduce interface surface. Do not use if external callers need it. Safe steps: check callers, reduce visibility, update tests. Verify public API. +- `Replace Constructor with Factory Method`: Symptom: creation needs naming, selection, caching, or controlled reference lookup. Use when `new` hides important creation policy. Do not use for simple construction. Safe steps: add factory, redirect construction, restrict constructor if safe. Verify creation paths. +- `Replace Error Code with Exception`: Symptom: exceptional failure is represented by status codes callers must inspect. Use when failure should interrupt normal flow. Do not use for ordinary expected branch choices. Safe steps: throw exception, update callers, remove code checks. Verify failure handling. +- `Replace Exception with Test`: Symptom: callers use exceptions for avoidable expected conditions. Use when a cheap pre-check exists. Do not use when failure is exceptional or race-prone. Safe steps: add query/test, update callers, keep exception for true violations. Verify normal and failure paths. + +### Generalization Playbook + +- `Pull Up Field`, `Pull Up Method`, `Pull Up Constructor Body`: Symptom: siblings duplicate members or setup. Use when the superclass can honestly own the shared part. Do not use when duplication is accidental or variants will diverge. Safe steps: move shared member up, update subclasses, remove duplicates. Verify all subclasses. +- `Push Down Field`, `Push Down Method`: Symptom: superclass member is used only by some subclasses. Use when superclass contract is too broad. Do not use if callers rely on the superclass member. Safe steps: move member down, update references, narrow contract. Verify affected subtype callers. +- `Extract Subclass`: Symptom: only some instances need special behavior. Use when variation is stable and meaningful. Do not use for temporary flags or speculative categories. Safe steps: create subclass, move variant behavior, update construction. Verify base and variant behavior. +- `Extract Superclass`: Symptom: classes share real behavior or data. Use when a common owner simplifies duplication. Do not use for coincidental method names. Safe steps: create superclass, pull up shared members, update inheritance. Verify all subclasses. +- `Extract Interface`: Symptom: clients use only a common subset. Use when the subset is a real client contract. Do not use as a generic abstraction habit. Safe steps: define interface, type clients to it, keep implementers honest. Verify client compilation and behavior. +- `Collapse Hierarchy`: Symptom: subclass and superclass are practically identical. Use when hierarchy adds no distinction. Do not use if remaining subclasses would violate substitutability. Safe steps: choose survivor, move members, replace references, delete empty type. Verify type expectations. +- `Form Template Method`: Symptom: similar algorithms share structure but vary steps. Use when skeleton and steps are stable. Do not use when algorithms are only superficially similar. Safe steps: align method names, pull up skeleton, push variant steps down. Verify all algorithms. +- `Replace Inheritance with Delegation`: Symptom: inheritance causes refused bequest or excessive coupling. Use when object uses another object rather than is that object. Do not use if subtype substitution is central. Safe steps: add delegate, forward needed behavior, replace inherited access. Verify public behavior. +- `Replace Delegation with Inheritance`: Symptom: a class delegates nearly everything to an object it truly is. Use rarely when subtype relation is honest. Do not use if inheritance would create unused behavior. Safe steps: inherit, remove redundant delegate, update construction. Verify substitutability. + +--- + +## Decision Anti-Patterns + +- MUST NOT apply a refactoring because its name sounds modern; apply it because it treats a diagnosed smell. +- MUST NOT turn a simple conditional into polymorphism unless variation is stable, repeated, and owned by type/state. +- MUST NOT create a parameter object from unrelated arguments just to shorten a signature. +- MUST NOT introduce a superclass or interface from coincidental method names without a real client or shared behavior. +- MUST NOT replace duplication with an abstraction that has a worse name than the duplicated code. +- MUST NOT stop at getters and setters when the real smell is behavior living outside the data. +- MUST NOT hide feature work inside a refactoring sequence. +- MUST NOT preserve a forwarding class merely because deleting it requires caller updates. +- MUST NOT use bidirectional association as a convenience shortcut when one side can receive the collaborator as a parameter or lookup. +- MUST NOT delete speculative or dead-looking code until generated, reflected, serialized, plugin-facing, and public usages are checked. +- MUST NOT add assertions for normal user input, expected absence, or recoverable errors. +- MUST NOT use exceptions as routine tests when callers can cheaply check the condition first. +- MUST NOT inline names that explain business intent even when the body is short. +- MUST NOT move behavior away from its data if doing so creates feature envy in the opposite direction. +- MUST NOT continue cleanup after the diagnosed smell is fixed unless the next smell blocks the requested change. + +--- + +## Technique Execution Safety + +### Extraction Safety + +- Before `Extract Method`, MUST identify every variable read, written, or returned by the fragment. +- SHOULD leave variables local to the extracted method when they are declared and used only inside the fragment. +- SHOULD pass prior values as parameters only when the extracted fragment genuinely needs them. +- MUST double-check any variable modified inside the fragment; if later code needs the changed value, return it explicitly or choose a safer refactoring. +- SHOULD use `Replace Temp with Query` before extraction when temporary variables are blocking a clean method boundary. +- MUST name the extracted method after its purpose, not after the mechanical steps it performs. +- MUST NOT extract a fragment that hides an important side effect behind a harmless-sounding name. + +### Inlining Safety + +- Before `Inline Method`, MUST confirm the method adds no useful name, abstraction, override point, or public contract. +- SHOULD inline only after checking all callers, especially when dynamic dispatch, inheritance, or interface calls may be involved. +- MUST NOT inline a method if callers depend on it as part of a public or test-facing API. +- Before `Inline Class`, MUST move all useful behavior and data to the target class and update all references. +- MUST delete the emptied class only after references, construction sites, tests, and documentation no longer require it. + +### Moving Safety + +- Before `Move Method`, MUST inspect which class owns most of the data used by the method. +- SHOULD extract the moved fragment first when only part of a method belongs elsewhere. +- MUST update all callers and preserve visibility intentionally; do not widen access just to make the move compile. +- Before `Move Field`, MUST migrate reads and writes through accessors or direct replacements in a small sequence. +- MUST NOT move behavior away from its data if the separation was deliberate and supports interchangeable behavior. + +### Encapsulation Safety + +- Before `Encapsulate Field`, SHOULD add access methods, migrate all direct readers and writers, then make the field private. +- SHOULD review accessor callers after encapsulation; behavior may belong inside the owning class rather than outside it. +- Before `Encapsulate Collection`, MUST prevent callers from mutating the internal collection directly. +- SHOULD expose add/remove operations that preserve invariants instead of exposing a settable collection. +- MUST NOT add trivial getters and setters as the final design if they merely preserve public data under different names. + +### Conditional Safety + +- Before `Consolidate Conditional Expression`, MUST verify that the conditions are side-effect free. +- SHOULD extract the consolidated condition into a named query when the expression is complex. +- Before `Consolidate Duplicate Conditional Fragments`, SHOULD move duplicate code before or after the conditional only when doing so preserves execution order. +- Before `Replace Nested Conditional with Guard Clauses`, MUST identify the normal path and preserve special-case behavior. +- Before `Replace Conditional with Polymorphism`, MUST confirm that the conditional varies by stable type, state, or strategy; otherwise prefer explicit methods or a simpler conditional. +- MUST NOT introduce polymorphism for a simple conditional that is easier to read in place. + +### Method Call Safety + +- Before `Add Parameter`, MUST check whether the method should instead own the data as a field or obtain it through an existing collaborator. +- SHOULD preserve compatibility by creating a new method or transition path before deleting the old signature when callers are numerous or public. +- Before `Remove Parameter`, MUST confirm the parameter is unused or no longer changes behavior. +- Before `Separate Query from Modifier`, MUST split state mutation from returned information and update callers to use the right method for each intent. +- Before `Replace Parameter with Explicit Methods`, MUST confirm the parameter selects distinct behavior rather than ordinary data. +- Before `Introduce Parameter Object`, MUST confirm the grouped parameters represent one concept and not an arbitrary bag. +- MUST NOT simplify a method call if the simplification creates hidden dependencies between classes. + +### Data Reorganization Safety + +- Before `Replace Data Value with Object`, MUST define the object's meaning, equality, validation, and allowed behavior. +- Before changing value/reference semantics, MUST decide whether identity, mutability, sharing, and lifecycle management are required. +- SHOULD make value objects immutable before replacing references with values. +- SHOULD use factory creation when replacing values with references so callers receive the canonical object. +- Before changing association direction, MUST identify which side owns updates and how consistency is maintained. +- MUST remove a bidirectional association when one side does not need navigation. +- MUST NOT add a bidirectional association unless both sides genuinely need it and consistency logic is explicit. + +### Generalization Safety + +- Before pulling members up, MUST confirm sibling duplication is real and the superclass contract can honestly own the member. +- Before pushing members down, MUST confirm the superclass no longer promises or needs the member. +- Before extracting a superclass or interface, MUST identify real shared behavior or a real client-facing subset. +- MUST NOT extract an interface only because two classes happen to share method names. +- Before collapsing a hierarchy, MUST check remaining subclasses for substitutability and public type expectations. +- Before replacing inheritance with delegation, MUST preserve the delegated behavior and update construction and forwarding paths deliberately. +- MUST NOT replace delegation with inheritance unless the delegating class truly is a subtype and the inheritance will not create refused bequest. + +--- + +## Safety and Tradeoff Rules + +- MUST choose a treatment based on the smell, not on a preferred pattern. +- MUST NOT introduce polymorphism, inheritance, bidirectional links, or new classes when a simpler extraction or rename solves the problem. +- MUST NOT remove parameters, associations, or abstractions if doing so creates worse coupling or hides required variation. +- SHOULD prefer local simplification before hierarchy changes. +- SHOULD prefer names and extracted methods before comments. +- SHOULD prefer deleting unused structure before extending it. +- SHOULD preserve domain meaning when replacing primitives or arrays with objects. +- SHOULD keep behavior with the data it changes unless a deliberate interchangeable behavior model is needed. +- SHOULD use assertions for invariants, not as substitutes for normal validation or recoverable error handling. +- MUST preserve public compatibility or provide a transition path when refactoring public interfaces. + +--- + +## Refactoring Workflow for Agents + +Before editing: + +1. Identify the requested behavior change or maintenance goal. +2. Scan the touched area for smells using the catalog above. +3. Name the primary smell, its cost, and the smallest useful refactoring. +4. Identify the expected cleaner end state and the stop condition. +5. Identify tests or checks that prove behavior is preserved. +6. Decide whether the refactoring belongs before, after, or separate from feature work. + +During editing: + +1. Apply one named transformation at a time. +2. Keep the code runnable after each meaningful step. +3. Rename, extract, move, inline, or encapsulate before introducing larger design structures. +4. Re-run relevant tests after risky movement, public interface changes, or changed state flow. +5. Re-check whether the chosen technique is still the smallest treatment. +6. Stop if the refactoring exposes a different, larger problem and report the new scope. + +After editing: + +1. Confirm behavior preservation. +2. Confirm the original smell is reduced or removed. +3. Confirm no broader feature change was hidden in the refactor. +4. Confirm no new smell was introduced, especially middle-man, speculative generality, or inappropriate intimacy. +5. Confirm that any intentionally untreated smell has a reason. +6. Report the refactoring technique used, the stop condition reached, and the validation performed. + +--- + +## Review Checklist + +- Is the change a refactoring, a feature, or a bug fix, and is that boundary clear? +- Did the code become cleaner in the touched area? +- Is there a named smell that justified the transformation? +- Was the smallest suitable technique used? +- Did all relevant tests pass? +- Did any public interface change receive compatibility handling? +- Did the change reduce duplication, bloat, coupling, or unclear control flow? +- Did it avoid speculative abstractions? +- Did it avoid needless polymorphism, inheritance, or bidirectional associations? +- Is any remaining smell explicitly deferred rather than hidden? diff --git a/programming-principles/references/refactoring-guru.mini.md b/programming-principles/references/refactoring-guru.mini.md new file mode 100644 index 0000000..b6f2adc --- /dev/null +++ b/programming-principles/references/refactoring-guru.mini.md @@ -0,0 +1,40 @@ +# OBEY Refactoring.Guru + +## When to use + +Use when changing existing code where code smells, refactoring technique choice, behavior preservation, and cleanup scope control matter. + +## Primary bias to correct + +Refactoring is not general cleanup or pattern application. It is a small, smell-driven, behavior-preserving treatment with verification and a stop condition. + +## Decision rules + +- Separate refactoring from feature work and bug fixes. +- Diagnose the smell before choosing a technique: symptom, maintenance cost, expected end state, verification path. +- Prefer the smallest treatment that directly reduces the diagnosed smell. +- Keep code runnable and understandable through small named transformations. +- Stop when the named smell is gone or materially reduced. +- Use the Rule of Three: tolerate uncertain duplication early, refactor the third similar occurrence. +- For bloaters, prefer extraction and responsibility splits before creating method objects or subclasses. +- For switch/type-code smells, isolate the decision first; use polymorphism only when variation is stable. +- For change preventers, move behavior and data toward the owner of the changing concept. +- For dispensables, delete or inline unused structure. Check public/generated/reflected uses first. +- For couplers, reduce navigation and private knowledge. Keep delegating layers only when they hide volatile structure. +- Encapsulation is not finished by adding getters and setters. Move behavior inward. +- Avoid speculative abstractions: do not create wrappers, interfaces, or hierarchy variants without a real concept. + +## Trigger rules + +- When a method needs comments or scrolling to understand, try Extract Method. +- When a class has multiple reasons to change, use Extract Class. +- When primitives carry meaning, model the concept if it adds naming, validation, or behavior. +- When a parameter list grows, introduce a parameter object only for a real recurring concept. +- When a class mostly forwards, remove the middle man unless it protects boundary policy. +- When null checks dominate, introduce a null object only if absence obeys the same interface. + +## Final checklist + +- Is this clearly refactoring, feature work, or bug fixing? +- Was the smallest suitable treatment used? +- Did the named smell become materially better? diff --git a/programming-principles/references/refactoring.full.md b/programming-principles/references/refactoring.full.md new file mode 100644 index 0000000..6bcf75c --- /dev/null +++ b/programming-principles/references/refactoring.full.md @@ -0,0 +1,433 @@ +# OBEY Refactoring by Martin Fowler + +## Purpose + +This repository follows the discipline of **Refactoring** in the sense of Martin Fowler: +improve the internal structure of code **without changing its observable behavior**. + +All code generation, edits, and reviews must optimize for: +- small behavior-preserving changes +- clearer names and simpler control flow +- lower duplication +- smaller units of responsibility +- explicit movement from bad design toward good design +- steady design improvement as part of daily work + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +When modifying existing code, do **not** start by rewriting large areas. +Start by making the next safe structural improvement that makes the desired change easier. + +Prefer: +1. establish a safety net +2. make a preparatory refactoring +3. make the functional change +4. refactor again if needed + +Reject changes that bundle large functional changes with unrelated structural churn. + +--- + +## What Counts as Refactoring + +Refactoring here means: +- changing structure without changing external behavior +- applying small, composable transformations +- removing code smells before or during feature work +- making the next change easier +- improving readability, locality, and testability + +Refactoring here does **not** mean: +- large rewrites +- unverified cleanup +- “modernization” with unclear behavioral impact +- renaming everything at once +- mixing architecture migration, feature work, and cleanup in one uncontrolled patch + +--- + +## Non-Negotiable Rules + +1. **Preserve Behavior** + - Refactorings must preserve observable behavior. + - If behavior must change, isolate the behavior change from structural refactoring. + - Never disguise a feature change as a refactoring. + +2. **Work in Small Steps** + - Prefer many small safe edits over one large transformation. + - Each step should be understandable and reversible. + - If a patch feels too large to reason about locally, split it. + +3. **Keep the System Running** + - Do not leave code in a broken intermediate state unless explicitly asked for a draft. + - Every refactoring sequence should maintain a runnable, buildable state where practical. + +4. **Refactor Before and After Feature Work** + - If code is hard to change, first reshape it. + - After the feature lands, clean remaining structural debt introduced by the change. + +5. **Use the Simplest Helpful Refactoring** + - Do not introduce patterns or abstractions earlier than needed. + - Prefer local simplification before large-scale abstraction. + +--- + +## Safety Rules + +### Tests and Verification +1. Create or identify a safety net before risky refactoring. +2. Prefer characterization tests when working on unclear existing behavior. +3. If tests are absent, make the smallest changes possible and improve testability first. +4. Keep refactoring and test updates aligned with preserved behavior. +5. Never delete a failing test just to complete a refactoring. + +### Commit and Patch Discipline +1. Separate structural edits from behavior changes whenever practical. +2. Group related refactorings together. +3. Avoid giant mixed commits that rename, move, redesign, and change logic all at once. +4. Prefer reviewable sequences of transformations. + +### Preparatory Refactoring +Before implementing a feature, ask: +- what makes this change awkward? +- what local structural change would make it straightforward? +- can I rename, extract, move, split, or inline first? + +Do the preparatory refactoring before the feature change. + +--- + +## Code Smell Policy + +When modifying code, actively look for these smells. + +### Duplicated Code +- Duplicate logic is a default target for elimination. +- Remove duplication by extracting shared behavior, not by introducing vague utility dumping grounds. +- Do not abstract coincidental similarity. + +### Long Functions +- Split long functions when they mix responsibilities, levels of abstraction, or phases of work. +- Extract meaningful chunks with names that explain intent. +- Do not create micro-method noise with no explanatory value. + +### Long Parameter Lists +- Replace repeated clumps with parameter objects or richer domain objects where appropriate. +- Remove boolean flags that switch behavior. +- Avoid signatures that require callers to memorize argument order. + +### Global Data and Hidden Dependencies +- Reduce reliance on globals, singletons, and ambient context. +- Make dependencies explicit where possible. +- Refactor toward injection, parameters, or clear ownership. + +### Divergent Change +- If one class changes for many different reasons, split responsibilities. +- Separate business logic, formatting, transport, persistence, and integration concerns. + +### Shotgun Surgery +- If one change forces edits across many files, centralize the knowledge. +- Introduce a better boundary or clearer ownership. + +### Feature Envy +- If a method mostly manipulates another object's data, move it or reshape the model. +- Put behavior near the data or concept it belongs to. + +### Data Clumps and Primitive Obsession +- Replace repeated primitive bundles with meaningful types. +- Give recurring business concepts names and validation. + +### Switch Statements and Conditionals +- Reduce repeated branching on type or mode when polymorphism, tables, strategies, or better data structures fit. +- Do not replace a single honest conditional with needless indirection. + +### Temporary Fields and Weird Lifecycles +- Remove fields that exist only for unusual code paths when a separate object or clearer phase model is better. +- Prefer modeling states explicitly over half-initialized objects. + +### Middle Man and Speculative Generality +- Remove forwarding layers that add no value. +- Delete abstractions created “just in case” if they are not earning their keep. + +--- + +## Preferred Refactoring Moves + +### Naming Refactorings +- Rename variables to reveal intent. +- Rename functions to describe behavior, not mechanism. +- Rename types and modules to align with problem-domain terminology. +- Rename before deeper refactoring when bad names block understanding. + +### Extraction Refactorings +- Extract function when a block has a coherent purpose. +- Extract variable when an expression is hard to read. +- Extract class when one class has multiple reasons to change. +- Extract module when a file mixes unrelated concerns. + +### Movement Refactorings +- Move function to the module or type where the data or concept lives. +- Move field when ownership is clearer elsewhere. +- Move statements to group related operations and reduce cognitive jumps. + +### Simplification Refactorings +- Inline accidental abstractions. +- Collapse unnecessary layers. +- Replace nested conditionals with guard clauses where it improves clarity. +- Consolidate duplicate conditional fragments. + +### Data Refactorings +- Encapsulate mutable state. +- Replace magic values with named constants or domain types. +- Introduce parameter objects for repeated argument groups. +- Replace raw collections with named abstractions when behavior accumulates around them. + +--- + +## Refactoring Catalog Index + +### Composing Methods +- USE Extract Method when a code fragment has a coherent purpose and a useful name. +- USE Inline Method when a method body is clearer than its indirection. +- USE Inline Temp when a temporary variable obscures a direct expression. +- USE Replace Temp with Query when a calculated value deserves a named query and can be reused safely. +- USE Introduce Explaining Variable when a complex expression needs named parts. +- USE Split Temporary Variable when one variable carries multiple meanings. +- USE Remove Assignments to Parameters when parameter mutation obscures input meaning. +- USE Replace Method with Method Object when local state prevents clean extraction. +- USE Substitute Algorithm when a clearer algorithm can replace a tangled one without changing behavior. + +### Moving Features +- USE Move Method or Move Field when behavior or state belongs more naturally to another object. +- USE Extract Class when one class has more than one reason to change. +- USE Inline Class when a class no longer earns its existence. +- USE Hide Delegate when clients know too much about an object's collaborator. +- USE Remove Middle Man when a forwarding object no longer hides useful detail. +- USE Introduce Foreign Method only when you cannot edit the class that should own the behavior. +- USE Introduce Local Extension when repeated foreign methods need a local, coherent extension point. + +### Organizing Data +- USE Self Encapsulate Field when direct field access blocks flexibility. +- USE Replace Data Value with Object when a primitive carries behavior, validation, or meaning. +- USE Change Value to Reference when identity and shared updates matter. +- USE Change Reference to Value when value semantics simplify ownership. +- USE Replace Array with Object when positions in a collection have names or rules. +- USE Duplicate Observed Data only when UI or framework synchronization forces it; keep synchronization explicit. +- USE Change Unidirectional Association to Bidirectional only when traversal is needed both ways. +- USE Change Bidirectional Association to Unidirectional when one direction is unnecessary coupling. +- USE Encapsulate Collection when external mutation can bypass invariants. +- USE Replace Record with Data Class when raw records need named access and behavior can grow safely. +- USE Replace Type Code with Class, Subclasses, or State/Strategy according to whether behavior varies by type. +- USE Replace Subclass with Fields when subclass variation is only data. + +### Simplifying Calls and Conditionals +- USE Decompose Conditional, Consolidate Conditional Expression, and Consolidate Duplicate Conditional Fragments to make branching intent visible. +- USE Remove Control Flag when loop or conditional state can be expressed directly. +- USE Replace Nested Conditional with Guard Clauses when it clarifies the normal path. +- USE Replace Conditional with Polymorphism only when repeated type-based behavior justifies it. +- USE Introduce Null Object when repeated null behavior has a stable meaning. +- USE Introduce Assertion when an assumption should be explicit during development. +- USE Rename Method, Add Parameter, Remove Parameter, Parameterize Method, or Replace Parameter with Explicit Methods to make caller intent clearer. +- USE Preserve Whole Object when callers pass several values from the same object. +- USE Replace Parameter with Method when the receiver can obtain the value itself without hidden coupling. +- USE Remove Setting Method when post-construction mutation should not be allowed. +- USE Hide Method when public surface exposes unnecessary operations. +- USE Replace Constructor with Factory Method when creation intent or subtype selection needs a name. +- USE Encapsulate Downcast when callers should not own cast details. +- USE Replace Error Code with Exception or Replace Exception with Test according to the expected failure model. + +### Generalization and Big Refactorings +- USE Pull Up Field, Pull Up Method, or Pull Up Constructor Body when duplicated superclass behavior is real. +- USE Push Down Method or Push Down Field when only some subclasses need the feature. +- USE Extract Subclass, Extract Superclass, or Extract Interface only when callers or variation points justify them. +- USE Collapse Hierarchy when inheritance no longer adds meaning. +- USE Form Template Method when similar algorithms differ in controlled steps. +- USE Replace Inheritance with Delegation when inheritance couples unrelated responsibilities. +- USE Replace Delegation with Inheritance only when the subtype relationship is genuine and stable. +- USE Tease Apart Inheritance when one hierarchy mixes multiple variation axes. +- USE Convert Procedural Design to Objects when data and behavior need clearer ownership. +- USE Separate Domain from Presentation when UI and policy are tangled. +- USE Extract Hierarchy when several types share behavior with meaningful variation. + +--- + +## Function-Level Rules + +1. One function should usually perform one coherent task. +2. Keep abstraction level consistent inside a function. +3. Remove hidden side effects unless the function's purpose is to cause them. +4. Prefer guard clauses over deeply nested conditionals when that clarifies the happy path. +5. Split phases like parsing, validation, computation, and I/O when they are mixed together. +6. Keep variable scope tight. +7. Delete dead code rather than comment it out. + +--- + +## Class and Module Rules + +1. A class or module should have a narrow reason to change. +2. Separate policy from presentation, I/O, persistence, and framework details. +3. Prefer composition of small focused units over god objects. +4. Delete or inline abstractions that no longer pay for themselves. +5. Do not create `utils`, `helpers`, or `common` modules as a default response to duplication. +6. Organize modules around concepts and behavior, not leftover convenience. + +--- + +## Rules for Working with Conditionals + +1. Replace repeated branching on type or status with stronger modeling when useful. +2. Use lookup tables for stable mapping logic. +3. Replace nested if/else pyramids with guard clauses, extracted predicates, or strategies when that reduces branching complexity. +4. Keep explicit conditionals when they are simple and honest. +5. Never introduce polymorphism merely to avoid a small local conditional. + +--- + +## Data and Mutation Rules + +1. Encapsulate mutation. +2. Narrow write access to the smallest useful surface. +3. Replace ad hoc mutations with intention-revealing operations. +4. Remove duplicated update logic by centralizing state transitions. +5. Prefer immutable intermediate values when that simplifies reasoning. + +--- + +## Error Handling Rules + +1. Refactor error handling to make the main path visible. +2. Keep cleanup, validation, and recovery logic from drowning core behavior. +3. Standardize similar error paths when they duplicate structure. +4. Preserve existing error semantics unless intentionally changing behavior. + +--- + +## Review Rules + +When reviewing or generating changes, actively look for: +- duplicated logic +- long functions +- long classes +- tangled control flow +- mixed abstraction levels +- feature envy +- shotgun surgery +- divergent change +- pass-through layers +- speculative generality +- hidden side effects +- global state reliance +- code that requires too much context to change safely + +--- + +## Forbidden Patterns + +Do not generate or keep these patterns unless explicitly required and justified. + +### Big-Bang Rewrite +- replacing a working subsystem wholesale to “clean it up” +- rewriting before understanding current behavior +- changing structure and behavior in one giant move + +### Mixed-Intent Patches +- feature work mixed with huge unrelated renames +- behavior changes hidden inside cleanup +- code motion that makes review impossible + +### Abstracting Too Early +- introducing interfaces or strategy hierarchies before a second real need appears +- creating common libraries for one caller +- replacing understandable duplication with unclear shared code + +### Refactoring Theater +- renaming things while deeper design problems remain untouched +- introducing patterns instead of removing complexity +- creating more files, layers, or wrappers without improving changeability + +### Untested Structural Surgery +- large refactors without any safety net +- “cleanup” on fragile code with no verification strategy +- assuming behavior is obvious when it is not + +--- + +## Code Generation Rules + +When asked to modify existing code, use this default order: +1. understand current behavior +2. identify the friction for the requested change +3. add or improve the safety net if needed +4. perform preparatory refactoring +5. implement the behavioral change +6. perform follow-up cleanup +7. stop when the design is clearly better + +Preferred first moves: +- rename badly named things +- extract coherent functions +- isolate side effects +- split mixed responsibilities +- move behavior closer to the owning concept +- remove duplication +- simplify conditionals + +Preferred avoidance: +- unnecessary framework migrations +- gratuitous API redesign +- large hierarchy introduction +- replacing all old code with new code because the old code is ugly + +--- + +## Testing Rules + +1. Add characterization tests before risky edits when behavior is unclear. +2. Keep tests focused on externally visible behavior. +3. Update tests only when behavior intentionally changes. +4. Do not couple tests to private implementation details more than necessary. +5. Refactor tests too when they become noisy or duplicative. +6. Keep test data expressive and minimal. + +--- + +## Stopping Rules + +Stop refactoring when: +- the requested change is easy to implement +- the main smells blocking change are removed +- further cleanup would become speculative +- the next abstraction is not yet justified +- readability and local changeability are clearly improved + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Did we preserve observable behavior during refactoring? +- Did we separate structural change from behavior change where practical? +- Did we remove at least one real source of friction? +- Is the code easier to read than before? +- Is the code easier to test or change than before? +- Did we reduce duplication or accidental complexity? +- Did we avoid speculative abstraction? +- Did we avoid a giant mixed patch? +- Did names improve? +- Did control flow become simpler? +- Did responsibilities become clearer? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the next **small, behavior-preserving transformation** +that makes the requested change easier and the code easier to understand. +Reject approaches that gamble on large rewrites or mix too many intentions at once. diff --git a/programming-principles/references/refactoring.mini.md b/programming-principles/references/refactoring.mini.md new file mode 100644 index 0000000..5fb9655 --- /dev/null +++ b/programming-principles/references/refactoring.mini.md @@ -0,0 +1,39 @@ +# OBEY Refactoring by Martin Fowler + +## When to use + +Use when changing existing code, preparing a feature or bug fix, reviewing cleanup, or reducing structural friction without intending to change observable behavior. + +## Primary bias to correct + +Refactoring is behavior-preserving design work in small steps. Do not turn cleanup into a rewrite, a hidden feature change, or speculative architecture. + +## Decision rules + +- Preserve observable behavior during refactoring. Isolate behavior changes from structural changes. +- Work in small, reversible, buildable, testable, reviewable steps. Split a patch when it is too large to reason about locally. +- Establish or identify a safety net before risky refactoring. Use characterization tests for unclear behavior. +- Use preparatory and follow-up refactoring around feature work: reshape blocking structure first, make the behavior change, then clean debt. +- Refactor the current blocking smell, not every smell in sight: duplication, long functions, long parameter lists, globals, divergent change, shotgun surgery, feature envy, primitive obsession, repeated conditionals, temporary fields, middle men, speculative generality. +- Prefer the simplest named move: rename, extract, inline, move, introduce parameter object, encapsulate, decompose conditionals, use guard clauses, substitute algorithm. +- Make names and functions reveal intent. Rename before deeper work when bad names block understanding. +- Put behavior and state with the concept that owns them. Split classes with multiple reasons to change. +- Simplify conditionals honestly. Use polymorphism, state, strategy, or tables only when variation is real and repeated. +- Keep patch intent reviewable. Group related refactorings. Avoid giant mixed patches. +- Stop when the requested change is easy, the blocking smell is gone, and the next cleanup would be speculative. + +## Trigger rules + +- When adding behavior, first ask what structural friction blocks the change. +- When fixing a bug in unclear code, characterize the current failure first. +- When the same edit appears a third time, remove duplication. +- When a function mixes responsibilities, rename, extract, or split. +- When one change forces edits across many files, centralize the knowledge. +- When tempted to rewrite, choose the next small behavior-preserving transformation. + +## Final checklist + +- Observable behavior preserved? +- Structural change, behavior change, and test updates separated? +- At least one real source of friction removed? +- Patch still reviewable and runnable? diff --git a/programming-principles/references/release-it.full.md b/programming-principles/references/release-it.full.md new file mode 100644 index 0000000..e8e84ca --- /dev/null +++ b/programming-principles/references/release-it.full.md @@ -0,0 +1,382 @@ +# OBEY Release It! by Michael T. Nygard + +## Purpose + +This repository follows **Release It!** in the sense of Michael Nygard: +design and implement software that survives production reality - failures, overload, latency, partial outages, bad data, hostile traffic, and operational mistakes. + +All code generation, edits, and reviews must optimize for: +- production readiness +- failure isolation +- graceful degradation +- back pressure and overload protection +- timeouts and retries with discipline +- observability +- survivability over ideal-path elegance + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Assume production will be messy. + +When uncertain, prefer the design that: +1. fails visibly instead of hanging silently +2. limits blast radius instead of maximizing coupling +3. sheds load instead of collapsing +4. preserves core service under stress +5. makes diagnosis possible + +Do not design only for the happy path. + +--- + +## Stability Mindset Rules + +1. Every dependency can be slow, unavailable, or wrong. +2. Every queue can fill. +3. Every cache can miss or stampede. +4. Every timeout can cascade. +5. Every caller can retry badly. +6. Every “temporary” degraded state can become normal for hours. + +The code must assume these conditions, not merely tolerate them by accident. + +--- + +## Production Readiness and Release Risk Rules + +1. Do not treat QA success or feature completion as proof of production readiness. +2. Design deployment, operations, security, observability, and rollback as part of the system. +3. Reduce release risk through small exposure steps, compatibility discipline, and reversible changes. +4. Make version, build, configuration, dependency, and runtime state visible enough to diagnose a live instance. +5. Validate critical configuration at startup and make configuration changes auditable and reversible. + +--- + +## Dependency Protection Rules + +### Timeouts Are Mandatory +1. Outbound calls must have explicit time limits. +2. Timeouts must be chosen intentionally, not left to library defaults. +3. Different dependencies may need different timeout budgets. +4. Infinite waits are forbidden. + +### Retries Must Be Disciplined +1. Retry only where repeated attempts are safe for the caller and provider. +2. Bound retry count and total retry time. +3. Add jitter/backoff to avoid synchronized retry storms. +4. Do not retry validation errors or permanent failures. + +### Circuit Breakers and Fast Failure +1. Protect unstable dependencies with fast-fail mechanisms when appropriate. +2. When a dependency is clearly unhealthy, stop flooding it. +3. Surface fallback or degraded mode explicitly. + +### Bulkheads and Isolation +1. Separate resource pools for unrelated workloads where failure isolation matters. +2. One failing integration must not consume all threads, connections, or worker capacity. +3. Isolate slow or risky work from core request paths. + +Anti-patterns (MUST NOT): +- nested retries at multiple layers +- no timeout around remote calls +- one shared pool for all outbound work +- treating all failures as transient + +--- + +## Load and Capacity Rules + +### Back Pressure +1. The system must have a strategy for overload. +2. Reject, defer, queue, or degrade intentionally. +3. Unbounded acceptance of work is forbidden. + +### Queues +1. Queues are buffers, not infinite storage. +2. Monitor queue length, age, throughput, and failure rate. +3. Know what happens when producers outpace consumers. +4. Define dead-letter or poison-message handling explicitly. + +### Demand Control +1. Protect scarce resources with limits. +2. Prefer early rejection over total collapse. +3. Reserve capacity for critical traffic when appropriate. + +### Load Shedding +1. Define which work is optional under stress. +2. Shed low-value work first. +3. Preserve core functions whenever possible. + +### Additional Stability Patterns +- USE Steady State design so routine operation does not require manual cleanup, unbounded growth, or periodic rescue. +- USE Fail Fast when continuing would hold scarce resources or hide an unrecoverable dependency problem. +- USE Let It Crash only when supervisors, isolation, and restart behavior make crashing safer than limping. +- USE Handshaking between instances, load balancers, and dependencies so traffic reaches only ready components. +- USE Decoupling Middleware when it reduces direct failure propagation; monitor the middleware as a dependency. +- USE Governors to cap expensive behavior before it harms the rest of the system. + +Anti-patterns (MUST NOT): +- unbounded queues +- accepting work with no plan to finish it +- letting best-effort tasks crowd out critical work + +--- + +## Runtime State and Restart Safety Rules + +1. Make runtime state visible through logs, metrics, health endpoints, administrative interfaces, and diagnostic data. +2. Validate external responses by status, content type, shape, and semantics before trusting them. +3. Make deployment and operational automation idempotent or restartable where practical. +4. Avoid partial deployment or migration steps without a rollback or roll-forward path. +5. Validate operational assumptions at system boundaries. + +### Restartable Automation +Required when: +- deployments touch many machines +- scripts may be rerun after partial failure +- migrations run while old and new application versions coexist +- operational procedures must be repeatable under release pressure + +Anti-patterns (MUST NOT): +- one-shot deployment scripts that cannot safely resume +- manual repair steps with no recorded state +- side effects before a durable release checkpoint with no recovery plan + +--- + +## Resource Management Rules + +1. Explicitly budget scarce resources: + - threads + - DB connections + - sockets + - file descriptors + - memory + - CPU-intensive worker slots +2. Release resources deterministically. +3. Do not hold locks or expensive connections across slow remote calls. +4. Use streaming or pagination for large payloads where appropriate. +5. Guard memory-heavy operations. + +Anti-patterns (MUST NOT): +- one huge in-memory batch by default +- blocking worker threads on slow I/O when a better model exists +- connection pools sized by guess and then ignored + +--- + +## Data Boundary Rules + +1. Treat all external input as untrusted. +2. Validate syntax, shape, and business plausibility separately where needed. +3. Avoid letting malformed data poison caches, queues, or downstream systems. +4. Normalize and sanitize data once at the right boundary. +5. Keep parsing errors and domain rule violations distinct. + +--- + +## Operational Visibility Rules + +### Observability Is Part of the Design +1. Emit meaningful logs at boundaries and failure points. +2. Include identifiers needed for correlation and diagnosis. +3. Measure latency, throughput, error rate, saturation, queue depth, and retry behavior. +4. Expose health information that reflects real dependency state. + +### Logging +1. Log structured context, not just prose. +2. Log failures with the dependency, operation, and outcome. +3. Do not log secrets. +4. Avoid log spam loops under retry storms. + +### Metrics +At minimum, capture: +- request rate +- success/failure counts +- dependency latency +- timeout counts +- queue depth +- retry counts +- circuit-breaker state +- saturation signals + +Anti-patterns (MUST NOT): +- only logging stack traces without context +- no metrics for slow dependencies +- health checks that always return green despite broken downstreams + +--- + +## Incidents, Capacity, and Runtime Control + +1. After incidents, identify the failure chain, missing defenses, detection gaps, and design changes. +2. For performance or capacity incidents, inspect demand, saturation, latency distribution, queue age, dependency behavior, and traffic concentration. +3. Provide administrative interfaces or operational controls only with authorization, auditability, safe defaults, and clear stop mechanisms. +4. Keep process code, scripts, and automation observable enough that operators can see what changed and why. +5. Treat control planes and delivery tooling as production systems when they can affect production. + +--- + +## Deployment and Startup Rules + +1. Startup must fail fast on missing critical configuration. +2. Health checks must reflect actual ability to serve. +3. Health checks must not mask deadlocks or stuck subsystems. +4. Avoid expensive or destructive startup work in request-serving processes when possible. +5. Migrations and one-time jobs must be deliberate, observable, and recoverable. + +--- + +## Interconnect, Routing, Security, and Chaos Rules + +1. Keep DNS, service discovery, routing, and load balancing health-aware and current. +2. Design interconnects to avoid concentrated demand, hidden single points of failure, and uncontrolled fan-out. +3. Treat hostile traffic, abusive users, and malformed requests as production load cases. +4. Include security in production readiness: secrets, permissions, administrative access, dependency trust, and input handling. +5. Use production tests, launch checks, capacity tests, and game days to validate operational assumptions. +6. Run chaos or disaster simulations only with a hypothesis, limited blast radius, observability, stop condition, and recovery path. +7. Feed findings from chaos and disaster work back into design, operations, and tests. + +--- + +## API and Contract Rules + +1. Make failure modes explicit in API contracts where they matter. +2. Return clear retryable vs non-retryable outcomes. +3. Prefer coarse-grained, resilient interactions over fragile chattiness. +4. Use versioning and compatibility discipline for long-lived contracts. +5. Document retry, timeout, version, and compatibility expectations clearly. + +--- + +## Cache Rules + +1. Cache is an optimization, not a source of truth unless explicitly designed that way. +2. Plan for cache miss storms, stale data, and cache outages. +3. Avoid dogpiles with request coalescing or appropriate expiry strategies. +4. Define what happens when the cache is unavailable. + +Anti-patterns (MUST NOT): +- assuming cache hit rate is always high +- rebuilding the whole cache synchronously on miss +- hiding correctness assumptions inside cache behavior + +--- + +## Scheduled and Background Work Rules + +1. Spread scheduled work so demand does not concentrate at the same instant. +2. Do not set all periodic jobs to run on the same obvious clock boundary. +3. Failure and retry policy must be explicit. +4. Retried work must use increasing backoff where synchronized retry pulses would create load. +5. Long-running work needs bounded waits, progress visibility, timeout, and cancellation strategy. + +--- + +## Review Rules + +When reviewing code, actively look for: +- outbound calls with no timeout +- retries without backoff, limits, or a clear failure policy +- no backoff or jitter +- unbounded queues or buffers +- shared resource pools with no isolation +- no overload strategy +- no failure visibility +- health checks that say nothing meaningful +- scheduled jobs concentrating load at the same instant +- caches treated as always available + +--- + +## Forbidden Patterns + +### Happy-Path Design +- code that assumes dependencies are fast and correct +- no timeout, no retry discipline, no degradation path + +### Retry Storms +- retries at every layer +- retries with no increasing backoff or limits +- synchronized retries without jitter + +### Collapse by Queue +- unbounded queue growth +- taking work forever even while falling behind +- no poison-message handling + +### Silent Failure +- swallowed exceptions +- generic “something went wrong” without context +- missing correlation information + +### Blast-Radius Amplification +- one dependency outage consuming all worker threads or DB connections +- shared pools for all risk classes with no isolation + +--- + +## Code Generation Rules + +When generating code, default to: +1. explicit timeout for every remote dependency +2. explicit retry policy only where safe +3. restartable deployment and operational automation where practical +4. bounded resources and queues +5. clear failure paths +6. useful diagnostic hooks +7. graceful degradation or fast failure where appropriate + +Avoid by default: +- infinite waits +- implicit library retries +- unbounded buffering +- best-effort logging with no metrics +- fragile startup sequences +- one-shot release automation with no restart path + +--- + +## Testing Rules + +1. Test timeout behavior. +2. Test retry, backoff, and failure boundaries. +3. Test degraded dependency scenarios. +4. Test overload and queue saturation behavior where practical. +5. Test restartable deployment or operational automation where practical. +6. Test startup and health-check failure modes. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Does every remote call have an explicit timeout? +- Are retries bounded and safe? +- Are deployment and operational scripts restartable or idempotent where practical? +- Is there an overload strategy? +- Are queues and resource pools bounded? +- Is failure isolated from unrelated work? +- Are there enough diagnostics to investigate issues? +- Are health signals meaningful? +- Are scheduled and background workloads bounded and paced safely? +- Did we preserve the core service under likely failure scenarios? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, prefer the design that: +1. survives partial failure +2. limits blast radius +3. fails fast or degrades gracefully +4. exposes enough information to operate +5. prevents overload from becoming collapse + +Production reality outranks happy-path elegance. diff --git a/programming-principles/references/release-it.mini.md b/programming-principles/references/release-it.mini.md new file mode 100644 index 0000000..10ce0fd --- /dev/null +++ b/programming-principles/references/release-it.mini.md @@ -0,0 +1,38 @@ +# OBEY Release It! by Michael T. Nygard + +## When to use + +Use for services, APIs, jobs, queues, deployment paths, control tooling, and critical flows that must survive production failures, overload, latency, bad data, hostile traffic, and operational mistakes. + +## Primary bias to correct + +A passing happy path is not production readiness. Design the failure semantics, demand limits, isolation, recovery path, and diagnosis surface before production defines them for you. + +## Decision rules + +- Assume every dependency, queue, cache, timeout, caller retry, and degraded state can fail in slow, partial, or prolonged ways. +- Prefer designs that fail visibly, limit blast radius, shed load, preserve core service, and make diagnosis possible. +- Treat deployment, operations, security, observability, rollback, and configuration validation as part of the system. +- Put explicit, intentional time limits on outbound calls and waits. No infinite waits. +- Retry only when safe for caller and provider. Bound count and total time. Use backoff or jitter. +- Isolate dependency failures with circuit breakers, fast failure, bulkheads, separate resource pools. +- Design overload behavior explicitly with back pressure, finite queues, demand limits, and load shedding. +- Budget scarce resources explicitly. Release them deterministically. Stream or paginate large payloads. +- Treat external input and responses as untrusted. Validate syntax, shape, business plausibility, semantics. +- Build observability into boundaries: structured context, correlation IDs, latency, throughput, error, saturation, queue depth, breaker state, dependency health. +- Make startup, health checks, migrations, and operational controls fail safely, auditable, stoppable, recoverable. + +## Trigger rules + +- When adding an outbound call, define timeout, retry eligibility, fallback, validation, and caller-survival behavior. +- When adding a queue, buffer, pool, cache, or background job, define capacity, full behavior, cleanup, and saturation monitoring. +- When a change touches deployment, config, startup, or migrations, make it idempotent and give it rollback. +- When designing API contracts, make failure modes explicit, distinguish retryable from non-retryable. + +## Final checklist + +- Explicit timeouts and no infinite waits? +- Retries safe, bounded, backed off? +- Failure isolated with breakers, bulkheads, load shedding? +- External input validated before trusted? +- Startups and migrations restartable and observable? diff --git a/programming-principles/references/the-pragmatic-programmer.full.md b/programming-principles/references/the-pragmatic-programmer.full.md new file mode 100644 index 0000000..c20070a --- /dev/null +++ b/programming-principles/references/the-pragmatic-programmer.full.md @@ -0,0 +1,359 @@ +# OBEY The Pragmatic Programmer by Andrew Hunt and David Thomas + +## Purpose + +This repository follows **The Pragmatic Programmer** in the sense of Andrew Hunt and David Thomas: +work pragmatically, take responsibility for quality, automate what is repetitive, and keep code and process adaptable. + +All code generation, edits, and reviews must optimize for: +- clear ownership and responsibility +- avoiding duplicated knowledge +- orthogonality +- incremental delivery +- ruthless feedback +- automation of repetitive work +- code that is easy to change and easy to reason about + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Be pragmatic, not dogmatic. + +When uncertain, choose the option that: +1. reduces knowledge duplication +2. keeps concerns independent +3. shortens feedback loops +4. leaves the system easier to change +5. makes intent clearer to future maintainers + +Do not follow style or process rituals that do not improve outcomes. + +--- + +## Core Pragmatic Principles + +### Own the Result +1. Take responsibility for the quality and changeability of the code you touch. +2. Do not blame tooling, framework defaults, or “existing style” for avoidable bad design. +3. Surface trade-offs, risks, and uncertainty explicitly. + +### Think Beyond the Local Edit +1. Every change affects future maintainability. +2. Small quick fixes that multiply future cost are usually a bad bargain. +3. Leave the area better than you found it. + +### Favor Adaptability +1. Build systems that are easy to observe, test, and change. +2. Prefer flexible boundaries over brittle cleverness. +3. Avoid premature commitment when requirements are still moving. + +### Named Pragmatic Habits +1. Treat quality as a requirement to negotiate with users and sponsors, not as an abstract pursuit of perfection. +2. Stop polishing when the software is good enough for its real users and risks. +3. Keep a knowledge portfolio: invest in learning, diversify skills, and revisit stale assumptions. +4. Communicate decisions, risks, and tradeoffs clearly enough that others can act on them. +5. Watch for entropy and small broken windows before they become normal. +6. Use Stone Soup tactics only to create real progress, not to hide missing agreement. +7. Watch for boiled-frog drift where gradual degradation becomes invisible. + +--- + +## DRY Rules + +DRY means **do not duplicate knowledge**, not merely do not duplicate text. + +1. A business rule should have one authoritative representation. +2. Validation logic for the same concept should not be scattered. +3. Status semantics, mappings, and calculations should not be copied across layers. +4. Configuration and schema meaning should not be repeated inconsistently. +5. Avoid duplicated process steps that can be automated. + +Anti-patterns (MUST NOT): +- the same rule encoded in UI, API, service, and DB trigger with no ownership +- copy/paste with minor edits for “just this one case” +- duplicated manual deployment or testing steps +- one concept with multiple partially aligned implementations + +--- + +## Orthogonality Rules + +1. Keep components independent so one change does not force unrelated changes elsewhere. +2. Minimize hidden couplings through globals, ambient context, or shared mutable state. +3. Avoid overlapping responsibilities between modules. +4. Separate policy from mechanism, data from presentation, orchestration from computation. + +Anti-patterns (MUST NOT): +- one change requiring edits in many unrelated places +- one module knowing too much about internal details of others +- shared utility modules creating sideways coupling everywhere + +--- + +## Tracer Bullets and Iterative Delivery + +1. Prefer a thin end-to-end slice over a pile of isolated pieces. +2. Use tracer bullets to validate architecture, integration, and assumptions early. +3. Keep the first slice simple but real enough to prove the path. +4. Refine from working feedback instead of predicting everything up front. + +Anti-patterns (MUST NOT): +- building many layers before anything runs end to end +- treating prototypes as production without hardening +- waiting for perfect certainty before integrating + +--- + +## Reversibility, Domain Languages, and Requirements + +1. Preserve reversibility when requirements, vendors, platforms, databases, or deployment environments may change. +2. Avoid irreversible commitments until evidence makes them worth the cost. +3. Use a small domain language when it expresses domain rules more directly than general-purpose code. +4. Keep domain languages readable by the people who must validate or change them. +5. Dig for real requirements; do not accept current implementation details as requirements. +6. Do not fall into the specification trap where prose keeps growing but uncertainty does not fall. +7. Start building a working slice when further specification no longer reduces meaningful risk. +8. Respect informed hesitation: if the team is not ready, identify the missing information or feedback. + +--- + +## Prototyping Rules + +1. Use prototypes to learn, not to pretend you are done. +2. Be explicit about what a prototype proves and what it does not. +3. Do not let experimental shortcuts silently become production defaults. +4. Carry forward only the lessons or code that still deserve to survive. + +--- + +## Automation Rules + +1. Automate repetitive, error-prone, or easy-to-forget tasks. +2. Prefer repeatable scripts over tribal-knowledge commands. +3. Build, test, lint, format, package, and deploy steps should be reproducible. +4. Keep local automation aligned with the project's shared build, test, and release automation. + +Anti-patterns (MUST NOT): +- “works on my machine” build steps +- manual release rituals with many hidden prerequisites +- documentation that describes what a script should do instead of having the script + +--- + +## Feedback Loop Rules + +1. Shorten the time between change and feedback. +2. Run relevant tests early and often. +3. Use automated checks where they reduce real risk. +4. Make failure visible fast. +5. Prefer a cheap early signal over a late expensive surprise. + +--- + +## Design by Contract and Assertions + +1. Make assumptions explicit in code. +2. Use assertions or invariant checks where they clarify impossible states. +3. Distinguish between programmer errors, contract violations, and expected domain failures. +4. Keep contracts close to the abstraction they protect. + +Anti-patterns (MUST NOT): +- relying on comments for critical preconditions +- hiding invariant assumptions in scattered callers +- returning nonsense values for impossible states + +--- + +## Error Handling and Recovery + +1. Detect errors close to their source. +2. Do not discard useful error context. +3. Let callers distinguish retryable, recoverable, and permanent failures where relevant. +4. Fail loudly enough to diagnose, but with boundaries that prevent system-wide collapse. + +--- + +## Naming and Communication Rules + +1. Code is communication first. +2. Use names that reflect domain meaning and developer intent. +3. Prefer clarity over cleverness. +4. Write comments or docs where they convey decision rationale, contracts, or non-obvious behavior. +5. Writing is part of engineering, not overhead. + +--- + +## Text and Data Rules + +1. Favor plain text and open formats for long-lived automation and integration where practical. +2. Make scripts and configs inspectable and diffable. +3. Keep serialization and config formats explicit and version-aware. +4. Avoid opaque binary or framework-specific lock-in unless justified. + +--- + +## State and Concurrency Rules + +1. Treat shared mutable state as expensive. +2. Prefer immutability, isolation, or explicit synchronization when state is shared. +3. Keep concurrency assumptions visible. +4. Do not add asynchronous complexity unless it clearly earns its cost. + +--- + +## Estimation and Increment Rules + +1. Break work into pieces that can be reasoned about, tested, and corrected. +2. Keep plans and estimates honest about uncertainty. +3. Prefer small deliverable increments to large hidden progress. +4. Make risk visible early. + +--- + +## Tooling Rules + +1. Know and use the tools that amplify correctness and speed. +2. Do not hand-do tasks that should be scripted. +3. Keep editor, formatter, lint, tests, and local scripts aligned with team standards. +4. Improve the toolchain when repeated friction appears. + +### Basic Tool Rules +- Use source control for every meaningful project, including small or solo work. +- Prefer inspectable plain text for long-lived scripts, configs, data, and generated sources when practical. +- Use shell tools for exploration, automation, and repeatable transformations where they fit. +- Use editor capabilities to reduce repetitive manual edits. +- Use text manipulation languages or scripts for systematic changes that would be error-prone by hand. +- Use code generators to remove duplicated mechanical work, but keep the source specification authoritative. +- When debugging, do not guess: reproduce, observe, isolate, explain, fix, and verify. +- Do not rely on generated code, tools, specifications, or formal methods you do not understand. + +--- + +## Resource and Coupling Rules + +1. Finish what you start when allocating, opening, locking, or otherwise acquiring resources. +2. Release every resource you acquire, preferably in the opposite order from acquisition. +3. Keep resource ownership local and explicit. +4. Apply shy-code and Law of Demeter discipline so modules reveal only necessary information. +5. Avoid temporal coupling; make ordering requirements explicit or remove them. +6. Use metaprogramming only when it reduces duplication or improves adaptability without hiding behavior. +7. Use blackboard-style coordination only when uncertain order, multiple sources, or opportunistic collaboration justify it. +8. Understand algorithmic growth before writing or accepting performance-sensitive code. + +--- + +## Project and Team Rules + +1. Build pragmatic teams around shared responsibility, automation, fast feedback, and visible quality. +2. Test unit behavior, integration, validation and verification, resource exhaustion, errors and recovery, performance, usability, and tests themselves where relevant. +3. Treat writing as engineering work: docs, comments, commit messages, scripts, and tests must communicate intent. +4. Set expectations explicitly with users and stakeholders. +5. Take pride in code, tests, documentation, and generated artifacts. +6. Be skeptical of methods, diagrams, and ceremonies that do not improve the work. + +--- + +## Broken Windows Rule + +1. Do not normalize local decay. +2. Fix small quality problems before they signal that nobody cares. +3. Tidy the code you touch where the cost is low and the value is immediate. +4. Avoid leaving behind “temporary” hacks with no cleanup plan. + +--- + +## Review Rules + +When reviewing code, actively look for: +- duplicated knowledge, not just duplicated lines +- hidden couplings +- missing automation opportunities +- long feedback loops +- local fixes that worsen future changeability +- unclear contracts or assumptions +- non-repeatable manual processes +- brittle integration points +- code that communicates poorly + +--- + +## Forbidden Patterns + +### Cargo-Cult Process +- rituals followed with no benefit +- documentation and checklists replacing automation + +### Knowledge Duplication +- same rule in many places +- copied logic because “layers need it too” + +### Non-Orthogonal Design +- modules with overlapping responsibilities +- changes leaking across boundaries by default + +### Manual Everything +- repeated human steps for build, test, release, setup, or validation +- hidden local environment assumptions + +### Prototype Fossilization +- experimental code promoted to production without redesign or hardening + +--- + +## Code Generation Rules + +When generating code, default to: +1. one clear source of truth for each rule +2. orthogonal responsibilities +3. fast local feedback +4. automation over repeated manual work +5. explicit contracts and assumptions +6. readable names and communication +7. incremental end-to-end slices when building new capabilities + +Avoid by default: +- copy/paste rule duplication +- tangled modules +- fragile manual workflows +- overcommitting to an architecture before the first end-to-end path works + +--- + +## Testing Rules + +1. Keep tests runnable quickly and often. +2. Prefer tests that align with the business or technical contract being protected. +3. Use automation so validation is habitual, not heroic. +4. Keep flaky or environment-dependent tests out of the critical feedback path where possible. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Did we reduce duplicated knowledge? +- Are responsibilities more orthogonal after the change? +- Did we improve or preserve fast feedback? +- Did we automate anything repetitive that was hurting reliability? +- Are contracts and assumptions clearer? +- Is the code easier to communicate about? +- Did we avoid prototype shortcuts becoming silent production defaults? +- Did we fix at least one small “broken window” if it was in the touched area? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the option that: +1. removes duplicated knowledge +2. keeps concerns orthogonal +3. shortens feedback loops +4. improves automation +5. leaves the codebase easier to change tomorrow + +Be pragmatic, and make the right thing the easy thing. diff --git a/programming-principles/references/the-pragmatic-programmer.mini.md b/programming-principles/references/the-pragmatic-programmer.mini.md new file mode 100644 index 0000000..22ae233 --- /dev/null +++ b/programming-principles/references/the-pragmatic-programmer.mini.md @@ -0,0 +1,47 @@ +# OBEY The Pragmatic Programmer by Andrew Hunt and David Thomas + +## When to use + +Use as a general engineering operating style when the goal is accountable delivery, adaptability, fast feedback, and code that remains easy to change. + +## Primary bias to correct + +Do not optimize only for the local edit, requested feature, or familiar ritual. Own the outcome by reducing duplicated knowledge, keeping concerns independent, proving assumptions early, and automating repeated work. + +## Decision rules + +- Be pragmatic, not dogmatic. Choose the practice that improves real outcomes. +- Own the result. Surface tradeoffs, risks, and avoidable design costs. +- Keep one authoritative representation for each piece of system knowledge. Derive or trace everything else. +- Preserve orthogonality: independent components, narrow interfaces, separated concerns. +- Keep volatile decisions reversible. Do not hard-code vendors or environments before evidence justifies commitment. +- Use domain vocabulary and small domain languages when they make rules clearer. +- Prefer thin end-to-end tracer bullets over piles of isolated pieces. +- Use prototypes to learn, not to pretend the work is done. +- Dig for real requirements. Separate durable needs from current proposed solutions. +- Automate repetitive, error-prone, easy-to-forget work. Builds, tests, linting, deployment. +- Shorten feedback loops with relevant tests, automated checks, cheap early signals. +- Make contracts, assumptions, invariants, and obligations explicit and close to the abstraction. +- Distinguish programmer errors, contract violations, expected failures, retryable failures, permanent failures. +- Treat resource ownership as a contract. Release every acquired allocation on success and failure paths. +- Use tooling as leverage, but understand generated code and tool output before relying on it. +- Debug from reproduced facts: observe, isolate, explain, fix, verify. +- Communicate through code, names, docs, comments, commit messages, scripts, and tests. +- Apply the broken windows rule: fix or visibly contain small quality decay before it normalizes. + +## Trigger rules + +- When the same fact appears in multiple artifacts, choose one owner and derive the rest. +- When one change requires edits in many unrelated places, repair the missing boundary. +- When uncertainty is high, reduce risk with tracer feedback or a prototype. +- When hidden assumptions live only in comments or tribal setup steps, move them into code or tests. +- When repeated manual steps appear, automate and version them. +- When a human finds a bug, add an automatic regression test. + +## Final checklist + +- One authoritative owner for each system fact? +- Unrelated concerns independent and volatile choices reversible? +- Working feedback exists for risky assumptions? +- Repeatable work automated and versioned? +- Tests automatic, relevant, and run before done? diff --git a/programming-principles/references/working-effectively-with-legacy-code.full.md b/programming-principles/references/working-effectively-with-legacy-code.full.md new file mode 100644 index 0000000..74279fd --- /dev/null +++ b/programming-principles/references/working-effectively-with-legacy-code.full.md @@ -0,0 +1,371 @@ +# OBEY Working Effectively with Legacy Code by Michael Feathers + +## Purpose + +This repository follows the discipline of **Working Effectively with Legacy Code** in the sense of Michael Feathers: +make risky existing code changeable by gaining understanding, creating seams, and establishing tests. + +All code generation, edits, and reviews must optimize for: +- safe change in poorly understood code +- characterization before redesign +- breaking dependencies that block tests +- introducing seams +- reducing fear around modification +- incremental improvement instead of heroic rewrites + +This file is a binding engineering policy: `MUST` is binding, `SHOULD` is a strong default, and `MUST NOT` is forbidden. + +--- + +## Primary Directive + +Legacy code is code that is expensive to change safely. +In practice, the default assumption is: + +**If a part of the code lacks trustworthy tests, treat it as legacy code.** + +When modifying legacy code: +1. understand what it does now +2. protect that behavior with tests where possible +3. find or create a seam +4. break dependencies that prevent observation or isolation +5. make the requested change +6. leave the area more testable than before + +Do not begin with a rewrite unless explicitly required. + +--- + +## Non-Negotiable Rules + +1. **Do Not Rewrite by Reflex** + - Prefer targeted extraction and improvement. + - Rewrite only when explicitly requested or clearly safer than continued change. + +2. **Characterize Before You Redesign** + - When current behavior is uncertain, capture it. + - Use characterization tests to document what the code does today, even if the behavior is ugly. + +3. **Find or Create a Seam** + - A seam is a place where behavior can be changed without editing the surrounding code directly. + - Use seams to inject doubles, isolate dependencies, and observe behavior. + +4. **Break Dependencies Deliberately** + - Remove direct dependence on time, randomness, files, network, process environment, globals, frameworks, and static construction where they block testing. + +5. **Leave the Code More Changeable** + - Every change should ideally improve testability, visibility, or modularity. + +--- + +## Default Workflow for Legacy Changes + +1. Identify the exact area affected. +2. Determine whether trusted tests already protect the behavior. +3. If not, add characterization tests around current behavior where possible. +4. Identify the dependency that makes change difficult. +5. Introduce or exploit a seam. +6. Break the blocking dependency. +7. Make the functional change. +8. Refactor for clarity and keep the seam or new structure if it still pays for itself. + +Short form: identify change points, find test points, break dependencies, write tests, make changes, then refactor. + +Do not start by cleaning the whole module. + +--- + +## Testing Strategy Rules + +### Characterization Tests +1. Use characterization tests when you do not yet know whether the current behavior is intentional. +2. Test externally visible behavior first. +3. Prefer narrow tests around the slice you are about to modify. +4. Capture ugly behavior if real consumers rely on it. +5. Once behavior is protected, improve structure safely. +6. Mark suspicious current behavior for clarification instead of silently "fixing" it during characterization. +7. Use sensing variables or temporary probes only to confirm that a test reaches the intended path; remove them after use. + +### New Behavior Tests +1. Add focused tests for the requested change. +2. Keep old behavior tests unless the behavior change is intentional. +3. Separate tests that describe legacy behavior from tests that describe the new requirement when useful. + +### Testability Improvements +1. Make dependencies explicit. +2. Remove hard-coded collaborators. +3. Break apart mixed responsibilities that force expensive setup. +4. Reduce constructor side effects and static initialization side effects. + +--- + +## Seam Rules + +### What Counts as a Useful Seam +A useful seam is any boundary that allows substitution, observation, or interception. + +Examples: +- constructor injection +- parameter injection +- extracted method +- wrapper around static call +- adapter around framework object +- factory indirection +- module boundary +- link seam, import seam, or preprocessing seam where the language/build system supports it +- subclass seam when forced by language constraints + +### Required Behavior +1. Use the smallest seam that unlocks the change. +2. Prefer explicit seams over magical test hooks. +3. Prefer seams that remain useful after the current task. +4. Create seams near hard dependencies, not randomly in the code. +5. Separate sensing from separation: decide whether the seam observes behavior, substitutes a dependency, or both. +6. Use link and preprocessing seams carefully; they can unlock tests but do not usually improve design by themselves. + +--- + +## Dependency Breaking Rules + +When legacy code is hard to test, first look for these dependency types: + +### Hidden Inputs +- current time +- random values +- environment variables +- thread-local state +- static singletons +- global configuration +- implicit current user or request + +### Hard Outputs +- direct file writes +- direct network calls +- process exits +- direct database writes +- direct message publication +- logging used as control flow + +### Construction Problems +- constructors that do real work +- new allocations of complex collaborators buried inside methods +- factory calls hidden deep in behavior +- object graphs built in the middle of logic + +### Required Moves +- wrap static and global access +- inject clocks, random generators, external interfaces, and hard collaborators +- split construction from use +- extract side effects behind explicit collaborators +- narrow the code under test to a manageable slice + +--- + +## Test Selection and Understanding Rules + +1. Use effect sketches when the impact of a change is unclear. +2. Start from the change point and trace affected values, calls, fields, outputs, and collaborators outward. +3. Choose test points where effects can be observed with useful precision. +4. Use interception points when several planned changes can be protected by one broader test. +5. Use pinch points when many effects pass through one narrow point. +6. Treat broad tests at interception points as a first step toward narrower tests. +7. Use scratch refactoring to understand code, but discard it unless later backed by tests and review. +8. Sketch, mark, or group responsibilities in large code before moving behavior. +9. Do not check in exploratory restructuring that was only used to learn. + +--- + +## Preferred Legacy Techniques + +### Sprout Method +Use when new behavior can be added without deeply editing fragile code. + +Rules (MUST unless marked SHOULD or MUST NOT): +- extract the new behavior into a new method +- keep the old code mostly untouched +- route to the new method from a small insertion point + +### Sprout Class +Use when a new responsibility does not fit the old class or the old class is too risky to reshape first. + +Rules (MUST unless marked SHOULD or MUST NOT): +- add a focused new collaborator +- delegate from the legacy class +- slowly move behavior over if later justified + +### Wrap Method +Use when you need pre/post behavior around a risky method or a better way to observe effects. + +### Wrap Class +Use when a class is too hard to test directly and behavior can be mediated through a new abstraction. + +### Extract and Override Call +Use only when language constraints leave few better options. +Prefer composition once a cleaner route appears. + +--- + +## Dependency-Breaking Technique Index + +- USE Adapt Parameter when a method needs only a narrow view of a hard-to-create parameter. +- USE Break Out Method Object when a large method has local state that blocks extraction and testing. +- USE Definition Completion when missing definitions block tests in languages that allow completion in test code. +- USE Encapsulate Global References when globals or singletons prevent substitution. +- USE Expose Static Method when useful logic does not need instance state but is trapped behind instance setup. +- USE Extract and Override Factory Method when construction of a hard dependency must vary under test. +- USE Extract Implementer or Extract Interface when concrete dependencies make compilation or substitution hard. +- USE Introduce Instance Delegator when static behavior needs an instance seam. +- USE Parameterize Constructor or Parameterize Method when hidden collaborators should become explicit inputs. +- USE Primitivize Parameter only when the real type is too costly to bring into a harness and primitive data is enough for the new logic. +- USE Pull Up Feature or Push Down Dependency to move behavior or dependencies to a more testable level in a hierarchy. +- USE Replace Global Reference with Getter when direct global access needs a seam. +- USE Subclass and Override Method only when safer composition seams are not available. +- USE Supersede Instance Variable when a test needs to replace a hard dependency held in a field. +- USE Template Redefinition, Text Redefinition, link seams, or preprocessing seams only when language or build constraints make ordinary object seams impractical. + +--- + +## Legacy Refactoring Heuristics + +1. Work near the change point, not across the whole system. +2. Prefer one small dependency break over a broad redesign. +3. If a test requires too much setup, the design is telling you something useful. +4. If code is impossible to observe, expose outcomes through smaller units. +5. If code is impossible to invoke without full runtime setup, isolate the policy from the runtime. +6. If code depends on many details, separate policy from mechanism. +7. If the old code cannot be safely changed, insert new code beside it and redirect gradually. + +--- + +## Handling Risky Areas + +### Large Methods +- carve out pure computation first +- isolate side effects second +- add tests around extracted parts +- avoid editing many branches at once + +### Static and Global Dependencies +- create a wrapper or façade +- move callers to the wrapper +- inject the wrapper where possible +- reduce direct calls incrementally + +### Database-Heavy Code +- separate query and mapping concerns from policy +- test policy without a real database where possible +- keep integration tests for actual persistence behavior + +### UI or Framework Code +- move decision logic out of handlers and callbacks +- test the moved logic independently +- keep adapters thin + +### Constructors Doing Too Much +- stop doing I/O, network, or configuration lookup in constructors +- move setup into factories, builders, or composition roots +- keep constructed objects easy to instantiate under test + +--- + +## Review Rules + +When reviewing legacy-oriented changes, actively look for: +- no tests around modified logic +- structural and behavioral changes mixed together +- broad edits in poorly understood modules +- hidden global dependencies left untouched +- hard-coded collaborators +- direct static calls +- constructors with side effects +- business logic trapped in framework entry points +- places where a sprout method or sprout class lowers risk + +--- + +## Forbidden Patterns + +### Rewrite as the First Move +- replacing a subsystem before understanding current behavior +- rebuilding instead of gaining test leverage +- assuming old behavior is irrelevant because the code looks bad + +### No-Safety Change +- changing legacy code with no tests or observation strategy +- large edits with no characterization +- relying on manual reasoning alone for risky behavior + +### Hidden Dependency Expansion +- adding more globals, statics, ambient context, or framework reach-through in already hard-to-test code +- embedding new hard dependencies in the same style as the legacy code + +### Cosmetic Refactoring Only +- renaming and formatting while leaving the real dependency knots intact +- “cleanup” that does not make the next change safer + +--- + +## Code Generation Rules + +When asked to modify legacy code, default to producing: +- characterization tests where needed +- small seams +- wrappers around hard dependencies +- explicit collaborators +- extracted pure logic +- minimal structural edits that unlock safe change + +Preferred first moves: +- extract method +- wrap static call +- inject collaborator +- split construction from behavior +- move logic out of framework entry points +- introduce a focused new class for new behavior +- add a narrow characterization test + +Preferred avoidance: +- huge dependency-breaking rewrites +- replacing old modules wholesale +- introducing large new architectures before basic seams exist +- mocking untestable structure instead of improving it + +--- + +## Testing Rules + +1. Prefer fast tests around the behavior you are changing. +2. Use characterization tests to lock current behavior before deeper edits. +3. Prefer tests at the highest level that still isolate the change safely. +4. Keep integration tests for real boundaries, but do not depend on them alone. +5. Once a seam exists, test through the seam. + +--- + +## Review Checklist + +Before finalizing any change, verify: +- Did we treat untested code as risky legacy code? +- Did we capture current behavior where it was unclear? +- Did we create or exploit a seam? +- Did we reduce at least one hard dependency? +- Is the changed area easier to test than before? +- Did we avoid a rewrite as the first move? +- Did we keep edits local to the requested change? +- Did we separate structural changes from behavior changes where practical? +- Did we leave the code more changeable than we found it? + +If any answer is no, revise before shipping. + +--- + +## Final Instruction + +When uncertain, choose the smallest change that: +1. increases understanding +2. increases testability +3. breaks one hard dependency +4. preserves current behavior +5. makes the next change cheaper + +Reject big rewrites and heroic cleanup when a seam and a test would do. diff --git a/programming-principles/references/working-effectively-with-legacy-code.mini.md b/programming-principles/references/working-effectively-with-legacy-code.mini.md new file mode 100644 index 0000000..1363010 --- /dev/null +++ b/programming-principles/references/working-effectively-with-legacy-code.mini.md @@ -0,0 +1,40 @@ +# OBEY Working Effectively with Legacy Code by Michael Feathers + +## When to use + +Use when changing code that is expensive to change safely because behavior is unclear, tests are weak or missing, dependencies are hidden, or runtime/framework setup blocks local feedback. + +## Primary bias to correct + +Gain control before improving design. Understand current behavior, protect what must stay, create the smallest useful seam, break the dependency that blocks feedback, make the requested change, then leave the area more testable. + +## Decision rules + +- Treat any area without trustworthy tests as legacy code. Do not start with rewrite. +- Before editing, state the requested behavior change and the current behavior that must remain. +- Follow the legacy loop: identify change point, check protection, add characterization, find a seam, break the blocking dependency, change behavior, refactor locally. +- Prefer fast, focused tests around the slice being changed. +- Choose test points by tracing effects outward from the change point. +- Use the smallest seam that allows substitution, observation, or interception. +- Break dependencies deliberately: expose hidden inputs, hard outputs, hard construction, globals, statics, ambient context. +- Keep behavior changes, structural refactorings, and cleanup separate. +- Use sprout method, sprout class, wrap method, wrap class when direct edits are risky. +- For hard-to-test methods, split construction from use, extract side effects, carve pure computation first. +- Leave the touched area easier to understand, test, or change. + +## Trigger rules + +- When behavior is uncertain, add characterization or another explicit observation path before changing semantics. +- When tests require too much setup, break the first real barrier: constructor work, hidden allocation, global state, static construction. +- When time, randomness, environment, files, network, or process exits block repeatable tests, wrap that boundary. +- When a large method defeats local reasoning, extract pure computation first. +- When rewrite feels tempting, choose the smallest sprout, wrap, seam, or refactoring step. + +## Final checklist + +- Untested area treated as legacy risk? +- Behavior delta and behavior-to-preserve stated? +- Smallest useful seam chosen? +- Blocking dependency reduced? +- Temporary seam has a cleanup path? +- Touched area more changeable than before?