关于 low-level-design
用于规划或讲解代码库低层设计的 skill,涵盖模块、状态、职责、调用关系和文件布局。它以分阶段框架将设计对应到需求与证据,并避免引入不必要的结构。
主要功能
- 01从需求到扩展性的设计阶段
- 02梳理实体及关系
- 03定义模块状态与行为
- 04草拟并追踪实现流程
- 05将后续变更对应到设计部分
适用场景
- 01为新功能规划模块和文件
- 02讲解现有代码结构
- 03评估具体的代码设计决策
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yahyabedirhan/ skills --skill low-level-design安装后在 Claude Code 中输入/low-level-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/design-patterns.mddesign-principles.mdlayout.mdoop-concepts.md详细介绍
底层设计
底层设计决定有哪些模块、每个模块各自拥有哪些状态和规则、它们如何相互调用,以及每个文件放在哪里。好的设计能让新人不用通读所有内容,就能找到自己需要的东西:程序从哪里启动、如何添加端点、共享逻辑在哪里,以及某项变更会影响什么。在每次会话中,无论是设计、解释还是重新设计,都通过交付框架来产出和呈现内容,这样用户总能在相同的位置找到相同的内容。由用户决定会话的目的;框架决定答案的组织方式。用户同意设计后就停止,因为后续的实现会在之后通过工单完成。
常见的问题是在尚无必要时就添加结构:只有一个产品的工厂、只有一种实现的接口、每个函数单独一个文件。先从满足需求的最简单设计开始,只有在有明确理由时才添加类、接口或模式,例如今天就需要第二种实现,或者同一处修改需要在多个文件中重复进行。设计模块前,先检查项目现有依赖是否已提供所需功能,只构建缺少的部分。
若要从服务、数据存储、API 和规模等层面设计系统,请使用 /system-design。若要设计命令行界面的命令、标志、输出和帮助信息,请使用 /cli-design。
步骤
- 用一句话重述本次会话的目的,根据请求及其上下文来判断,避免因误解而多花一轮回复:设计新内容、解释现有代码、重新设计、决定一件事,或上述目的的组合。
- **在项目中未提供任何指示而启动:**记录该项目当前的设计。
- **目的不明确时:**询问用户希望本次会话做什么:设计新内容、解释现有代码,还是修改代码。同时自行确认代码是否已存在。
- 逐阶段按照下方的交付框架推进;遇到需要做决定时,阅读相关的原则、概念或模式参考资料。
- 呈现设计、收集反馈并修改,直到用户同意。
- 将设计保存在用户指定的位置。
会话的目的决定如何推进各个阶段。以下情况很常见,但并非仅有这些:一次会话可以混合多种情况,也可以从一种情况转到另一种情况,例如从解释转为重新设计;也可以属于其他情况。因此,应根据用户的需求灵活运用框架。
- **设计新内容:**根据用户的规格说明,从头到尾逐阶段完成一份完整的初稿。
- **解释现有代码:**按照代码的现状逐阶段梳理,不做任何修改。
- 根据证据构建每个阶段的内容。先从现有设计文档入手,并对照代码进行核实,再检查代码本身:入口点、类型、文件夹和测试。用户的描述也是证据。每项论断都要注明其依据文件。
- 将从行为和测试中推断出的需求标记为推断。将代码无法说明的内容,例如做出某项选择的原因或计划中的事项,标记为未知,并向用户询问,不要自行补充。
- 说明每个文件负责什么;如果一个文件混杂了多个职责,也要说明它混杂了什么。只陈述观察到的弱点,不提出修改建议,因为本次会话不会改变代码。
- 回答后续问题时,沿着设计追踪调用过程。
- **重新设计现有代码:**先解释现有设计,并请用户确认解释正确。然后按照相同阶段重新设计,只修改重新设计所需的部分:小改动只执行受影响的阶段,结构调整则执行全部阶段。对每个发生变化的阶段,都展示变更前后的内容,包括文件夹树。
- **决定一件事,**例如“这应该是一个接口吗?”或“这个模块应该放在哪里?”:根据其影响到的设计部分、背后的需求、各个选项及其权衡来回答,并给出建议。
交付框架
Requirements → Entities and relationships → Class design → Implementation → Extensibility
每个阶段都建立在前一阶段之上:实体源自需求,类赋予实体状态和行为,实现证明类能够正常工作,而可扩展性则检验它们能否应对接下来的变化。当前阶段满足其产物清单中的所有要求后,再进入下一阶段。如果后续阶段暴露了缺口,就回到较早的阶段。避免两种相反的失误:结构尚未明确就开始编写代码,以及过度打磨细节,导致设计始终无法成形。
需求
将请求或规格说明转化为设计依据,围绕以下四个主题展开:
- 功能:系统必须支持的操作。
- 规则与完成条件:成功和失败的判定依据,以及系统何时停止或改变状态。
- 错误处理:系统如何响应无效输入和非法操作。
- 范围:包含哪些内容,例如核心逻辑和业务规则;明确排除哪些内容,例如 UI、存储、并发,或用户排除的其他内容。明确排除项可以避免设计不断加入无人要求的功能。
用简短的编号列表列出这些内容。模块确定后,可以用一张小表格映射需求与模块。
实体与关系
从需求中提取名词。持有变化状态或负责执行规则的名词是实体;仅附属于其他实体的信息则是该实体的字段。这样可以避免设计拆分出大量细小对象。
然后确定它们之间的关系:哪个实体负责统筹,哪些实体持有持久状态,哪些实体拥有、使用或包含其他实体,以及每条规则归属何处。
用简短列表列出实体,并用箭头表示关系(Game -> Board)。方框和箭头就够了;无需使用正式的 UML。
类设计
从统筹者开始,自上而下将每个实体转化为类或模块,并依据需求而非直觉推导其状态和行为:
- 状态:模块为执行其负责的需求而必须记住的信息。
- 行为:调用者必须能够向它提出的问题或要求执行的操作。对每个操作,简要说明它的返回内容和拒绝条件,并设计一个小型 API,让每个方法都对应真实的操作或问题。
让每条规则归属于持有其状态的模块,这就是“告诉,不要询问”:生命周期规则,例如“现在可以运行吗?”,归统筹者负责;数据规则,例如“这个单元格被占用了吗?”,归持有数据的模块负责。这样出了问题时,你就知道该查看哪个模块。按照 layout.md 中的说明将模块放入文件夹。
列出文件夹树,并为每一行添加一条注释,说明对应文件或文件夹的职责。
实现
只梳理承载逻辑的方法,不必涵盖所有方法:先写正常流程,包括步骤、对其他模块的调用,以及它改变的状态;然后处理边缘情况:无效输入、非法操作,以及在错误状态下发起的调用。除非项目需要真正的代码,否则请写伪代码。
然后进行验证:逐步追踪一个具体场景,记录每步之后的状态,再加上一次拒绝操作,并修正追踪过程中暴露的问题,例如某个状态从未设置,或某条规则检查得太晚。
用调用树展示追踪流程,并在每次调用旁标明所属文件。
可扩展性
列出接下来可能发生的变更,并为每项变更指出由设计中的哪一部分承接,以及需要修改什么。让所有状态变更都经过同一个位置的设计,可以在不重构的情况下支持撤销等功能。如果某项变更只需新增一个文件并注册,就已被设计承接;如果同一处修改需要在多个文件中重复进行,就需要增加一个扩展点。对于当前需求不需要的变更,不要纳入设计。
用一张小型的“变更 → 修改内容”表格列出这些变更。
展示与修订
加载 /show-me,并以可视化方式呈现每个阶段;仅用文字说明做出选择的原因。
- 保留一个当前设计版本,在此基础上修订,而不是从头开始。
- 每次修订都先说明改了什么以及原因,并以
diff的形式展示变更前后树结构或列表的差异;同时将变更同步到所有受影响的部分。 - 用户已经确定的内容保持不变,除非用户重新提出。
- 如果设计有多个合理选项,将选项并列编号,并分别列出代价;首先给出你的建议。
- 只询问代码、规范和项目文档无法确定的问题,一次问一个。其余事项由你决定,并说明决定内容。
保存设计
由用户决定保留哪些内容,以及保存在哪里。设计完善期间,将其保存在项目的临时或草稿文件夹中,并遵循仓库的约定;如果没有约定,就询问保存位置。说明可以留在对话中。用户同意并希望保留设计后,将其写入项目文档;更新现有设计文档,而不是另建一份,以确保项目只有一份设计文档。文档开头先写新手最需要了解的内容,然后为每个阶段单独设置一个章节。
参考资料
原则、概念与模式
| 参考资料 | 涵盖内容 |
|---|---|
| design-principles.md | KISS、DRY、YAGNI、关注点分离、迪米特法则、SOLID |
| oop-concepts.md | 封装、抽象、多态、继承与组合 |
| design-patterns.md | 创建型、结构型和行为型模式,以及各自何时会构成过度设计 |
| layout.md | 文件夹、通用代码与业务代码、命名、何时拆分文件 |
来源:Hello Interview,Introduction 和 Delivery Framework。