low-level-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Low-level design

低层设计

A low-level design decides which modules exist, what state and rules each one owns, how they call each other, and where each file sits. A good one lets a newcomer find what they came for (where the program starts, how to add an endpoint, where the shared logic lives, what a change touches) without reading everything. Produce and present it through the delivery framework in every session, whether you are designing, explaining or redesigning, so the user always finds the same things in the same places. The user chooses what the session is for; the framework sets how you lay out the answer. Stop once the user agrees to the design, since building it is done later, from tickets.
The recurring failure is structure added before it is needed: factories with one product, interfaces with one implementation, a file per function. Start from the simplest design that meets the requirements, and add a class, an interface or a pattern only for a named reason, such as a second implementation today or the same edit repeated across several files. Before designing a module, check whether a dependency the project already has provides it, and build only the difference.
低层设计决定有哪些模块、每个模块负责维护哪些状态和规则、模块之间如何调用,以及每个文件应放在何处。优秀的低层设计能让新来者无需通读所有内容,就能找到自己所需的信息(程序从哪里启动、如何添加 endpoint、共享逻辑位于何处、一次变更会触及什么)。无论是在设计、解释还是重构,每次会话都应通过交付框架产出并呈现设计,让用户始终能在相同位置找到相同内容。用户决定本次会话的目的;框架则规定答案的组织方式。一旦用户认可设计便停止,因为后续会依据 tickets 进行实现。
反复出现的失败,是在结构真正需要之前就提前加入:只有一个产品的 factory、只有一个实现的 interface、每个函数单独一个文件。应从满足需求的最简单设计出发,只有在存在明确且可命名的原因时才添加 class、interface 或 pattern,例如已经出现第二种实现,或同一处修改需要在多个文件中反复进行。设计某个模块前,先检查项目已有 dependency 是否已经提供该能力,只构建其中的差异。

The flow

流程

  1. Restate the session's purpose in one line, read from the request and its context, so a misreading costs one reply: designing something new, explaining existing code, redesigning it, deciding one thing, or a mix.
    • Invoked with no instructions inside a project: write down that project's current design.
    • When the purpose isn't clear: ask what the user wants from the session: a new design, an explanation of the existing code, or a change to it. Check yourself whether the code exists.
  2. Work through the delivery framework below, stage by stage, reading a principles, concepts or patterns reference when a decision calls for it.
  3. Present, take feedback and revise until the user agrees.
  4. Keep the design where the user wants it.
The purpose decides how the stages are walked. The cases below are common, not the only options: a session can mix them, move from one to another, such as an explanation turning into a redesign, or be something else, so fit the framework to what the user asked for.
  • Designing something new: a complete first draft through every stage, from the user's spec.
  • Explaining existing code: walk the code through the stages as it is, without changing it.
    • Build each stage from the evidence. Start from an existing design document and check it against the code, then the code itself: entry points, types, folders and tests. The user's description is evidence too. Cite the file behind each claim.
    • Mark requirements read from behaviour and tests inferred. Mark what the code can't show, such as why a choice was made or what is planned, unknown, and ask about it rather than filling it in.
    • Show what each file owns and, where a file mixes several things, what it mixes. Report weaknesses as observations without proposing changes, since this session leaves the code as it is.
    • Answer follow-up questions by tracing a call through the design.
  • Redesigning existing code: explain it first and get the user's agreement that it's right. Then redesign it through the same stages, changing only what the redesign needs: a small change runs only the stages it touches, a restructure runs them all. Show the before and after of every stage that changed, including the folder tree.
  • Deciding one thing, such as "should this be an interface?" or "where does this module go?": answer it with the part of the design it touches, the requirement behind it, the options with their trade-offs, and a recommendation.
  1. 用一行重述本次会话的目的,依据请求及其上下文判断,让一次误解只造成一次回复的代价。
    • **在项目中调用但未提供任何说明时:**记录该项目的当前设计。
    • **当目的不明确时:**询问用户希望本次会话达成什么:提供新设计、解释现有代码,还是修改现有代码。自行确认相关代码是否存在。
  2. 按照下方的交付框架逐阶段推进,并在某项决策需要时阅读原则、概念或模式参考资料。
  3. 呈现方案、收集反馈并修订,直到用户认可。
  4. 将设计保存在用户希望的位置。
