搜索

PythonReactSEOPDF
GitHub Skill

software-architect

Design and build software with high cohesion, low coupling, composition-first structures, and functional-style defaults (TigerStyle-inspired).

Stephanvs☆ 6 仓库 Stars0 仓库 Forks

安装与使用

请根据来源仓库和你的 AI 工具说明安装此技能。先阅读技能文件和依赖要求,再添加到工作流。

查看来源 ↗使用指南

技能说明

Software Architect

“Make things as simple as possible, but no simpler.” — Albert Einstein

This skill is for architecting and implementing software that is:

  • Highly cohesive (each module has a clear purpose).
  • Loosely coupled (modules depend on stable interfaces, not internal details).
  • Composition-first (prefer wiring small parts together over inheritance trees).
  • Functional-style by default (pure core, explicit effects, data-oriented design).
  • Disciplined about quality (simplicity over cleverness; high standards).

TigerStyle-inspired ideas are used as defaults and heuristics (apply when they improve safety, correctness, clarity, or performance; relax when they add accidental complexity).

North Star

  • Optimize for correctness through understanding, then performance.
  • Prefer simple, explicit control flow over implicit behavior.
  • Choose the smallest design that fully solves the problem.
  • Treat “clever” as a code smell; prefer boring, obvious solutions.
  • Pay down confusion immediately (rename, simplify, split, delete).

Architecture Goals

High Cohesion
  • Each module has exactly one job and one reason to change.
  • Keep related data and the code that manipulates it together (ownership is a design tool).
  • Create APIs that reflect the domain (nouns/verbs that fit), not the implementation.
Low Coupling
  • Depend on interfaces/ports and data contracts, not concrete implementations.
  • Make dependencies explicit at construction time (dependency injection over hidden globals).
  • Keep the public surface area small and stable; keep internals private and replaceable.
  • Avoid “action at a distance” (side effects and shared mutable state that surprise callers).
Composition Over Inheritance / Dynamic Polymorphism

Use inheritance and deep OO polymorphism only when it is clearly the simplest tool. Default to:

  • Composition: build behavior by wiring small components together.
  • Parameterization: pass functions/strategies/config instead of subclassing.
  • Data + functions: plain data structures and pure transformations.
  • Sum types / tagged unions + pattern matching (when available) instead of subtype webs.
  • Small interfaces (ports) that describe capabilities, not “class identity”.

Rule of thumb: prefer “assemble behavior at the boundary” over “embed behavior in types”.

Functional-Style Defaults

Functional Core, Imperative Shell
  • Put domain logic in pure functions (deterministic, testable, no I/O).
  • Keep I/O, time, randomness, and external services in the imperative shell.
  • Make boundaries explicit: parsing/validation, domain decisions, side effects.

Pseudo-structure:

imperative shell:
  load inputs (I/O)
  validate/parse -> domain types
  call pure domain functions
  persist/emit effects (I/O)
Immutability and Data Flow
  • Prefer immutable data and persistent transformations.
  • Minimize mutable state; if state must exist, confine it (single owner, clear lifecycle).
  • Prefer returning new values over mutating shared ones.
  • Avoid hidden mutation via global caches/singletons (unless carefully isolated and justified).
Errors as Values
  • Model failures explicitly (Result/Either/Option) instead of implicit exceptions where possible.
  • Separate programmer errors (bugs, invalid invariants) from operational errors (timeouts, disk full, network failures).
  • Fail fast on programmer errors (assert/throw/panic) and handle operational errors deliberately.

TigerStyle-Inspired Defaults (Apply When Applicable)

These are high-leverage defaults for quality and safety. Apply them when they reduce risk and improve clarity; relax them when the surrounding ecosystem makes them counterproductive.

Prefer Explicitness
  • Avoid magic, reflection-heavy control flow, and spooky metaprogramming.
  • Prefer iteration over recursion; use recursion only when clearly bounded and simpler.
  • Make non-obvious defaults explicit at call sites (especially for safety/performance knobs).
  • Keep control flow in one place (“push ifs up and fors down”):
    • Parent functions own branching and orchestration.
    • Leaf helpers do straight-line work and stay pure.
Put Limits on Everything
  • Ensure loops/queues/retries have clear upper bounds and timeouts.
  • Bound work per request/event to avoid tail-latency spikes.
  • Avoid unbounded memory growth (caches need eviction and explicit sizing).
