software-design-principles

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Software 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 (
value ?? backup ?? 'unknown'
). If data should exist, validate and throw a clear error.
🚨 Strive for maximum type-safety. No
any
. No
as
.
Type escape hatches defeat TypeScript's purpose. There's always a type-safe solution.
🚨 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
new SomeService()
inside methods. Pass dependencies through constructors.
🚨 Intention-revealing names only. Never use
data
,
utils
,
helpers
,
handler
,
processor
. Name things for what they do in the domain.
🚨 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'
)。如果数据理应存在,需进行验证并抛出清晰的错误。
🚨 追求极致类型安全。禁止使用
any
,禁止使用
as
。类型逃逸机制违背了TypeScript的设计初衷。永远存在类型安全的解决方案。
🚨 让非法状态无法被表示。使用可区分联合类型,而非可选字段。如果某种状态组合不应存在,就让类型系统禁止它。
🚨 注入依赖,而非实例化。禁止在方法内部使用
new SomeService()
。通过构造函数传递依赖。
🚨 仅使用表意明确的命名。禁止使用
data
utils
helpers
handler
processor
这类名称。要根据领域内的功能来命名。
🚨 禁止编写代码注释。注释是无法在代码中表达意图的失败体现。如果需要注释来解释代码的功能,说明代码不够清晰——请重构它。
🚨 使用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

九条规则

  1. One level of indentation per method
    • In practice, I will tolerate upto 3
  2. Don't use the ELSE keyword
    • Use early returns instead
  3. Wrap all primitives and strings
    • Create value objects
    • Encapsulate validation logic
    • Make domain concepts explicit
  4. First class collections
    • Classes with collections should contain nothing else
  5. One dot per line
  6. Don't abbreviate
    • Use full, descriptive names
  7. Keep all entities small
    • Small classes (< 150 lines)
    • Small methods (< 10 lines)
    • Small packages/modules
    • Easier to understand and maintain
  8. Avoid getters/setters/properties on entities
    • Tell, don't ask
    • Objects should do work, not expose data
  1. 每个方法仅允许一层缩进
    • 实际应用中,最多可容忍3层缩进
  2. 禁止使用ELSE关键字
    • 改用提前返回
  3. 包装所有原始类型与字符串
    • 创建值对象
    • 封装验证逻辑
    • 明确领域概念
  4. 一等集合
    • 包含集合的类不应包含其他内容
  5. 每行最多一个点运算符
  6. 禁止缩写
    • 使用完整、描述性的名称
  7. 保持所有实体小巧
    • 小型类(少于150行)
    • 小型方法(少于10行)
    • 小型包/模块
    • 更易于理解和维护
  8. 避免在实体中使用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:
new X()
inside methods, static method calls. Extract to constructor.
禁止在方法内部实例化依赖。应注入依赖。
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.eventType
Error 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:
  • data
  • utils
  • helpers
  • common
  • shared
  • manager
  • handler
  • processor
These names are meaningless - they tell you nothing about what the code actually does.
禁止使用以下名称:
  • data
  • utils
  • helpers
  • common
  • shared
  • manager
  • handler
  • processor
这些名称毫无意义——它们无法告诉你代码的实际功能。

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:
  1. Understand the purpose: What is this really doing?
  2. Ask domain experts: What would they call this?
  3. Extract domain concept: Is there a domain term for this?
  4. Rename comprehensively: Update all references
当遇到通用名称时:
  1. 明确用途:它实际在做什么?
  2. 咨询领域专家:他们会如何称呼这个概念?
  3. 提炼领域概念:是否存在对应的领域术语?
  4. 全面重命名:更新所有引用

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 | ShippedOrder

Avoid Type Escape Hatches

避免类型逃逸机制

STRICTLY FORBIDDEN without explicit user approval:
  • any
    type
  • as
    type assertions (
    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 new
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 new

Application Rules

应用规则

  • Prefer
    const
    over
    let
  • Prefer spread (
    ...
    ) over mutation
  • Prefer
    map
    /
    filter
    /
    reduce
    over
    forEach
    with mutation
  • If you must mutate, make it explicit and contained
  • 优先使用
    const
    而非
    let
  • 优先使用扩展运算符(
    ...
    )而非变更
  • 优先使用
    map
    /
    filter
    /
    reduce
    而非带变更的
    forEach
  • 如果必须变更,需明确且局限在局部范围内

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 today
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 today

Application 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
    any
    or
    as
    → fix the types, not the symptoms
  • Use
    new X()
    inside a method → inject through constructor
  • Name something
    data
    ,
    utils
    ,
    handler
    → use domain language
  • 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 → 思考是否应该让对象自行完成该操作
  • 因为“代码能运行”而跳过重构 → 重构本身就是工作的一部分
  • 编写注释 → 让代码自我解释
  • 修改参数 → 返回新值
  • 为“以后”构建功能 → 只构建当前需要的功能