Use before implementing or refactoring software. Contains two skills: (1) Modular Software Design — for designing module boundaries, APIs, layers, abstractions, services, repositories, adapters, or architecture, helping reduce total system complexity by creating deep modules, hiding implementation knowledge, avoiding leakage and pass-through APIs, comparing alternative designs, documenting interfaces before coding, and critiquing existing architecture; and (2) Software Testing — for writing unit tests, integration tests, or end-to-end tests, creating mocks/stubs/fakes, designing a testing strategy, doing TDD, reviewing test quality, fixing flaky tests, or refactoring test suites, generating risk-focused test plans, picking appropriate test levels, choosing between mocks/fakes/real dependencies, and applying Arrange-Act-Assert patterns with concrete examples.
88
88%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Passed
No known issues
Use these rules while preparing or reviewing a software design.
Hide implementation knowledge unless callers legitimately control it.
Hide:
Expose:
A boundary leaks when callers must know facts that should belong inside the callee.
Check:
A pass-through method adds surface area without adding abstraction.
Suspicious:
class UserService:
def get_user(self, user_id):
return self.repository.get_user(user_id)Acceptable only when the method changes abstraction by adding policy, translation, validation, orchestration, stability, security, or a domain concept.
A pass-through variable is data threaded through layers that do not use or own it. Move that state into the module that owns the operation, or collapse the boundary that merely forwards it.
Split modules when:
Merge modules when:
Rule: split by hidden knowledge and axis of change; merge when separation exports coordination complexity.
Each layer must provide a distinct abstraction.
Typical responsibilities:
A layer is suspicious when it has the same method names, parameters, return shapes, and responsibilities as adjacent layers.
Do not pass database rows, HTTP requests, GraphQL contexts, ORM models, vendor DTOs, or framework objects into core domain logic unless those are genuinely part of the domain.
Pull complexity into a module when doing so removes duplicated caller logic, hides volatile decisions, centralizes invariants, or makes common usage obvious.
Do not pull complexity downward when the abstraction is speculative, surprising, too generic, only serves one simple caller, or turns the module into an unfocused god object.
Test: will the added internal complexity remove more complexity from callers and future changes than it adds to the module?
Before exposing an error, flag, or special mode, ask:
Expose a special case only when the caller has a legitimate decision to make.
Names and comments are part of the interface.
Use names that reveal domain intent, not mechanism. Comments should document contract, invariants, side effects, lifecycle constraints, defaults, error semantics, and non-obvious assumptions. Do not use comments to compensate for a vague abstraction or misleading name.