目的决定各阶段的推进方式。以下是常见情形,但并非唯一选项:一次会话可以混合这些情形、在情形之间切换,例如从解释转为重构,也可以采用其他方式;应根据用户的要求调整框架。
  • **设计新内容:**根据用户规格,完成一份贯穿所有阶段的完整初稿。
  • **解释现有代码:**按照当前代码逐阶段讲解,不做修改。
    • 每个阶段都应基于证据构建。先从现有设计文档开始,将其与代码核对,然后再检查代码本身:入口点、类型、文件夹和测试。用户的描述同样属于证据。为每项结论引用对应文件。
    • 将从行为和测试中推断出的需求标记为 inferred。将代码无法说明的内容标记为 unknown,例如为何做出某项选择或后续有什么计划;应就此询问用户,而不是自行补全。
    • 说明每个文件负责什么;如果一个文件混合了多项职责,指出它混合了哪些内容。仅将弱点作为观察结果报告,不主动提出修改方案,因为本次会话不改动代码。
    • 回答后续问题时,在设计中追踪相应调用链。
  • **重构现有代码:**先解释现有设计并获得用户对其正确性的认可。然后通过相同阶段进行重构,只改动重构所需的部分:小范围变更仅运行受影响的阶段,结构性重组则运行全部阶段。展示每个发生变化阶段的前后对比,包括文件夹树。
  • **决定单个事项,**例如“这应该是一个 interface 吗?”或“这个模块应放在哪里?”:回答时说明涉及的设计部分、背后需求、各项选项及其权衡,并给出建议。

The delivery framework

交付框架

text
Requirements → Entities and relationships → Class design → Implementation → Extensibility
Each stage rests on the one before it: the entities come from the requirements, the classes give the entities state and behaviour, the implementation proves the classes work, and extensibility tests them against what comes next. Move to the next stage once the current one has everything its product lists. Go back to an earlier stage when a later one exposes a gap. Avoid two opposite failures: writing code before the structure is clear, and polishing details so long that the design never comes together.
text
Requirements → Entities and relationships → Class design → Implementation → Extensibility
每个阶段都以前一阶段为基础:实体来自需求,类为实体赋予状态和行为,实现验证类能够工作,可扩展性则依据后续变化检验它们。当前阶段具备其产出清单中的全部内容后,再进入下一阶段。如果后续阶段暴露出缺口,就返回较早阶段。避免两种相反的失败:在结构尚未清晰前编写代码,以及过度打磨细节,致使设计始终无法成型。

Requirements

需求

Turn the request or spec into something to design against, working down four themes:
  • Capabilities: the operations the system must support.
  • Rules and completion: what defines success and failure, and when the system stops or changes state.
  • Error handling: how it responds to invalid input and illegal actions.
  • Scope: what is in (core logic, business rules), and what is explicitly out (UI, storage, concurrency, whatever the user excludes). The exclusions keep the design from growing features nobody asked for.
Show them as a short numbered list. A mapping from requirement to module fits as a small table once the modules exist.
将请求或规格转化为可据此开展设计的内容,围绕四个主题展开:
  • 能力:系统必须支持的操作。
  • 规则与完成条件:何为成功和失败,以及系统何时停止或改变状态。
  • 错误处理:系统如何响应无效输入和非法操作。
  • 范围:哪些内容包含在内(核心逻辑、业务规则),哪些内容明确排除在外(UI、存储、并发,以及用户排除的其他事项)。这些排除项可防止设计不断增加无人要求的功能。
以简短编号列表展示。模块确定后,可以用一张小表格映射需求与模块。

Entities and relationships

实体与关系

Take the nouns from the requirements. A noun that holds changing state or enforces rules is an entity; one that is only information attached to another is a field on it. This keeps the design from splitting into many tiny objects.
Then settle how they relate: which entity is the orchestrator, which own durable state, which has, uses or contains which, and where each rule lives.
Show the entities as a short list and the relationships as arrows (
Game -> Board
). Boxes and arrows are enough; skip formal UML.
从需求中提取名词。持有可变状态或负责执行规则的名词是实体;仅作为附加信息挂在另一个名词上的内容则是它的字段。这样可以避免把设计拆成许多过小的对象。
随后确定实体之间的关系:哪个实体是 orchestrator,哪个实体持有持久状态,谁拥有、使用或包含谁,以及每条规则位于何处。
将实体展示为简短列表,将关系展示为箭头(
Game -> Board
)。方框和箭头已经足够,无需使用正式的 UML。

Class design

类设计

Turn each entity into a class or module, top-down from the orchestrator, and derive its state and behaviour from the requirements rather than from intuition:
  • State: what it must remember to enforce the requirements it owns.
  • Behaviour: what callers must be able to ask or tell it, each operation with what it returns and rejects, in a small API where each method matches a real action or question.
