agentsclimarketplace

Software design

Skill patforna/core-skills/skills/software-design

Software design principles (Ousterhout-style). Use when making module/interface design decisions, reviewing code for complexity, or deciding where to put functionality. TRIGGER when: designing APIs, reviewing module boundaries, deciding function signatures, or user asks about complexity management. DO NOT TRIGGER when: purely mechanical refactoring with no design decisions.From its SKILL.md

Install
npx -y skills add patforna/core-skills --skill software-design

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

13.4 KB, ~2.5k tokens by cl100k_base, as published. Nobody here has run it

Software Design -- Complexity Management

You make design decisions by minimizing complexity. Complexity is anything that makes code hard to understand or modify. Apply these rules during implementation and code review.

Complexity Diagnostic

Complexity has three symptoms. When you detect any of these, the design needs work:

  1. Change amplification: A single logical change requires edits in many places. Look for the same knowledge encoded in multiple locations.
  2. Cognitive load: Understanding a piece of code requires accumulating a large amount of context. Count how many things a developer must hold in mind.
  3. Unknown unknowns: It is unclear what code must be modified or what information is relevant. This is the worst form -- it causes bugs that aren't caught until production.

Two root causes produce these symptoms: dependencies (code that cannot be understood or modified in isolation) and obscurity (important information is not obvious).

Deep Modules

Design every module to be deep: simple interface, powerful implementation. The interface is the cost a caller pays; the implementation is the value they receive.

Decision procedure for module depth:

  1. Count the elements in the module's interface: methods, parameters, exceptions, configuration, implicit behavioral contracts.
  2. Estimate the implementation complexity hidden behind that interface.
  3. If the interface complexity approaches the implementation complexity, the module is shallow. Restructure it.

When splitting a function/class into two pieces:

  • Each resulting piece must provide a different abstraction that is independently useful.
  • If both pieces have similar signatures and similar levels of abstraction, the split made things worse -- merge them back.
  • Splitting a method only to make it shorter, without creating a cleaner abstraction, produces shallow modules.

When merging pieces together:

  • Merge when pieces share information that would otherwise leak between them.
  • Merge when combining simplifies the caller's job (one call instead of two coordinated calls).
  • Merge when it eliminates code duplication.
  • Do NOT merge if the pieces are independently useful and combining would create a multi-purpose module with no clear single abstraction.

Information Hiding

Every module should encapsulate design decisions (data structures, algorithms, serialization formats, protocols) that no other module needs to see.

Test for information leakage: If two modules both depend on the same design decision (e.g., a file format, a protocol, an ordering), information is leaking. Fix by consolidating that knowledge into one module.

Test for temporal decomposition: If your module boundaries mirror the order of operations (read, then process, then write) rather than grouping by shared knowledge, you have temporal decomposition. Restructure so that all code dealing with the same information lives together, regardless of when it executes.

Test for overexposure: If the caller must understand implementation details to use the module correctly, the interface is leaking. Shrink the interface by hiding those details.

Generality: the "Somewhat General-Purpose" Sweet Spot

When designing a module, make the interface general-purpose and the usage special-purpose -- even if there is currently only one caller.

Three questions to test your interface:

  1. "What is the simplest interface that covers all my current needs?" -- If the interface has methods used by only one caller, those methods are probably too special-purpose.
  2. "In how many situations will this method be used?" -- If only one situation, it is likely too specific. Generalize the method and let the caller supply the specifics.
  3. "Is this API easy to use for my current needs?" -- If a caller must write boilerplate to bridge between the general-purpose interface and its current need, the interface is too general.

Different Layer, Different Abstraction

Each layer in a call chain must provide a fundamentally different abstraction from adjacent layers. Detect violations with these red flags:

Pass-through method: A method that does little except call another method with a similar or identical signature. Fix by: (a) eliminating it, (b) redistributing functionality so each layer does something distinct, or (c) merging the two layers.

Pass-through variable: A variable threaded through many methods solely so a deeply nested function can access it. Fix by: storing it in a shared context object, or a module-level/class-level attribute that the deep function can access directly.

Decorator/wrapper that mirrors the underlying interface: If the decorator's methods mostly forward to identically-named methods on the wrapped object, the decorator is a shallow layer. Only use decorators when they add substantial new behavior distinct from the underlying class.

Pull Complexity Downward

When you have a choice about where to handle complexity, push it into the implementation rather than the interface. A module with a complex implementation but simple interface is better than the reverse.

Decision procedure:

  1. Can the module handle the complexity internally without exposing it to callers? If yes, do it -- even if it makes the implementation harder.
  2. Is the current design forcing callers to provide configuration or make decisions that the module could figure out on its own? If yes, add a reasonable default and let the module decide.
  3. Would handling it internally require knowledge the module doesn't have? Then the complexity genuinely belongs in the caller.

Configuration parameters are a red flag. Each configuration parameter is complexity pushed to the caller. Before adding one, ask: can the module compute a reasonable value automatically? If yes, do that instead.

Define Errors Out of Existence

