software-design-principles
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSoftware Design Principles
软件设计原则
Professional software design patterns and principles for writing maintainable, well-structured code.
用于编写可维护、结构优良代码的专业软件设计模式与原则。
Critical Rules
核心规则
🚨 Fail-fast over silent fallbacks. Never use fallback chains (). If data should exist, validate and throw a clear error.
value ?? backup ?? 'unknown'🚨 Strive for maximum type-safety. No . No . Type escape hatches defeat TypeScript's purpose. There's always a type-safe solution.
anyas🚨 Make illegal states unrepresentable. Use discriminated unions, not optional fields. If a state combination shouldn't exist, make the type system forbid it.
🚨 Inject dependencies, don't instantiate. No inside methods. Pass dependencies through constructors.
new SomeService()🚨 Intention-revealing names only. Never use , , , , . Name things for what they do in the domain.
datautilshelpershandlerprocessor🚨 No code comments. Comments are a failure to express intent in code. If you need a comment to explain what code does, the code isn't clear enough—refactor it.
🚨 Use Zod for runtime validation. In TypeScript, use Zod schemas for parsing external data, API responses, and user input. Type inference from schemas keeps types and validation in sync.
🚨 优先快速失败,而非静默回退。禁止使用回退链()。如果数据理应存在,需进行验证并抛出清晰的错误。
value ?? backup ?? 'unknown'🚨 追求极致类型安全。禁止使用 ,禁止使用 。类型逃逸机制违背了TypeScript的设计初衷。永远存在类型安全的解决方案。
anyas🚨 让非法状态无法被表示。使用可区分联合类型,而非可选字段。如果某种状态组合不应存在,就让类型系统禁止它。
🚨 注入依赖,而非实例化。禁止在方法内部使用 。通过构造函数传递依赖。
new SomeService()🚨 仅使用表意明确的命名。禁止使用 、、、、这类名称。要根据领域内的功能来命名。
datautilshelpershandlerprocessor🚨 禁止编写代码注释。注释是无法在代码中表达意图的失败体现。如果需要注释来解释代码的功能,说明代码不够清晰——请重构它。
🚨 使用Zod进行运行时验证。在TypeScript中,使用Zod模式解析外部数据、API响应和用户输入。通过模式进行类型推断,可保持类型与验证逻辑同步。
When This Applies
适用场景
- Writing new code (these are defaults, not just refactoring goals)
- Refactoring existing code
- Code reviews and design reviews
- During TDD REFACTOR phase
- When analyzing coupling and cohesion
- 编写新代码(这些是默认规则,而非仅作为重构目标)
- 重构现有代码
- 代码评审与设计评审
- 测试驱动开发(TDD)的重构阶段
- 分析代码耦合度与内聚性时
Core Philosophy
核心理念
Well-designed, maintainable code is far more important than getting things done quickly. Every design decision should favor:
- Clarity over cleverness
- Explicit over implicit
- Fail-fast over silent fallbacks
- Loose coupling over tight integration
- Intention-revealing over generic
设计良好、可维护的代码远比快速完成功能重要。每个设计决策都应优先考虑:
- 清晰性而非技巧性
- 显式而非隐式
- 快速失败而非静默回退
- 松耦合而非紧集成
- 表意明确而非通用模糊
Code Without Comments
无注释代码
Never write comments - write expressive code instead.
永远不要编写注释——取而代之的是编写表意清晰的代码。
Object Calisthenics
对象健身法(Object Calisthenics)
Apply object calisthenics principles:
遵循对象健身法原则:
The Nine Rules
九条规则
-
One level of indentation per method
- In practice, I will tolerate upto 3
-
Don't use the ELSE keyword
- Use early returns instead
-
Wrap all primitives and strings
- Create value objects
- Encapsulate validation logic
- Make domain concepts explicit
-
First class collections
- Classes with collections should contain nothing else
-
One dot per line
-
Don't abbreviate
- Use full, descriptive names
-
Keep all entities small
- Small classes (< 150 lines)
- Small methods (< 10 lines)
- Small packages/modules
- Easier to understand and maintain
-
Avoid getters/setters/properties on entities
- Tell, don't ask
- Objects should do work, not expose data
-
每个方法仅允许一层缩进
- 实际应用中,最多可容忍3层缩进
-
禁止使用ELSE关键字
- 改用提前返回
-
包装所有原始类型与字符串
- 创建值对象
- 封装验证逻辑
- 明确领域概念
-
一等集合
- 包含集合的类不应包含其他内容
-
每行最多一个点运算符
-
禁止缩写
- 使用完整、描述性的名称
-
保持所有实体小巧
- 小型类(少于150行)
- 小型方法(少于10行)
- 小型包/模块
- 更易于理解和维护
-
避免在实体中使用getter/setter/属性
- 命令,而非查询
- 对象应执行操作,而非暴露数据
When to Apply
适用时机
-
During refactoring:
-
During code review:
-
重构期间
-
代码评审期间
Feature Envy Detection
特性依恋检测
Method uses another class's data more than its own? Move it there.
typescript
// ❌ FEATURE ENVY - obsessed with Order's data
class InvoiceGenerator {
generate(order: Order): Invoice {
const total = order.getItems().map(i => i.getPrice() * i.getQuantity()).reduce((a,b) => a+b, 0)
return new Invoice(total + total * order.getTaxRate() + order.calculateShipping())
}
}
// ✅ Move logic to the class it envies
class Order {
calculateTotal(): number { /* uses this.items, this.taxRate */ }
}
class InvoiceGenerator {
generate(order: Order): Invoice { return new Invoice(order.calculateTotal()) }
}Detection: Count external vs own references. More external? Feature envy.
如果某个方法使用其他类的数据多于自身类的数据?将该方法移至目标类中。
typescript
// ❌ FEATURE ENVY - obsessed with Order's data
class InvoiceGenerator {
generate(order: Order): Invoice {
const total = order.getItems().map(i => i.getPrice() * i.getQuantity()).reduce((a,b) => a+b, 0)
return new Invoice(total + total * order.getTaxRate() + order.calculateShipping())
}
}
// ✅ Move logic to the class it envies
class Order {
calculateTotal(): number { /* uses this.items, this.taxRate */ }
}
class InvoiceGenerator {
generate(order: Order): Invoice { return new Invoice(order.calculateTotal()) }
}检测方式: 统计外部引用与自身引用的数量。如果外部引用更多?则存在特性依恋。
Dependency Inversion Principle
依赖倒置原则
Don't instantiate dependencies inside methods. Inject them.
typescript
// ❌ TIGHT COUPLING
class OrderProcessor {
process(order: Order): void {
const validator = new OrderValidator() // Hard to test/change
const emailer = new EmailService() // Hidden dependency
}
}
// ✅ LOOSE COUPLING
class OrderProcessor {
constructor(private validator: OrderValidator, private emailer: EmailService) {}
process(order: Order): void {
this.validator.isValid(order) // Injected, mockable
this.emailer.send(...) // Explicit dependency
}
}Scan for: inside methods, static method calls. Extract to constructor.
new X()禁止在方法内部实例化依赖。应注入依赖。
typescript
// ❌ TIGHT COUPLING
class OrderProcessor {
process(order: Order): void {
const validator = new OrderValidator() // Hard to test/change
const emailer = new EmailService() // Hidden dependency
}
}
// ✅ LOOSE COUPLING
class OrderProcessor {
constructor(private validator: OrderValidator, private emailer: EmailService) {}
process(order: Order): void {
this.validator.isValid(order) // Injected, mockable
this.emailer.send(...) // Explicit dependency
}
}检查要点: 方法内部的 调用、静态方法调用。将其提取至构造函数中。
new X()Fail-Fast Error Handling
快速失败错误处理
NEVER use fallback chains:
typescript
value ?? backup ?? default ?? 'unknown' // ❌Validate and throw clear errors instead:
typescript
// ❌ SILENT FAILURE - hides problems
return content.eventType ?? content.className ?? 'Unknown'
// ✅ FAIL FAST - immediate, debuggable
if (!content.eventType) {
throw new Error(`Expected 'eventType', got undefined. Keys: [${Object.keys(content)}]`)
}
return content.eventTypeError format:
Expected [X]. Got [Y]. Context: [debugging info]禁止使用回退链:
typescript
value ?? backup ?? default ?? 'unknown' // ❌应进行验证并抛出清晰的错误:
typescript
// ❌ SILENT FAILURE - hides problems
return content.eventType ?? content.className ?? 'Unknown'
// ✅ FAIL FAST - immediate, debuggable
if (!content.eventType) {
throw new Error(`Expected 'eventType', got undefined. Keys: [${Object.keys(content)}]`)
}
return content.eventType错误格式:
Expected [X]. Got [Y]. Context: [调试信息]Naming Conventions
命名规范
Principle: Use business domain terminology and intention-revealing names. Never use generic programmer jargon.
原则: 使用业务领域术语与表意明确的名称。禁止使用通用的程序员行话。
Forbidden Generic Names
禁用的通用名称
NEVER use these names:
datautilshelperscommonsharedmanagerhandlerprocessor
These names are meaningless - they tell you nothing about what the code actually does.
禁止使用以下名称:
datautilshelperscommonsharedmanagerhandlerprocessor
这些名称毫无意义——它们无法告诉你代码的实际功能。
Intention-Revealing Names
表意明确的名称
Instead of generic names, use specific domain language:
typescript
// ❌ GENERIC - meaningless
class DataProcessor {
processData(data: any): any {
const utils = new DataUtils()
return utils.transform(data)
}
}
// ✓ INTENTION-REVEALING - clear purpose
class OrderTotalCalculator {
calculateTotal(order: Order): Money {
return taxCalculator.applyTax(order.subtotal, order.taxRate)
}
}应使用具体的领域语言替代通用名称:
typescript
// ❌ GENERIC - meaningless
class DataProcessor {
processData(data: any): any {
const utils = new DataUtils()
return utils.transform(data)
}
}
// ✓ INTENTION-REVEALING - clear purpose
class OrderTotalCalculator {
calculateTotal(order: Order): Money {
return taxCalculator.applyTax(order.subtotal, order.taxRate)
}
}Naming Checklist
命名检查清单
For classes:
- Does the name reveal what the class is responsible for?
- Is it a noun (or noun phrase) from the domain?
- Would a domain expert recognize this term?
For methods:
- Does the name reveal what the method does?
- Is it a verb (or verb phrase)?
- Does it describe the business operation?
For variables:
- Does the name reveal what the variable contains?
- Is it specific to this context?
- Could someone understand it without reading the code?
对于类:
- 名称是否明确了类的职责?
- 是否是领域中的名词(或名词短语)?
- 领域专家是否能识别该术语?
对于方法:
- 名称是否明确了方法的功能?
- 是否是动词(或动词短语)?
- 是否描述了业务操作?
对于变量:
- 名称是否明确了变量的内容?
- 是否针对当前上下文具有特异性?
- 无需阅读代码就能理解其含义吗?
Refactoring Generic Names
重构通用名称
When you encounter generic names:
- Understand the purpose: What is this really doing?
- Ask domain experts: What would they call this?
- Extract domain concept: Is there a domain term for this?
- Rename comprehensively: Update all references
当遇到通用名称时:
- 明确用途:它实际在做什么?
- 咨询领域专家:他们会如何称呼这个概念?
- 提炼领域概念:是否存在对应的领域术语?
- 全面重命名:更新所有引用
Type-Driven Design
类型驱动设计
Principle: Follow Scott Wlaschin's type-driven approach to domain modeling. Express domain concepts using the type system.
原则: 遵循Scott Wlaschin的类型驱动领域建模方法。使用类型系统表达领域概念。
Make Illegal States Unrepresentable
让非法状态无法被表示
Use types to encode business rules:
typescript
// ❌ PRIMITIVE OBSESSION - illegal states possible
interface Order {
status: string // Could be any string
shippedDate: Date | null // Could be set when status != 'shipped'
}
// ✓ TYPE-SAFE - illegal states impossible
type UnconfirmedOrder = { type: 'unconfirmed', items: Item[] }
type ConfirmedOrder = { type: 'confirmed', items: Item[], confirmationNumber: string }
type ShippedOrder = { type: 'shipped', items: Item[], confirmationNumber: string, shippedDate: Date }
type Order = UnconfirmedOrder | ConfirmedOrder | ShippedOrder使用类型编码业务规则:
typescript
// ❌ PRIMITIVE OBSESSION - illegal states possible
interface Order {
status: string // Could be any string
shippedDate: Date | null // Could be set when status != 'shipped'
}
// ✓ TYPE-SAFE - illegal states impossible
type UnconfirmedOrder = { type: 'unconfirmed', items: Item[] }
type ConfirmedOrder = { type: 'confirmed', items: Item[], confirmationNumber: string }
type ShippedOrder = { type: 'shipped', items: Item[], confirmationNumber: string, shippedDate: Date }
type Order = UnconfirmedOrder | ConfirmedOrder | ShippedOrderAvoid Type Escape Hatches
避免类型逃逸机制
STRICTLY FORBIDDEN without explicit user approval:
- type
any - type assertions (
as,as unknown as,as any)as SomeType - /
@ts-ignore@ts-expect-error
There is always a better type-safe solution. These make code unsafe and defeat TypeScript's purpose.
未经明确批准,严格禁止使用:
- 类型
any - 类型断言(
as、as unknown as、as any)as SomeType - /
@ts-ignore@ts-expect-error
永远存在更优的类型安全解决方案。这些机制会导致代码不安全,违背TypeScript的设计初衷。
Use the Type System for Validation
使用类型系统进行验证
typescript
// ✓ TYPE-SAFE - validates at compile time
type PositiveNumber = number & { __brand: 'positive' }
function createPositive(value: number): PositiveNumber {
if (value <= 0) {
throw new Error(`Expected positive number, got ${value}`)
}
return value as PositiveNumber
}
// Can only be called with validated positive numbers
function calculateDiscount(price: PositiveNumber, rate: number): Money {
// price is guaranteed positive by type system
}typescript
// ✓ TYPE-SAFE - validates at compile time
type PositiveNumber = number & { __brand: 'positive' }
function createPositive(value: number): PositiveNumber {
if (value <= 0) {
throw new Error(`Expected positive number, got ${value}`)
}
return value as PositiveNumber
}
// Can only be called with validated positive numbers
function calculateDiscount(price: PositiveNumber, rate: number): Money {
// price is guaranteed positive by type system
}Prefer Immutability
优先使用不可变性
Principle: Default to immutable data. Mutation is a source of bugs—unexpected changes, race conditions, and difficult debugging.
原则: 默认使用不可变数据。可变状态是bug的源头——意外变更、竞态条件以及难以调试的问题。
The Problem: Mutable State
问题:可变状态
typescript
// MUTABLE - hard to reason about
function processOrder(order: Order): void {
order.status = 'processing' // Mutates input!
order.items.push(freeGift) // Side effect!
}
// Caller has no idea their object changed
const myOrder = getOrder()
processOrder(myOrder)
// myOrder is now different - surprise!typescript
// MUTABLE - hard to reason about
function processOrder(order: Order): void {
order.status = 'processing' // Mutates input!
order.items.push(freeGift) // Side effect!
}
// Caller has no idea their object changed
const myOrder = getOrder()
processOrder(myOrder)
// myOrder is now different - surprise!The Solution: Return New Values
解决方案:返回新值
typescript
// IMMUTABLE - predictable
function processOrder(order: Order): Order {
return {
...order,
status: 'processing',
items: [...order.items, freeGift]
}
}
// Caller controls what happens
const myOrder = getOrder()
const processedOrder = processOrder(myOrder)
// myOrder unchanged, processedOrder is newtypescript
// IMMUTABLE - predictable
function processOrder(order: Order): Order {
return {
...order,
status: 'processing',
items: [...order.items, freeGift]
}
}
// Caller controls what happens
const myOrder = getOrder()
const processedOrder = processOrder(myOrder)
// myOrder unchanged, processedOrder is newApplication Rules
应用规则
- Prefer over
constlet - Prefer spread () over mutation
... - Prefer /
map/filteroverreducewith mutationforEach - If you must mutate, make it explicit and contained
- 优先使用 而非
constlet - 优先使用扩展运算符()而非变更
... - 优先使用 /
map/filter而非带变更的reduceforEach - 如果必须变更,需明确且局限在局部范围内
YAGNI - You Aren't Gonna Need It
YAGNI - 你不会需要它
Principle: Don't build features until they're actually needed. Speculative code is waste—it costs time to write, time to maintain, and is often wrong when requirements become clear.
原则: 不要在实际需要之前构建功能。投机性代码是浪费——编写它需要时间,维护它也需要时间,而且当需求明确时,它往往是错误的。
The Problem: Speculative Generalization
问题:投机性泛化
typescript
// YAGNI VIOLATION - over-engineered for "future" needs
interface PaymentProcessor {
process(payment: Payment): Result
refund(payment: Payment): Result
partialRefund(payment: Payment, amount: Money): Result
schedulePayment(payment: Payment, date: Date): Result
recurringPayment(payment: Payment, schedule: Schedule): Result
// ... 10 more methods "we might need"
}
// Only ONE method is actually used todaytypescript
// YAGNI VIOLATION - over-engineered for "future" needs
interface PaymentProcessor {
process(payment: Payment): Result
refund(payment: Payment): Result
partialRefund(payment: Payment, amount: Money): Result
schedulePayment(payment: Payment, date: Date): Result
recurringPayment(payment: Payment, schedule: Schedule): Result
// ... 10 more methods "we might need"
}
// Only ONE method is actually used todayApplication Rules
应用规则
- Build the simplest thing that works
- Add capabilities when requirements demand them, not before
- "But we might need it" is not a requirement
- 构建最简单的可行方案
- 仅在需求明确要求时添加功能,而非提前添加
- “但我们可能需要它”不是合理的需求
When Tempted to Cut Corners
当你想走捷径时
STOP if you're about to:
- Use chains → fail fast with clear error instead
?? - Use or
any→ fix the types, not the symptomsas - Use inside a method → inject through constructor
new X() - Name something ,
data,utils→ use domain languagehandler - Add a getter → ask if the object should do the work instead
- Skip refactor because "it works" → refactor IS part of the work
- Write a comment → make the code self-explanatory
- Mutate a parameter → return a new value
- Build "for later" → build what you need now
如果你即将做以下事情,请立即停止:
- 使用 链 → 改为快速失败并抛出清晰错误
?? - 使用 或
any→ 修复类型,而非掩盖问题as - 在方法内部使用 → 通过构造函数注入
new X() - 将某个对象命名为 、
data、utils→ 使用领域语言命名handler - 添加getter → 思考是否应该让对象自行完成该操作
- 因为“代码能运行”而跳过重构 → 重构本身就是工作的一部分
- 编写注释 → 让代码自我解释
- 修改参数 → 返回新值
- 为“以后”构建功能 → 只构建当前需要的功能