Keep each rule with the module that owns its state (tell, don't ask): lifecycle rules ("can this run now?") belong to the orchestrator, data rules ("is this cell taken?") to the module holding the data, so when something breaks you know which module to open. Place the modules in folders following layout.md.
Show the folder tree with one comment per line saying what each file or folder owns.
将每个实体转化为 class 或 module,从 orchestrator 开始自顶向下处理,并依据需求而非直觉推导其状态和行为:
  • 状态:为执行其负责的需求,必须记住什么。
  • 行为:调用方必须能够询问或指示它做什么;每项操作都应在一个小型 API 中说明其返回值和拒绝条件,并且每个方法都对应真实的动作或问题。
将每条规则与拥有相应状态的模块放在一起(tell, don't ask):生命周期规则(“现在可以运行吗?”)属于 orchestrator,数据规则(“这个格子是否已被占用?”)属于持有数据的模块。这样一旦出现问题,就知道应打开哪个模块。按照 layout.md 放置模块。
展示文件夹树,每行用一条注释说明每个文件或文件夹负责什么。

Implementation

实现

Sketch the methods that carry the logic, not every method: the happy path first (the steps, the calls into other modules, the state changed), then the edge cases (invalid input, illegal operations, calls in the wrong state). Write pseudocode unless the project needs real code.
Then verify: trace one concrete scenario step by step, with the state after each step, plus one rejection, and fix what the trace reveals, such as a state never set or a rule checked too late.
Show a traced flow as a call tree, with the owning file beside each call.
勾勒承载核心逻辑的方法,而不必列出每个方法:先写 happy path(步骤、对其他模块的调用、状态变更),再处理 edge cases(无效输入、非法操作、状态不正确时的调用)。除非项目确实需要真实代码,否则使用 pseudocode。
随后进行验证:逐步追踪一个具体场景,列出每一步之后的状态,再加上一条拒绝路径,并修复追踪所暴露的问题,例如某项状态从未设置,或某条规则检查得太晚。
以 call tree 展示追踪流程,并在每次调用旁标出所属文件。

Extensibility

可扩展性

List the changes likely to come next, and for each one name the part of the design that absorbs it and what changes. A design that routes every state change through one place takes a feature like undo without restructuring. A change that lands in one new file plus a registration is already absorbed; one that repeats the same edit across several files needs a seam. Refuse the changes the current requirements don't need, rather than designing for them.
Show the changes as a small "change → what you touch" table.
列出接下来可能发生的变更,并针对每项变更指出设计中哪一部分可以吸收它、需要改动什么。一个将所有状态变更都汇集到同一位置的设计,可以直接吸收 undo 等功能,而无需重构。若一项变更只需新增一个文件并完成注册,就已经被设计吸收;若同一处修改需要在多个文件中反复出现,则需要预留 seam。拒绝当前需求并不需要的变更,不要为它们预先设计。
用一张简短的“变更 → 需要修改的内容”表格展示这些变化。

Presenting and revising

呈现与修订

Load
/show-me
and present every stage visually, with prose only for the reasons behind choices.
  • Keep one current version of the design and revise it rather than starting over.
  • Open each revision with what changed and why, as a before/after
    diff
    of the tree or list that changed, and carry a change through every part it affects.
  • Leave a point the user settled as it is unless they reopen it.
  • When the design has more than one reasonable option, number the options side by side, each with its cost, and lead with your recommendation.
  • Ask only what the code, the spec and the project's docs can't settle, one question at a time. Decide the rest and say what you decided.
加载
/show-me
并以可视化方式呈现每个阶段,只用文字说明选择背后的理由。
  • 维护当前设计的一个版本并在其上修订,而不是每次从头开始。
  • 每次修订先说明改了什么以及为什么改,以发生变化的树或列表的前后
    diff
    呈现,并将变更同步到所有受影响部分。
  • 用户已经确定的事项保持不变,除非用户重新提出。
  • 当设计存在多个合理选项时,将选项并排编号,列出各自成本,并首先给出建议。
  • 只询问代码、规格和项目文档无法解决的问题,每次只问一个。其余内容自行决定,并说明所作决定。

Keeping the design

保存设计

The user decides what is kept and where. While the design is being refined, keep it in a draft in the project's temp or scratch folder, following the repository's conventions; when it has none, ask where. An explanation can stay in the conversation. Once the user agrees and wants the design kept, write it into the project's documentation, and update an existing design document rather than adding a second, so the project has one. Open it with what a newcomer needs first, then give each stage its own section.
由用户决定保留哪些内容以及保存在哪里。在设计打磨期间,按照仓库约定,将草稿放在项目的 temp 或 scratch 文件夹中;如果仓库没有相应约定,则询问用户。解释过程可以只保留在对话中。一旦用户认可设计并希望保留,应将其写入项目文档,并更新已有设计文档,而不是再新增第二份,从而确保项目中只有一份设计文档。文档开头先说明新来者最需要了解的内容,然后为每个阶段设置独立章节。

References

参考资料

Principles, concepts and patterns

原则、概念与模式

ReferenceCovers
design-principles.mdKISS, DRY, YAGNI, separation of concerns, Law of Demeter, SOLID
oop-concepts.mdEncapsulation, abstraction, polymorphism, inheritance and composition
design-patterns.mdCreational, structural and behavioural patterns, and when each is over-engineering
layout.mdFolders, generic and business code, naming, when to split a file

Source: Hello Interview, Introduction and Delivery Framework.
参考资料涵盖内容
design-principles.mdKISS、DRY、YAGNI、关注点分离、Law of Demeter、SOLID
oop-concepts.md封装、抽象、多态、继承与组合
design-patterns.md创建型、结构型和行为型模式,以及何时使用会构成过度设计
layout.md文件夹、通用代码与业务代码、命名,以及何时拆分文件

来源:Hello Interview,简介 和 交付框架。