diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-27 22:54:50 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-27 22:54:50 +0200 |
| commit | b761d428465ad94f700e7134c5cc0b4d9582853b (patch) | |
| tree | 79e811ac1f7ebc2a31002e85f8a09cc6f517a15a | |
| parent | d501cff2c6c521d4d8ff0d535164ed70ac790c84 (diff) | |
add beyond
11 files changed, 1039 insertions, 0 deletions
diff --git a/prompts/skills/beyond-solid-principles/SKILL.md b/prompts/skills/beyond-solid-principles/SKILL.md new file mode 100644 index 0000000..3300966 --- /dev/null +++ b/prompts/skills/beyond-solid-principles/SKILL.md @@ -0,0 +1,150 @@ +--- +name: beyond-solid-principles +version: 1.0.0 +description: > + This skill should be used when the user asks to "check architecture principles", + "audit system design", "review code for coupling", "find architecture smells", or + "improve system-level design". Also triggers when the user mentions a principle by + name (e.g., "check separation of concerns", "is this violating DRY?", "Law of + Demeter", "KISS", "YAGNI", "resilience", "evolvability", "loose coupling"). Supports + checking all ten principles at once or focusing on a single principle. +--- + +# Beyond SOLID — System-Level Architecture Principles + +Analyze source code and system architecture for violations of ten foundational design +principles that govern how modules, services, layers, and components are structured +and interact. Produce actionable findings with severity ratings, code/architecture +locations, and concrete remediation suggestions. + +These principles operate at the architecture scale — modules, services, bounded +contexts, layers, APIs — complementing the class-level SOLID principles. + +## Subcommands + +Request a full audit or focus on a single principle: + +| Command Pattern | Principle | Reference | +|----------------|-----------|-----------| +| `sw-soc` | Separation of Concerns | `references/soc.md` | +| `sw-srp-sys` | Single Responsibility (system-level) | `references/srp-sys.md` | +| `sw-dry` | Don't Repeat Yourself (DRY) | `references/dry.md` | +| `sw-demeter` | Law of Demeter / Principle of Least Knowledge | `references/demeter.md` | +| `sw-coupling` | Loose Coupling, High Cohesion | `references/coupling.md` | +| `sw-evolvability` | Build for Change (Evolvability) | `references/evolvability.md` | +| `sw-resilience` | Design for Failure / Resilience | `references/resilience.md` | +| `sw-kiss` | KISS — Keep It Simple | `references/kiss.md` | +| `sw-pola` | Principle of Least Surprise (POLA) | `references/pola.md` | +| `sw-yagni` | YAGNI at Architecture Level | `references/yagni.md` | +| `beyond-solid-principles` | All ten principles | All references | + +When no subcommand is specified, default to checking all ten principles. +When a principle is mentioned by name (even without the command prefix), match it to +the appropriate subcommand. + +## Workflow + +### 1. Identify Target Code + +Determine what code or architecture to analyze: +- When files or a directory are provided, use those. +- When a service, module, or component is referenced by name, locate it. +- When ambiguous, ask which files, directories, or services to scan. + +These principles apply to any language and any architecture style — monoliths, +modular monoliths, microservices, serverless, event-driven, layered, hexagonal, etc. +Adapt the principle checks to the idioms and scale of the target system. + +For smaller codebases, focus on module/package boundaries, dependency direction, +and internal layering. For distributed systems, also consider service boundaries, +API contracts, data ownership, messaging patterns, and operational resilience. + +### 2. Load Principle References + +Before analyzing, read the reference file(s) for the requested principle(s): + +- [`references/soc.md`](references/soc.md) for Separation of Concerns +- [`references/srp-sys.md`](references/srp-sys.md) for Single Responsibility (system-level) +- [`references/dry.md`](references/dry.md) for DRY +- [`references/demeter.md`](references/demeter.md) for Law of Demeter +- [`references/coupling.md`](references/coupling.md) for Loose Coupling, High Cohesion +- [`references/evolvability.md`](references/evolvability.md) for Build for Change +- [`references/resilience.md`](references/resilience.md) for Design for Failure +- [`references/kiss.md`](references/kiss.md) for KISS +- [`references/pola.md`](references/pola.md) for Principle of Least Surprise +- [`references/yagni.md`](references/yagni.md) for YAGNI + +For a full audit (`beyond-solid-principles`), read all ten. + +### 3. Analyze + +For each target file, module, or service boundary, apply the violation patterns from +the loaded references. Think carefully about each pattern — not every heuristic match +is a true violation. Consider context, system scale, team size, and maturity. + +Key analysis dimensions: +- **Static structure**: dependency direction, import graphs, layer boundaries. +- **Change patterns**: which files/modules change together (if VCS history is available). +- **Runtime topology**: service call chains, data flow, failure propagation paths. +- **API contracts**: consistency, encapsulation, versioning, naming conventions. +- **Operational posture**: timeouts, retries, circuit breakers, health checks. + +### 4. Report Findings + +Present findings using this structure: + +#### Per Violation + +``` +**[PRINCIPLE] Violation — Severity: HIGH | MEDIUM | LOW** +Location: `filename` or `service/module`, lines ~XX-YY (if applicable) +Issue: Clear description of what violates the principle and why it matters. +Suggestion: Concrete remediation approach with brief code or architecture sketch if helpful. +``` + +Severity guidelines: +- **HIGH**: Active maintenance pain, production risk, blocks independent evolution, + or causes cascading failures. +- **MEDIUM**: Architecture smell that will cause problems as the system grows or + as more teams contribute. +- **LOW**: Minor design impurity, worth noting but fine to defer. + +#### Summary + +After all findings, provide: +- A count table: `| Principle | HIGH | MEDIUM | LOW |` +- Top 3 priorities: which violations to fix first and why. +- Overall assessment: one paragraph on the system's structural health and + evolvability posture. + +### 5. Refactor Mode (Optional) + +When a fix or refactoring is requested (e.g., "fix this", "refactor it", +"show me the clean version"), produce refactored code or an architecture proposal +that resolves the identified violations. Explain each change briefly. + +## Pragmatism Guidelines + +These are guidelines, not laws. Apply judgment: + +- Small projects and prototypes get a lighter touch. Don't flag a weekend project + for lacking circuit breakers or API versioning. +- Some "violations" are conscious trade-offs. When a rationale is documented (e.g., + in an ADR), acknowledge it rather than insisting on purity. +- Scale matters. A single-service CRUD app does not need the same architectural rigor + as a platform serving millions of requests. Calibrate severity to context. +- Principles have productive tensions. DRY conflicts with loose coupling across service + boundaries. KISS conflicts with resilience patterns. YAGNI conflicts with evolvability. + Flag the tension and offer judgment, not dogma. +- Prefer actionable findings over exhaustive catalogs. Five important findings + beat twenty trivial ones. + +## Example Interaction + +**User**: `sw-coupling` (with a codebase directory) + +**Claude**: +1. Reads `references/coupling.md` +2. Analyzes the directory for coupling/cohesion violations +3. Reports findings with locations, severity, and suggestions +4. Provides a summary with priorities diff --git a/prompts/skills/beyond-solid-principles/references/coupling.md b/prompts/skills/beyond-solid-principles/references/coupling.md new file mode 100644 index 0000000..cb03f78 --- /dev/null +++ b/prompts/skills/beyond-solid-principles/references/coupling.md @@ -0,0 +1,84 @@ +# Loose Coupling, High Cohesion + +> "The goal is to create modules that can be understood, developed, tested, and maintained independently." — Larry Constantine & Ed Yourdon, Structured Design (1979) + +## Core Idea + +Larry Constantine and Ed Yourdon defined these complementary metrics in Structured Design (1979). Coupling measures how much one component depends on another's details; cohesion measures how related the elements within a single component are. The ideal is low coupling between components and high cohesion within them. Sam Newman calls this the core motivator behind microservices and event-driven architecture. Azure's microservices guidance: "microservices are loosely coupled if you can change one service without requiring other services to be updated at the same time, and cohesive if they have a single, well-defined purpose." At system scale, Martin's package coupling metrics provide quantitative tools: the Stable Dependencies Principle, the Stable Abstractions Principle, and the Distance from Main Sequence metric. + +## Violation Patterns + +### 1. Distributed Monolith + +**Heuristic:** Services that must deploy together despite being technically separate. Gremlin identifies three forms: behavioral coupling (dependency must be available), temporal coupling (requiring low-latency synchronous communication), and implementation coupling (changes to one service force changes in others). + +**Look for:** +- Coordinated deployments are normal because changes require multiple services to update together +- Inability to deploy services independently +- Service A becomes unavailable when service B goes down + +**Refactoring/Remedy:** Redraw boundaries along business capabilities using DDD. Each service should own its data and be independently deployable. Use asynchronous messaging to eliminate temporal coupling. + +### 2. Shared Mutable State / Shared Database + +**Heuristic:** Multiple services read and write the same database schema, creating hidden coupling at the data layer. + +**Look for:** +- Multiple services with direct access to the same tables +- Schema changes requiring coordination across teams +- "Using database entities as events" (Azure antipattern) +- Modules sharing global caches or static singletons + +**Refactoring/Remedy:** Each service owns its data store with no cross-service database access. When Service B needs Service A's data, A publishes events and B maintains a local projection. Use Anti-Corruption Layers to protect boundaries. + +### 3. Synchronous Call Chain Entanglement + +**Heuristic:** Long synchronous dependency chains (A→B→C→D) where all services must be responsive simultaneously. This creates temporal coupling that cascades both latency and failure. + +**Look for:** +- Distributed traces showing deep synchronous call chains +- P95/P99 latencies compounding across hops +- One slow service causing all upstream services to degrade +- Thread pool exhaustion from blocked calls + +**Refactoring/Remedy:** Replace synchronous chains with asynchronous messaging (Kafka, RabbitMQ, SQS) where possible. Use event-driven patterns where services react to domain events rather than pulling data through call chains. + +### 4. Low Cohesion — Technical-Layer Organization + +**Heuristic:** Organizing by technical layer (/entities/, /factories/, /repositories/) rather than business domain produces low cohesion: a single feature change touches files across many folders. + +**Look for:** +- Shotgun surgery — a feature change requires editing files in 5+ unrelated directories +- Architecture diagrams that look like spaghetti with no clear dependency direction +- "Common" or "Shared" projects with unrelated functionality mixed together (logging, date helpers, domain rules, UI utilities) + +**Refactoring/Remedy:** Organize by business capability and bounded context. Group operations that naturally change together into one cohesive module. Feature slicing: keep all code for one feature vertically aligned. + +### 5. Wide Interfaces Leaking Implementation Details + +**Heuristic:** Service interfaces become "wide" because other services need internal details, not because domain operations require them. + +**Look for:** +- APIs that expose internal data structures rather than domain operations +- Many endpoints/fields that exist for other services rather than domain needs +- Interservice chattiness grows as teams spend effort managing call graphs +- APIs that model internal implementation rather than the domain + +**Refactoring/Remedy:** Design APIs to model the domain, not internal implementation. Use intention-revealing operations (PlaceOrder, not SetOrderStatusAndUpdateInventoryAndNotifyShipping). Consumer-driven contract testing (Pact) verifies provider changes don't break consumer expectations. + +## System-Scale Notes + +- Martin's package coupling metrics: Afferent coupling (Ca) = inbound dependencies, Efferent coupling (Ce) = outbound dependencies. Instability (I = Ce/(Ca+Ce)). Distance from Main Sequence catches modules in the "Zone of Pain" (concrete but heavily depended upon) or "Zone of Uselessness" (abstract but unused). +- Deployment coupling is the most practical smell — can you deploy services independently? +- Git co-change analysis reveals logical coupling invisible in static dependency graphs. +- Chaos engineering (injecting failures) tests whether services are truly independent. +- Track Ca, Ce, and instability per module in CI pipelines as automated fitness functions. +- CodeOpinion emphasizes functional cohesion (grouping by related business operations) over informational cohesion (grouping by shared data). +- Destructive decoupling is the opposite extreme — decoupling so aggressively that interfaces everywhere have no coherent purpose. + +## False Positives to Avoid + +- Libraries or modules with high afferent coupling (many things depend on them) that are also highly stable and abstract are not violating coupling principles — they are in the ideal "Zone of the Main Sequence." +- A service that makes several calls to another service as part of a single coherent operation is not necessarily too tightly coupled — evaluate whether the calls represent a genuinely distributed workflow. +- Event-driven architecture still has coupling — it's just looser and temporal. Publishing an event doesn't eliminate the dependency; it changes its nature. +- A well-designed monolith with clear module boundaries can have lower coupling than poorly-designed microservices. diff --git a/prompts/skills/beyond-solid-principles/references/demeter.md b/prompts/skills/beyond-solid-principles/references/demeter.md new file mode 100644 index 0000000..84b7a35 --- /dev/null +++ b/prompts/skills/beyond-solid-principles/references/demeter.md @@ -0,0 +1,82 @@ +# Principle of Least Knowledge / Law of Demeter + +> "Only talk to your immediate friends." — Ian Holland, Northeastern University, 1987 + +## Core Idea + +The Law of Demeter states that a method may only invoke methods on itself, its parameters, objects it creates, and its instance variables — never on objects returned by other method calls. At architecture scale, LoD constrains communication patterns: a component should only interact with its immediate collaborators and never reach through them to access distant internal details. Robert C. Martin devotes several pages of Clean Code Chapter 6 to "Train Wrecks," "Hybrids," and "Hiding Structure." Martin Fowler pragmatically calls it the "Occasionally Useful Suggestion of Demeter." The key insight: LoD is a coupling-control rule that limits what components need to know about each other's internal structure. + +## Violation Patterns + +### 1. Train Wreck / Deep Object Navigation + +**Heuristic:** Chained method/property calls that traverse multiple objects, exposing deep structural knowledge. + +**Look for:** +- `ctxt.getOptions().getScratchDir().getAbsolutePath()` +- `order.getCustomer().getAddress().getCity()` +- Chains longer than two dots on objects (not data structures) +- Disguising chains with intermediate variables doesn't fix it — the caller still knows the internal structure + +**Refactoring/Remedy:** Apply "Tell, Don't Ask" — instead of querying an object's internals, tell the object what you need done. `account.withdraw(amount)` replaces interrogating the balance externally. Create intention-revealing methods that hide the navigation. + +### 2. Service Reach-Through / Transitive Dependencies + +**Heuristic:** Service A calls Service B, which calls Service C, giving A implicit knowledge of the B→C relationship. If C changes its API, both B and A may break. + +**Look for:** +- Distributed traces showing call chains longer than one hop from a given service's perspective +- A client service calling a downstream service then reaching through its DTOs to call another service +- Multi-hop synchronous flows that tightly couple components + +**Refactoring/Remedy:** Use facade patterns and API gateways that aggregate calls so clients don't chain through services. Enforce that each layer calls only the layer directly below it. + +### 3. Schema Reach-Through in Service Architectures + +**Heuristic:** A service reads another service's internal tables, internal event payloads, or database entities directly — coupling itself to the other service's internal representation. + +**Look for:** +- Services reading each other's database tables +- Integration tests that manipulate internal tables or queues of a service instead of using its public interface +- Azure explicitly calls "using database entities as events" an antipattern because it exposes internal details + +**Refactoring/Remedy:** Enforce "database per service." Services communicate only through public APIs or published events. Event payloads should represent domain concepts, not internal database entities. + +### 4. Client-Side Business Logic / Orchestrator Overreach + +**Heuristic:** UI clients, API gateways, or orchestrator services replicate domain rules that should live behind service boundaries, effectively knowing too much about the internal logic of services. + +**Look for:** +- Gateway/orchestrator code that queries multiple contexts and recomputes internal state +- Calling code that depends on deep internal fields of complex DTOs that should be encapsulated +- Clients assembling a domain operation by pulling many internal fields because no coherent "tell" operation exists + +**Refactoring/Remedy:** Expose narrow, intention-revealing APIs (PlaceOrder, ReserveInventory) instead of leaking raw data for callers to manipulate. Push domain logic into the service that owns the bounded context. + +### 5. Layer Skipping + +**Heuristic:** Presentation layer directly calls data access, bypassing business logic. Lower layers reach up to higher layers. + +**Look for:** +- UI code directly executing SQL or calling repository methods +- Infrastructure modules importing from service/domain modules +- Circular imports or lazy imports used to work around circular dependencies + +**Refactoring/Remedy:** Enforce strict layer dependency direction. Higher layers call lower layers only through defined interfaces. Use dependency inversion to break circular dependencies. + +## System-Scale Notes + +- Robert C. Martin draws an important distinction: data structures (DTOs, records) expose data with no behavior — navigating through them is acceptable. Objects expose behavior and hide data — chaining through objects violates LoD. +- Builder patterns and fluent APIs that return the same type at each step are explicitly not violations. +- The Response For a Class (RFC) metric — methods potentially invoked in response to a method call — correlates with bug probability. Following LoD reduces RFC. +- Coupling Between Objects (CBO) measures how many external types a class references. +- At architecture level, visualize service-to-service call chains: any path longer than one hop suggests a potential violation. +- The Demeter paper notes that LoD tends to force narrow method-level dependencies but can lead to wide class-level interfaces because you introduce auxiliary methods rather than digging into structures. This trade-off requires system-level judgment. +- JetBrains IntelliJ includes a built-in "Law of Demeter" inspection. + +## False Positives to Avoid + +- Navigating through data structures (DTOs, records, configuration objects) is acceptable — LoD applies to objects with behavior, not plain data. +- Fluent APIs and builder patterns that return `this` or the same builder type are not violations — the chain stays on one object. +- A facade that deliberately aggregates multiple calls behind a single interface is not a violation — it's the recommended fix. +- Standard library traversals (e.g., `path.parent.name` in pathlib, or stream operations) are generally not violations. diff --git a/prompts/skills/beyond-solid-principles/references/dry.md b/prompts/skills/beyond-solid-principles/references/dry.md new file mode 100644 index 0000000..0fac87e --- /dev/null +++ b/prompts/skills/beyond-solid-principles/references/dry.md @@ -0,0 +1,79 @@ +# Don't Repeat Yourself (DRY) + +> "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." — Andy Hunt & Dave Thomas, The Pragmatic Programmer (1999) + +## Core Idea + +DRY is about knowledge, not code. Two identical-looking code blocks representing different domain concepts are incidental duplication — merging them creates harmful coupling. Two different-looking blocks encoding the same business rule are the real violation. At architecture scale, the most dangerous violation is either (a) the same business rule implemented independently in multiple services leading to divergence, or (b) shared domain-object libraries that create distributed monoliths. Sam Newman: "The evils of too much coupling between services are far worse than the problems caused by code duplication." DRY has two distinct failure modes: under-DRY (true duplication of the same knowledge across components) and overzealous-DRY (premature centralization that creates coupling). + +## Violation Patterns + +### 1. Divergent Business Rules Across Services + +**Heuristic:** The same business rule (pricing, validation, eligibility, authorization) is independently encoded in multiple services without a shared authoritative source. + +**Look for:** +- Password validation enforced as 8 chars in web, 10 in mobile, 6 on backend +- Discount logic repeated in web app, mobile API, and reporting ETL +- A single business rule change requiring modifications in multiple services (shotgun surgery) + +**Refactoring/Remedy:** Identify the authoritative owner of each business rule. Centralize within the owning bounded context. Other consumers call the owner's API or subscribe to its events rather than re-implementing the rule. + +### 2. Schema and Contract Divergence + +**Heuristic:** Multiple services expose subtly different representations of the same concept because each defined it ad-hoc. + +**Look for:** +- "Customer" looks different across services and databases without a clear reason +- JSON fields with same meaning but different names (userId vs customer_id) +- Multiple, slightly different API definitions for the same entity creating integration friction + +**Refactoring/Remedy:** Use contract-first design (OpenAPI, Protobuf, AsyncAPI) to generate clients and servers from a single source of truth. Establish canonical models for cross-cutting concepts and version them carefully. + +### 3. Shared Library Coupling (Overzealous DRY) + +**Heuristic:** A shared domain-object library forces all consuming services to update simultaneously whenever a field changes. + +**Look for:** +- Many services cannot upgrade independently because a shared library or shared schema change forces widespread rebuild/redeploy +- Shared entity libraries that grow to include domain logic specific to individual services +- Azure warns "sharing common libraries" is a coupling antipattern + +**Refactoring/Remedy:** Share stable contracts (published interfaces), not implementations. Prefer duplication of domain-specific models across bounded contexts over shared libraries. Sam Newman: lean toward duplication when unsure. + +### 4. Shared Database as Integration Point + +**Heuristic:** Multiple services reading and writing the same database schema, using the database as an implicit integration contract. + +**Look for:** +- Multiple services with direct access to the same tables +- Schema changes requiring coordination across teams +- The database schema is treated as shared mutable state + +**Refactoring/Remedy:** Each service owns its data store. Integrate through APIs or events, not shared databases. Use materialized views or local copies for read-heavy cross-service data needs. + +### 5. Infrastructure Pattern Duplication + +**Heuristic:** Each service independently implements its own logging format, retry logic, idempotency strategy, health checks, and configuration management. + +**Look for:** +- Copy-pasted HTTP client setup, message deserialization, logging and correlation logic across services +- Different copies of connection strings and feature flags hardcoded in multiple services +- Bug fixes that must be applied in many places + +**Refactoring/Remedy:** Extract genuinely cross-cutting infrastructure into shared libraries or sidecars. Use centralized config services (Consul, Vault, Azure App Configuration). Standardize via service mesh for network-level concerns. + +## System-Scale Notes + +- **The Rule of Three:** Don't abstract until you see three instances of the same knowledge — by the third you understand the commonality well enough. Sandi Metz: "It is better to have some duplication than a bad abstraction." +- **Strategic approach:** Apply DRY aggressively within bounded contexts but accept duplication across them. +- Share stable, cross-cutting types (like a PostalCode value object) but duplicate domain-specific models (like Address, which will diverge between Billing and Delivery). +- **Fowler's Harvested Platform pattern:** don't build shared infrastructure upfront. Build well-factored applications, notice duplication, and extract shared code only after patterns stabilize. +- Static analysis tools (SonarQube, CPD) detect syntactic duplication but cannot identify knowledge duplication. Treat their findings as investigation triggers, not mandates. The key question: "Is this knowledge duplication or syntactic similarity?" + +## False Positives to Avoid + +- Having a Customer class in both Checkout and OrderManagement microservices is proper bounded-context separation, not a DRY violation — each representation will diverge to serve its context. +- Two code blocks that look identical but represent different domain concepts (e.g., tax calculation for two jurisdictions that happen to currently have the same rate) are incidental duplication — merging them would create harmful coupling. +- Boilerplate required by a framework (e.g., controller setup, DI wiring) is not a DRY violation — it's structural scaffolding. +- Configuration that differs per environment (dev/staging/prod) is not duplication — it's intentional variation. diff --git a/prompts/skills/beyond-solid-principles/references/evolvability.md b/prompts/skills/beyond-solid-principles/references/evolvability.md new file mode 100644 index 0000000..1fb7fb1 --- /dev/null +++ b/prompts/skills/beyond-solid-principles/references/evolvability.md @@ -0,0 +1,87 @@ +# Build for Change (Evolvability) + +> "Prefer evolvable over predictable — optimize for responding to unknown challenges rather than perfectly solving known ones." — Ford, Parsons & Kua, Building Evolutionary Architectures (2017) + +## Core Idea + +Neal Ford, Rebecca Parsons, and Patrick Kua coined "evolutionary architecture": architecture that supports guided, incremental change across multiple dimensions. Requirements, data, and constraints will shift — the question is whether the architecture absorbs that change gracefully or resists it catastrophically. Foundational modularity work by David Parnas argues that the effectiveness of modularization depends on the criteria used to divide the system, tying it directly to flexibility and comprehensibility. Evolvability is a first-class architectural quality attribute, not an afterthought. Martin Fowler wrote the foreword to the evolutionary architecture book, calling continuous delivery "a crucial enabling factor." + +## Violation Patterns + +### 1. Big-Bang Rewrite Dependency + +**Heuristic:** The system has accumulated so much coupling and rigidity that any significant change requires a massive coordinated effort or complete rewrite. + +**Look for:** +- Feature lead time growing rapidly as the system evolves +- Small changes have unexpectedly large blast radius +- "We can't change X without rewriting Y" is common +- eBay's multi-year migrations from Perl to C++ to Java illustrate the extreme cost + +**Refactoring/Remedy:** Apply the Strangler Fig pattern (Fowler, 2004): identify thin slices, introduce a routing facade, coexist with legacy, eliminate old functionality incrementally. Never attempt big-bang rewrites. + +### 2. Breaking API Changes Without Versioning + +**Heuristic:** API changes deployed without versioning cause cascading client failures. + +**Look for:** +- No version histories on APIs +- Field removals or renames without deprecation paths +- Clients that must update synchronously +- No backward-compatibility tests +- Azure's versioning policy: "an API version completely defines behaviour — behaviour change requires a version change" + +**Refactoring/Remedy:** Adopt API versioning (URI path, header, or query parameter). Enforce backward compatibility as the default — additive changes should never require a version bump. Use expand-and-contract for schema migrations. + +### 3. Tight Infrastructure/Persistence Coupling + +**Heuristic:** Business logic entangled with specific database technology, ORM frameworks, or cloud SDKs, so migrating storage requires rewriting domain logic. + +**Look for:** +- Domain types depending directly on persistence or transport types (ORM entities, API DTOs) +- Domain objects requiring framework initialization to instantiate +- Direct database SDK imports in business logic +- Technology lock-in visible in the core domain layer + +**Refactoring/Remedy:** Clean/onion architecture: domain model is persistence-ignorant. Create adapter/mapper layers. The domain defines what it needs; infrastructure adapts to it. Prefer libraries over frameworks (frameworks are harder to replace). + +### 4. Absence of Architectural Fitness Functions + +**Heuristic:** No automated mechanisms to detect when architectural qualities (coupling, performance, security) silently degrade over time. + +**Look for:** +- Architecture rules exist only in documentation/wikis +- No automated dependency checks in CI +- Coupling metrics not tracked +- Silent boundary erosion over months +- Ford et al. define fitness functions as "objective integrity assessments of architectural characteristics" + +**Refactoring/Remedy:** Implement fitness functions: automated tests running in CI/CD that enforce architectural constraints. Track DORA metrics (deployment frequency, lead time, change failure rate, MTTR). Use ArchUnit/NetArchTest to enforce dependency rules. + +### 5. No Incremental Delivery Capability + +**Heuristic:** All changes deployed "big bang" without feature flags, gradual rollout, or rollback capability. Deployment equals release. + +**Look for:** +- No feature flag infrastructure +- No canary or blue-green deployment capability +- Changes cannot be rolled back without redeployment +- Feature branches living longer than one week (indicating inability to do trunk-based development) + +**Refactoring/Remedy:** Feature flags decouple deployment from release, enabling instant rollback. Expand-and-contract database migrations prevent destructive schema changes. Invest in CI/CD, automated testing, and trunk-based development. + +## System-Scale Notes + +- Fitness functions can be categorized: triggered vs. continual, atomic vs. holistic, static vs. dynamic, automated vs. manual. +- The four DORA metrics directly measure an architecture's capacity for change. +- Build anticorruption layers to shield your domain from external system changes. +- Fowler's MonolithFirst: start with a modular monolith, split when proven domain boundaries, operational maturity, and team size justify it. +- ADRs (Architecture Decision Records) force discipline by requiring teams to articulate why a complex pattern is needed now. +- Key distinction: YAGNI applies to speculative features, NOT to practices that make software easier to modify. Refactoring, self-testing code, CI/CD, and clean architecture are enabling practices, never YAGNI violations. + +## False Positives to Avoid + +- Choosing a specific technology (e.g., PostgreSQL) is not an evolvability violation as long as the domain code doesn't depend directly on it. Technology choices are fine; tight coupling to them is the problem. +- A system without API versioning in internal-only APIs where a single team owns all consumers may be fine — versioning is most critical at organizational or external boundaries. +- Not every piece of code needs to be behind a feature flag. Feature flags add complexity and are most valuable for high-risk or high-impact changes. +- A well-designed monolith can be more evolvable than poorly-designed microservices. diff --git a/prompts/skills/beyond-solid-principles/references/kiss.md b/prompts/skills/beyond-solid-principles/references/kiss.md new file mode 100644 index 0000000..9abcfc1 --- /dev/null +++ b/prompts/skills/beyond-solid-principles/references/kiss.md @@ -0,0 +1,88 @@ +# KISS — Keep It Simple, Stupid + +> "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian Kernighan + +## Core Idea + +Kelly Johnson, lead engineer at Lockheed Skunk Works, coined the principle: design systems so they can be maintained by an average person under pressure. In software, Fred Brooks's 1986 distinction between essential complexity (inherent to the problem) and accidental complexity (introduced by our tools and choices) provides the intellectual framework. + +KISS is not "make everything trivial" — it's a bias toward the simplest architecture that satisfies requirements while remaining maintainable and testable. Joel Spolsky coined the "Architecture Astronaut" in 2001: smart thinkers who "go too far up, abstraction-wise" and create "absurd, all-encompassing, high-level pictures of the universe that don't actually mean anything at all." An ICSE 2021 study found that 82% of software professionals believed using trending technologies makes them more attractive to employers — resume-driven development systematically biases teams toward over-engineered solutions. + +## Violation Patterns + +### 1. Premature Microservices / Over-Distribution + +**Heuristic:** Splitting a small product into dozens of services without demonstrated need for independent scaling or deployment. Heavy Kubernetes, service mesh, and complex orchestration for a simple CRUD app. + +**Look for:** +- More services than engineers (Segment's 140 services for 3 engineers) +- Teams spending more time on infrastructure than features +- High ratio of infrastructure PRs to feature PRs +- Azure warns: "microservices require a fundamental shift in mindset" and "overly granular services increase complexity" + +**Refactoring/Remedy:** Fowler's MonolithFirst: "Almost all the successful microservice stories have started with a monolith that got too big and was broken up." Start with a modular monolith. Split only when proven domain boundaries, operational maturity, and team size justify it. + +### 2. Architecture Astronautics / Over-Abstraction + +**Heuristic:** Excessive architectural layers, patterns stacked on patterns, generalized frameworks that few people understand. + +**Look for:** +- 10+ architectural layers +- CQRS + Event Sourcing + DDD + microservices for a CRUD app +- A real project where implementing a simple "copy user" feature took two full days instead of hours because of all the layers +- Generic in-house "frameworks" that no one can explain + +**Refactoring/Remedy:** Kent Beck's four rules of Simple Design: passes all tests, reveals intention, has no duplication, has fewest elements. Remove layers that don't deliver proportional value. Treat complexity as a finite budget. + +### 3. Resume-Driven Development + +**Heuristic:** Technology choices driven by what looks good on resumes rather than what solves the problem. Kafka when REST suffices. Kubernetes for single-team applications. + +**Look for:** +- Technologies whose capabilities far exceed actual usage +- Significant learning curves imposed on the team for marginal benefit +- "We chose this because it's popular" rather than "we chose this because we need X" +- Polyglot persistence without a clear reason + +**Refactoring/Remedy:** Jeff Bezos's "one-way door vs. two-way door" framework: reserve heavy analysis for irreversible decisions, use simple reversible solutions elsewhere. Right-size technology to the problem. Pick boring technology where possible. + +### 4. Speculative Generality + +**Heuristic:** Interfaces with only one implementation, abstract classes never extended, design patterns used "in case we need it later," plugin architectures with one plugin. + +**Look for:** +- Disproportionate complexity for the actual requirements +- Unused extension points +- Under-utilized infrastructure capabilities +- Significant portions of system complexity corresponding to features not used or not on the near-term roadmap + +**Refactoring/Remedy:** Remove unused abstractions. Apply YAGNI rigorously. If there's only one implementation, you don't need the interface yet. Add abstraction when the second use case arrives and you understand the axis of variation. + +### 5. Excessive Middle Tiers and Indirection + +**Heuristic:** Intermediate layers that add latency and complexity without delivering meaningful value — the middle tier that performs only basic CRUD passthrough. + +**Look for:** +- Azure flags "the middle tier that performs only basic CRUD as adding latency/complexity without value" +- Proxy services that add no logic +- "Onion architecture" where every layer is a 1:1 passthrough to the next +- Charity Majors's test: "How long to ship a one-character fix?" + +**Refactoring/Remedy:** Remove passthrough layers. If a layer doesn't transform, validate, or make decisions, it's accidental complexity. Measure deployment time for trivial changes as a complexity gauge. + +## System-Scale Notes + +- The practical measure of KISS violations: disproportionate complexity relative to requirements +- A declining deployment frequency often signals that architecture has become too complex to change safely +- A high ratio of infrastructure code to business logic suggests accidental complexity is dominating +- If onboarding a new engineer takes weeks for what should be a simple domain, the system is over-engineered +- Fowler: "Don't even consider microservices unless you have a system that's too complex to manage as a monolith" +- Standardize cross-cutting concerns (logging, monitoring, deployment) to avoid a "complexity tax" multiplying across components +- Prefer simpler architecture styles when they meet requirements. Treat distribution as an operational trade-off, not a default + +## False Positives to Avoid + +- A system that has grown legitimately complex because the domain is complex is not violating KISS — KISS targets accidental complexity, not essential complexity +- Well-established patterns (e.g., MVC, repository pattern, dependency injection) that are idiomatic to the tech stack are not over-engineering — they are conventions that reduce cognitive load +- A large system with many modules is not inherently complex if each module is simple and boundaries are clear +- Investment in CI/CD, automated testing, and observability is not over-engineering — these are enabling infrastructure that makes simplicity sustainable diff --git a/prompts/skills/beyond-solid-principles/references/pola.md b/prompts/skills/beyond-solid-principles/references/pola.md new file mode 100644 index 0000000..03de48c --- /dev/null +++ b/prompts/skills/beyond-solid-principles/references/pola.md @@ -0,0 +1,95 @@ +# Principle of Least Surprise (POLA) + +> "A component of a system should behave in a way that most users will expect it to behave." — PL/I Bulletin, 1967 + +## Core Idea + +Also known as the Principle of Least Astonishment, POLA centers on predictability. The "user" can be an end-user, a fellow programmer, or a future maintainer. Joshua Bloch applied it extensively in *Effective Java* and his influential talk "How to Design a Good API & Why it Matters." + +At architecture scale, POLA violations are especially costly because they amplify across distributed boundaries and affect multiple teams. A team migrating a 180k-line Rails monolith suffered 30% data loss in production because `record.metadata = filtered_array` on an ActiveRecord CollectionProxy silently performed a database DELETE — four experienced developers missed it because no one expected an assignment operator to have destructive side effects. < |