Assert and Enforce Invariants
  • Use assertions/contracts to encode invariants at boundaries (inputs, outputs, state transitions).
  • When an invariant is critical, assert it in two places (before crossing a boundary and after crossing it) when practical.
  • Prefer multiple small assertions over one compound assertion (easier to debug and reason about).
Design First, Then Build
  • Spend thought upfront: define invariants, data model, interfaces, failure modes.
  • Do performance “back-of-the-envelope” sketches early (big-O and resource costs).
  • Avoid premature abstraction; but also avoid “we’ll fix it later” debt.
Zero-Technical-Debt Attitude
  • Do not ship known footguns (missing error handling, unbounded loops, fragile concurrency, unclear ownership, undocumented invariants).
  • If you must compromise, make it explicit: capture the rationale and a concrete follow-up.

How to Design (Before Writing Code)

  1. Clarify the problem

    • What are the inputs/outputs?
    • What must always be true (invariants)?
    • What are the failure modes and recovery expectations?
  2. Choose boundaries and dependencies

    • Identify the pure domain core.
    • Identify external systems (DB, network, filesystem, clock, random, UI).
    • Define ports/interfaces for external dependencies.
  3. Model the domain

    • Use types and naming to prevent invalid states.
    • Prefer explicit units/qualifiers in names (timeout_ms, limit_items_max).
  4. Plan for testability

    • Pure functions get unit tests.
    • Adapters get integration tests.
    • Edge cases include invalid/negative tests, not only happy paths.

Implementation Guidelines

  • Keep functions small enough to fit on a screen; split by responsibility.
  • Keep mutable state tightly scoped; prefer local variables over shared fields.
  • Keep APIs narrow: fewer parameters, simpler return types, fewer states.
  • Prefer readable naming over abbreviations; choose nouns/verbs carefully.
  • Delete dead code and unnecessary indirection.
  • Structure by feature/domain, not by technical role. Avoid folders like commands/, components/, utils/ at the top level. Instead, group by domain: auth/, calendar/, settings/. Technical groupings (if needed) should be leaves within feature folders, not roots.

Checklists

Architecture Checklist
  • Clear module boundaries and ownership
  • High cohesion within modules (no “misc” grab bags)
  • Low coupling across modules (interfaces/ports, no internal leaks)
  • Composition-first design (no unnecessary inheritance or frameworks-as-architecture)
  • Pure core identified; effects isolated at boundaries
  • Explicit error model and failure handling strategy
  • Work/memory bounded where relevant
  • Performance sketch done for hot paths
Implementation Checklist
  • Core logic is testable without I/O
  • Side effects are explicit and centralized
  • Invariants asserted at boundaries
  • Error cases handled (not silently ignored)
  • Naming is precise; no misleading abstractions
  • Complexity reduced (simplify after it works)
  • Docs/comments explain why for non-obvious decisions
Review / Simplification Checklist
  • Can a new engineer explain the design in 5 minutes?
  • Are there fewer concepts than necessary? If yes, delete/merge.
  • Are there more concepts than necessary? If yes, simplify.
  • Does each module have one reason to change?
  • Are dependencies pointing the right way (domain does not depend on infrastructure)?
  • Are there hidden side effects or shared mutable state?
  • Is any abstraction “clever”? Replace with something obvious.

Examples (Language-Agnostic)

Composition via Dependency Injection (no inheritance)
def process_order(deps, command):
  validated = validate(command)
  decision  = decide(validated)         # pure
  effects   = plan_effects(decision)    # pure
  deps.outbox.publish(effects.events)   # I/O
  deps.repo.save(effects.state)         # I/O

deps is explicit (repo/outbox/clock/etc.), domain decisions stay pure, and behavior is composed by passing dependencies rather than subclassing.

Functional Core / Imperative Shell Boundary
shell:
  raw_input -> parse/validate -> domain types
  domain types -> pure transform -> new domain types
  new domain types -> serialize/persist/emit

If you keep this boundary crisp, the system stays cohesive, low-coupled, and easy to test.

使用示例

查看来源仓库中的使用示例与配置说明。

查看来源 ↗

来源历史

收录时间
2026-01-20
索引更新时间
2026-01-26
Branch
main

收录与同步日期属于本站记录。源码修改历史请查看来源仓库。

GitHub ↗

chat Comments (0)

chat_bubble_outline

No comments yet. Be the first to share your thoughts!