CtrlK
BlogDocsLog inGet started
Tessl Logo

evilissimo/modular-software-design

Use before implementing or refactoring software when the task requires designing module boundaries, APIs, layers, abstractions, services, repositories, adapters, or architecture. Helps coding agents 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.

90

Quality

90%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

decision-rules.mdreferences/

Decision Rules for Modular Software Design

Use these rules while preparing or reviewing a software design.

Interface Rules

  • Prefer a small number of intention-revealing operations over many tiny methods.
  • Name methods by caller intent, not internal workflow.
  • Make the common case direct and hard to misuse.
  • Keep rare cases isolated from the common path.
  • Replace boolean flags with clearer domain concepts, policy objects, or separate operations when the flag changes behavior substantially.
  • Avoid call-order requirements unless the sequence is the actual abstraction.
  • Return stable, meaningful domain or application types instead of raw implementation artifacts.
  • Expose only errors that callers can take meaningful action on.
  • Document the interface contract before writing implementation code.

Information-Hiding Rules

Hide implementation knowledge unless callers legitimately control it.

Hide:

  • Internal representations and schemas.
  • SQL queries, ORM details, database transactions, and storage layout.
  • HTTP, GraphQL, queue, RPC, or framework objects outside their boundary.
  • Vendor DTOs, status codes, retry semantics, pagination tokens, and API quirks.
  • Mapping between external and internal models.
  • Validation order, lifecycle sequencing, caching, retries, timeouts, and concurrency behavior.
  • Special-case normalization and fallback behavior.
  • Constants or thresholds that are implementation details rather than product/domain policy.

Expose:

  • The abstraction callers need.
  • The smallest useful set of operations.
  • Stable input and output types.
  • Errors callers can handle.
  • Configuration callers genuinely own.

Leakage Detection

A boundary leaks when callers must know facts that should belong inside the callee.

Check:

  • Do API names mention table names, transport protocols, vendor concepts, or implementation mechanics?
  • Do parameters include transactions, cursors, retry policies, mappers, raw rows, framework contexts, or internal state?
  • Do return values expose storage rows, DTOs, status codes, exceptions, or framework/vendor objects?
  • Must callers perform setup, cleanup, validation, retries, normalization, or method calls in a precise sequence?
  • Are the same special cases handled by multiple callers?
  • Would a storage, vendor, protocol, or framework change affect callers that should be insulated?

Pass-Through Detection

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 or Merge Rules

Split modules when:

  • They hide different volatile decisions.
  • They change for different reasons.
  • One is domain policy and the other is infrastructure.
  • One concept can be used independently behind a clean interface.
  • Combining them forces callers to depend on concepts they do not need.

Merge modules when:

  • They constantly call each other.
  • They share private knowledge.
  • The split creates pass-through methods or variables.
  • Callers must coordinate both modules in a precise order.
  • The separation mirrors execution order rather than ownership of concepts.
  • The public interfaces are almost as complex as the combined implementation.

Rule: split by hidden knowledge and axis of change; merge when separation exports coordination complexity.

Layering Rules

Each layer must provide a distinct abstraction.

Typical responsibilities:

  • Transport layer: translate requests, responses, auth context, and protocol errors.
  • Application layer: coordinate use cases and transaction-level workflows.
  • Domain layer: enforce domain concepts, policies, invariants, and state transitions.
  • Infrastructure layer: hide persistence, queues, external services, vendors, and framework details.

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.

Pulling Complexity Downward

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?

Designing Away Errors and Special Cases

Before exposing an error, flag, or special mode, ask:

  • Can the interface make this state impossible?
  • Can a default remove the branch?
  • Can the module normalize inputs?
  • Can validation happen once inside the module?
  • Can a special case become an ordinary case?
  • Can a domain concept replace a boolean or raw status code?
  • Can retry/fallback behavior be hidden?

Expose a special case only when the caller has a legitimate decision to make.

Naming and Comments

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.

README.md

SKILL.md

tile.json