Exceptions are a major source of complexity. Reduce the number of places exceptions must be handled using four techniques, in order of preference:

  1. Define errors out of existence: Redefine the operation's semantics so the "error" condition is simply part of normal behavior. Example: a delete operation that succeeds even if the target doesn't exist (its contract is "ensure target doesn't exist," not "remove existing target"). Example: a substring that clamps out-of-range indices instead of throwing.

  2. Exception masking: Handle the exception at a low level so higher levels never see it. Example: a network layer that transparently retransmits lost packets. This is pulling complexity downward.

  3. Exception aggregation: Instead of catching each exception individually at the point of origin, let exceptions propagate upward and catch them with a single handler at a higher level. Example: a web server that catches all request-processing exceptions in one top-level handler that returns an error response.

  4. Just crash: For errors that are rare, hard to handle, and offer no meaningful recovery (e.g., out of memory, corrupted internal state), abort with a clear diagnostic message.

When NOT to apply: If callers genuinely need the exception information to make decisions, the exception must be exposed even though it adds complexity. Only define away or mask exceptions when the information is not needed outside the module.

Design It Twice

Before committing to any significant design decision, sketch at least two radically different approaches. Compare them on:

  • Interface simplicity (fewer concepts for callers to learn)
  • Generality (how many situations does each approach handle?)
  • Performance
  • Ease of implementation

Pick the best, or combine strengths from multiple alternatives. This applies to: interface design, implementation strategy, module decomposition, and data structure choice.

Comments

Write comments that describe things that are not obvious from the code. Use different words from the name of the entity being documented.

Interface comments (every public class, method, and function):

  • First sentence: what the method does from the caller's perspective (the abstraction).
  • Each parameter: what it means, its constraints, its units, whether null is permitted.
  • Return value: what it represents, edge cases.
  • Side effects: any state changes beyond the return value.
  • Exceptions: what can be thrown and when.
  • Preconditions: what must be true before calling.
  • If the interface comment must describe implementation details to be complete, the method is shallow -- redesign it.

Data structure / field comments (every instance variable and important field):

  • Describe what the variable represents (nouns), not how it is manipulated (verbs).
  • State units, boundary conditions (inclusive/exclusive), null semantics, invariants.

Implementation comments (inside method bodies):

  • Place before major blocks to describe what the block does and why, not how.
  • For loops, describe what each iteration does at a higher level than the code.
  • For tricky code, explain the reasoning or link to an issue.

Cross-module comments (for design decisions spanning multiple modules):

  • Place the authoritative documentation in the most central location.
  • Add short references ("See designNotes: Zombies") in other modules.
  • Never duplicate cross-module documentation -- it will become stale.

Naming

Choose names that create a clear image of the underlying entity.

  • Precision test: "If someone sees this name in isolation, will they correctly guess what it refers to?" If not, choose a more specific name.
  • Consistency: Use the same name for the same concept everywhere. Never reuse a name for a different concept. Ensure all variables with a given name have the same behavior.
  • Scope-proportional length: i is fine for a 3-line loop. A variable used across a large span of code needs a descriptive name.
  • Boolean names should be predicates: is_valid, has_items, cursor_visible -- not status, flag, blink.
  • Avoid filler words: Drop Object, Field, Data, type prefixes, class-name prefixes from variable names unless they add genuine disambiguation.
  • Hard to name = design smell: If you can't find a simple, precise name, the underlying concept may not have a clean design. Consider restructuring.

Consistency

Follow existing conventions in the codebase, even if you would do it differently.

  • Before introducing a new convention, verify that no existing convention covers the same situation.
  • Having a "better idea" is not sufficient reason to introduce inconsistency. The value of consistency almost always exceeds the value of a marginally better approach used in only some places.
  • Do not force dissimilar things into the same pattern. Consistency only helps when "if it looks like an X, it really is an X."

Red Flags Checklist

Use this checklist during code review or when evaluating your own design decisions.

Red FlagDiagnostic Question
Shallow ModuleIs the interface complex relative to the functionality provided?
Information LeakageDoes the same design decision appear in multiple modules?
Temporal DecompositionAre module boundaries based on operation order rather than information grouping?
OverexposureMust callers know implementation details to use the module?
Pass-Through MethodDoes this method do little except call another method with a similar signature?
Pass-Through VariableIs a variable threaded through many methods just to reach a deeply nested consumer?
RepetitionDoes the same code/pattern appear in multiple places, suggesting a missing abstraction?
Special-General MixtureDoes general-purpose code contain special-case logic, or vice versa?
Conjoined MethodsMust you read another method to understand this one?
Comment Repeats CodeCould someone write this comment just by reading the code next to it?
Implementation Contaminates InterfaceDoes the interface comment describe implementation details callers don't need?
Vague NameIs the name broad enough to refer to many different things?
Hard to Pick NameIs it difficult to find a simple, precise name? (Underlying concept may not be clean.)
Hard to DescribeIs it difficult to write a simple, complete comment? (Design may need restructuring.)
Nonobvious CodeCan the meaning and behavior be understood with a quick reading?

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,422. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.