Engineering

让代码结构随真实复杂度演化

成熟的工程结构不是一开始就拆得很细,而是在真实复杂度出现后,用刚好的抽象降低理解和修改成本。

让代码结构随真实复杂度演化

最新判断

更成熟的工程思维不是一开始就设计出完整架构,而是先让局部保持清晰,再根据真实出现的复杂度逐步演化。

不要在业务还没有稳定时,就把代码预先拆成:

types.ts
constants.ts
utils.ts
hooks.ts
services.ts
adapters.ts
models.ts
schemas.ts

这种结构看起来很工程化,却可能提前引入文件跳转、抽象层和维护成本。此时还不知道哪些边界会稳定,拆出来的往往只是想象中的结构。

更合适的演化顺序是:

  1. 先写在一起,让当前逻辑跑通并且容易读。
  2. 发现重复,再抽函数。
  3. 发现类型被复用,再抽共享类型。
  4. 发现底层资源具有独立生命周期,再抽资源 Hook。
  5. 发现业务流程需要协调多个资源,再抽组合 Hook。
  6. 发现模块边界已经稳定,再拆目录。

核心不是拒绝抽象,而是不要为想象中的复杂度提前付成本。等真实复杂度出现,再用抽象消化它。

类型先放在使用它的地方

如果 TtsFormat 只服务于 useTtsPlayback.ts,直接放在文件顶部最清晰:

type TtsFormat = {
  sampleRate: number
  channels: number
}

export function useTtsPlayback() {
  // ...
}

这样读当前 Hook 时就能直接看到它依赖的数据结构,不需要跳转到另一个文件。

内部状态、内部参数、临时辅助结构和组件专用 Props,也适合先留在当前文件:

type PlaybackState = 'idle' | 'playing' | 'paused'

type AudioQueueItem = {
  id: string
  buffer: ArrayBuffer
}

useVoiceSession.tsuseVoiceWebSocket.ts 或解码模块也开始依赖 TtsFormat 时,再把它移动到 features/voice/types.ts。这时抽离不是为了形式统一,而是因为它已经成为模块内共享概念。

函数和 Hook 也按同样方式演化

消息处理逻辑可以先留在 useVoiceSession 内:

function handleMessage(message: BackendMessage) {
  // ...
}

当逻辑变复杂时,先抽成语义明确的 handleBackendMessage();当它进一步被多个模块复用,或者能够脱离当前 Hook 独立测试时,再移动到单独文件。

目录结构也不需要一步到位。早期可以只有:

features/voice/
  VoiceInterviewPage.tsx
  useVoiceSession.ts

当资源管理、业务编排和共享类型真正出现后,再演化为:

features/voice/
  components/
  hooks/
  types.ts
  utils/
  services/

抽象应当解释已经出现的复杂度,而不是预测所有未来可能出现的复杂度。

可以提前拆出的内容

不是所有东西都必须等到重复后再拆。系统边界上的稳定契约,一开始就值得被显式表达,例如:

  • API 协议类型
  • 前后端消息类型
  • 数据库模型
  • 跨模块共享常量
  • 已经稳定的领域模型
export type BackendMessage =
  | { type: 'asr.partial'; text: string }
  | { type: 'tts.started'; format: TtsFormat }
  | { type: 'tts.audio'; audio: ArrayBuffer }
  | { type: 'tts.ended' }

它们不是某个函数或 Hook 的内部细节,而是模块之间或系统之间的契约。提前集中定义,可以让边界更明确,也能减少协议漂移。

当前可执行标准

  • 只在当前文件使用:留在当前文件。
  • 重复两次:先观察,不急着统一。
  • 重复三次:开始判断是否存在稳定共性。
  • 跨模块共享:抽到共享位置。
  • 系统边界契约:可以提前抽离。

这些数字不是机械规则。真正要判断的是:抽离后是否减少了理解和修改成本,还是只增加了跳转。

好的工程结构不是“拆得足够多”,而是当前代码容易读、容易改,边界没有被过度设计,同时未来仍能自然拆分。每一步只让代码保持刚好清晰。

Back to Blog

Related Posts

View All Posts »