Designing Software
Overview
Design happens before code. Good design surfaces edge cases, clarifies contracts, and reveals hidden assumptions before they become bugs. This skill provides frameworks for thinking through designs systematically.
When to Use
- Planning a new feature or module
- Designing an API or interface
- Making architectural decisions
- Reviewing a design document or proposal
- Refactoring existing code
Property Discovery
Before implementation, ask discovery questions to surface the properties your code must satisfy. Properties caught during design are cheaper than properties caught during testing.
Discovery Questions
| Property |
Discovery Question |
If Yes, Document |
| Roundtrip |
Does an inverse operation exist? |
decode(encode(x)) == x |
| Idempotence |
Is applying twice the same as once? |
f(f(x)) == f(x) |
| Invariants |
What quantities are preserved? |
Length, count, sum, ordering |
| Commutativity |
Is argument order irrelevant? |
f(a,b) == f(b,a) |
| Associativity |
Can operations be regrouped? |
f(f(a,b),c) == f(a,f(b,c)) |
| Identity |
Does a neutral element exist? |
f(x, identity) == x |
| Oracle |
Is there a reference implementation? |
new_impl(x) == old_impl(x) |
| Verifiability |
Can output correctness be easily checked? |
is_sorted(sort(x)) |
Design Questions Surfaced
Property discovery often reveals implicit decisions:
- Deleted/deactivated entities — Soft delete or hard delete? Filter by default?
- Case sensitivity — Case-insensitive matching? Normalized storage?
- Sort stability — Preserve original order for ties?
- Null handling — Nullable fields? Default values? Explicit vs implicit nulls?
- Concurrency — Thread-safe? Atomic operations needed?
- Idempotency keys — Retry-safe? Duplicate detection?
Document these decisions explicitly rather than discovering them during implementation.
Example: File Sync Feature
Before coding, ask:
- Roundtrip — Can we reconstruct local state from remote? Remote from local?
- Idempotence — Is syncing twice the same as syncing once?
- Invariants — Is file count preserved? File contents?
- Commutativity — Does sync order matter (A then B vs B then A)?
Answers reveal design requirements:
- Need conflict resolution strategy (commutativity fails)
- Need checksums for verification (invariant checking)
- Need sync state tracking (idempotence requires knowing what's already synced)
Architecture Levels
Use C4 Model abstractions to discuss systems at appropriate detail levels.
| Level |
Abstraction |
Audience |
Shows |
| 1 |
System Context |
Everyone |
System, users, external dependencies |
| 2 |
Container |
Technical |
Deployable units, communication protocols |
| 3 |
Component |
Developers |
Internal modules, responsibilities |
| 4 |
Code |
Developers |
Classes, functions (rarely needed) |
Start at Level 1. Most discussions need only Levels 1-2. Component diagrams go stale quickly; automate or skip them.
Diagram format: Use Mermaid syntax for all diagrams. Mermaid renders natively in most markdown viewers and is more maintainable than ASCII art.
See c4-model-reference.md for detailed guidance.
Design Principles
Apply SOLID principles with Python pragmatism:
| Principle |
Core Idea |
Apply When |
| Single Responsibility |
One reason to change |
Always |
| Open/Closed |
Extend without modifying |
Plugin systems, stable APIs |
| Liskov Substitution |
Subtypes are substitutable |
Class hierarchies, Protocols |
| Interface Segregation |
Small, focused interfaces |
Large Protocols, testability |
| Dependency Inversion |
Depend on abstractions |
Testability, flexibility |
Python nuance: Duck typing and composition reduce the need for formal abstractions. Don't over-engineer; wait for patterns to emerge before abstracting.
See solid-principles-reference.md for examples and anti-patterns.
Design Checklist
Before implementation:
Common Mistakes
| Mistake |
Why It Fails |
Correct Approach |
| Skipping property discovery |
Edge cases found late, in production |
Ask discovery questions upfront |
| Over-detailed diagrams |
Go stale, aren't maintained |
Use Level 1-2; Level 3 only if valuable |
| Premature abstraction |
Complexity without benefit |
Wait for three use cases |
| Designing for hypotheticals |
YAGNI violation |
Design for current requirements |
| Implicit decisions |
Inconsistent implementation |
Document case sensitivity, null handling, etc. |
Anti-Rationalizations
- "I'll figure out edge cases during implementation" — You'll ship bugs. Ask discovery questions now.
- "We might need this flexibility later" — Add flexibility when you need it, not before.
- "A diagram will clarify this" — Only if it's the right level of detail. Start with Level 1.
- "This is too simple to design" — Simple features have hidden complexity. Spend 5 minutes on discovery questions.
Supporting References
- c4-model-reference.md — C4 Model abstractions and diagram types
- solid-principles-reference.md — SOLID principles with Python examples
Summary
- Ask property discovery questions before coding. Roundtrip, idempotence, invariants — surface edge cases early.
- Document implicit decisions. Case sensitivity, null handling, sort stability — make them explicit.
- Start at the right abstraction level. System Context for most discussions; Container for technical detail.
- Wait for patterns before abstracting. Three use cases, then generalize.
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: designing-software3description: Use when planning features, designing APIs, or making architectural decisions. Covers property discovery, C4 modeling, and SOLID principles.4---56# Designing Software78## Overview910Design happens before code. Good design surfaces edge cases, clarifies contracts, and reveals hidden assumptions before they become bugs. This skill provides frameworks for thinking through designs systematically.1112## When to Use1314- Planning a new feature or module15- Designing an API or interface16- Making architectural decisions17- Reviewing a design document or proposal18- Refactoring existing code1920## Property Discovery2122Before implementation, ask discovery questions to surface the properties your code must satisfy. Properties caught during design are cheaper than properties caught during testing.2324### Discovery Questions2526| Property | Discovery Question | If Yes, Document |27|----------|-------------------|------------------|28| **Roundtrip** | Does an inverse operation exist? | `decode(encode(x)) == x` |29| **Idempotence** | Is applying twice the same as once? | `f(f(x)) == f(x)` |30| **Invariants** | What quantities are preserved? | Length, count, sum, ordering |31| **Commutativity** | Is argument order irrelevant? | `f(a,b) == f(b,a)` |32| **Associativity** | Can operations be regrouped? | `f(f(a,b),c) == f(a,f(b,c))` |33| **Identity** | Does a neutral element exist? | `f(x, identity) == x` |34| **Oracle** | Is there a reference implementation? | `new_impl(x) == old_impl(x)` |35| **Verifiability** | Can output correctness be easily checked? | `is_sorted(sort(x))` |3637### Design Questions Surfaced3839Property discovery often reveals implicit decisions:4041- **Deleted/deactivated entities** — Soft delete or hard delete? Filter by default?42- **Case sensitivity** — Case-insensitive matching? Normalized storage?43- **Sort stability** — Preserve original order for ties?44- **Null handling** — Nullable fields? Default values? Explicit vs implicit nulls?45- **Concurrency** — Thread-safe? Atomic operations needed?46- **Idempotency keys** — Retry-safe? Duplicate detection?4748Document these decisions explicitly rather than discovering them during implementation.4950### Example: File Sync Feature5152Before coding, ask:53541. **Roundtrip** — Can we reconstruct local state from remote? Remote from local?552. **Idempotence** — Is syncing twice the same as syncing once?563. **Invariants** — Is file count preserved? File contents?574. **Commutativity** — Does sync order matter (A then B vs B then A)?5859Answers reveal design requirements:60- Need conflict resolution strategy (commutativity fails)61- Need checksums for verification (invariant checking)62- Need sync state tracking (idempotence requires knowing what's already synced)6364## Architecture Levels6566Use C4 Model abstractions to discuss systems at appropriate detail levels.6768| Level | Abstraction | Audience | Shows |69|-------|-------------|----------|-------|70| 1 | System Context | Everyone | System, users, external dependencies |71| 2 | Container | Technical | Deployable units, communication protocols |72| 3 | Component | Developers | Internal modules, responsibilities |73| 4 | Code | Developers | Classes, functions (rarely needed) |7475**Start at Level 1.** Most discussions need only Levels 1-2. Component diagrams go stale quickly; automate or skip them.7677**Diagram format:** Use Mermaid syntax for all diagrams. Mermaid renders natively in most markdown viewers and is more maintainable than ASCII art.7879See [c4-model-reference.md](c4-model-reference.md) for detailed guidance.8081## Design Principles8283Apply SOLID principles with Python pragmatism:8485| Principle | Core Idea | Apply When |86|-----------|-----------|------------|87| **Single Responsibility** | One reason to change | Always |88| **Open/Closed** | Extend without modifying | Plugin systems, stable APIs |89| **Liskov Substitution** | Subtypes are substitutable | Class hierarchies, Protocols |90| **Interface Segregation** | Small, focused interfaces | Large Protocols, testability |91| **Dependency Inversion** | Depend on abstractions | Testability, flexibility |9293**Python nuance**: Duck typing and composition reduce the need for formal abstractions. Don't over-engineer; wait for patterns to emerge before abstracting.9495See [solid-principles-reference.md](solid-principles-reference.md) for examples and anti-patterns.9697## Design Checklist9899Before implementation:100101- [ ] Properties identified and documented102- [ ] Edge cases surfaced through discovery questions103- [ ] Appropriate abstraction level chosen (system/container/component)104- [ ] Dependencies flow toward abstractions, not concretions105- [ ] Each module has a single, clear responsibility106107## Common Mistakes108109| Mistake | Why It Fails | Correct Approach |110|---------|--------------|------------------|111| Skipping property discovery | Edge cases found late, in production | Ask discovery questions upfront |112| Over-detailed diagrams | Go stale, aren't maintained | Use Level 1-2; Level 3 only if valuable |113| Premature abstraction | Complexity without benefit | Wait for three use cases |114| Designing for hypotheticals | YAGNI violation | Design for current requirements |115| Implicit decisions | Inconsistent implementation | Document case sensitivity, null handling, etc. |116117## Anti-Rationalizations118119- "I'll figure out edge cases during implementation" — You'll ship bugs. Ask discovery questions now.120- "We might need this flexibility later" — Add flexibility when you need it, not before.121- "A diagram will clarify this" — Only if it's the right level of detail. Start with Level 1.122- "This is too simple to design" — Simple features have hidden complexity. Spend 5 minutes on discovery questions.123124## Supporting References125126- [c4-model-reference.md](c4-model-reference.md) — C4 Model abstractions and diagram types127- [solid-principles-reference.md](solid-principles-reference.md) — SOLID principles with Python examples128129## Summary1301311. **Ask property discovery questions before coding.** Roundtrip, idempotence, invariants — surface edge cases early.1322. **Document implicit decisions.** Case sensitivity, null handling, sort stability — make them explicit.1333. **Start at the right abstraction level.** System Context for most discussions; Container for technical detail.1344. **Wait for patterns before abstracting.** Three use cases, then generalize.135136---137> Converted and distributed by [TomeVault](https://tomevault.io/claim/cyarie) — claim your Tome and manage your conversions.138<!-- tomevault:4.0:skill_md:2026-04-14 -->