mcpField
S
self-documenting-code

self-documenting-code

github.com/theswerd开发6152 次安装34 周前更新
代码仓库

关于 self-documenting-code

面向编码的技能,指导通过函数与数据结构让代码表达自身意图,并便于测试和维护。它区分可复用的语义函数与承载复杂流程的实用函数。

主要功能

  1. 01语义函数明确输入与输出
  2. 02实用函数组织复杂流程
  3. 03模型设计避免无效状态
  4. 04品牌类型区分领域标识符
  5. 05指导函数与模型的边界划分

适用场景

  1. 01将复杂流程拆分为具名步骤
  2. 02设计字段一致的数据模型
  3. 03避免标识符意外互换

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/theswerd/aicode --skill self-documenting-code

安装后在 Claude Code 中输入/self-documenting-code确认已加载。

详细介绍

代码应具备自说明性

如何将逻辑拆分为函数,以及如何设计这些函数传递的数据,决定了代码库能否经得起时间考验。

语义函数

语义函数是任何代码库的构建块。好的语义函数应尽可能精简,以优先保证正确性。语义函数应接收完成目标所需的所有输入,并直接返回所有必要的输出。语义函数可以封装其他语义函数,以描述期望的流程和用法;作为代码库的构建块,如果有在各处使用且定义良好的复杂流程,就用语义函数将其编码。

语义函数中的副作用通常不可取,除非副作用本身就是明确目标,因为语义函数应当可以安全地重复使用,无需了解其内部实现,只需相信它会完成其所述功能。如果逻辑复杂,且在大型流程中不易看出其作用,一个好的模式是将该流程拆分为一系列自我说明的语义函数:每个函数接收所需内容,返回下一步所需的数据,并且不做其他事情。好的语义函数示例包括 quadratic_formula(),以及 retry_with_exponential_backoff_and_run_y_in_between<Y: func, X: Func>(x: X, y: Y)。即使这些函数以后再也不会被使用,未来查阅代码的人类和 Agent 也会感谢这种信息索引方式。

语义函数周围不应需要注释,代码本身就应是对其功能的自我说明。理想情况下,语义函数应极易进行单元测试,因为好的语义函数定义明确。

实用函数

实用函数应作为一系列语义函数和独特逻辑的封装。它们是代码库中复杂的流程。在构建生产系统时,逻辑变得杂乱是很自然的,实用函数就是用于组织这些逻辑的。通常不应在多个地方使用这类函数;如果使用超过少数几个地方,可以考虑拆解其中明确的逻辑,并将其移入语义函数。例如 provision_new_workspace_for_github_repo(repo, user) 或 handle_user_signup_webhook()。测试实用函数属于集成测试的范畴,通常在测试整个应用功能时进行。随着时间推移,实用函数预计会彻底改变,从内部实现到功能皆是如此。为此,最好在它们上方添加文档注释。避免重述函数名或其显而易见的特征,而应指出意料之外的情况,例如“余额低于 10 时会提前失败”,或澄清函数名可能引发的其他误解。阅读文档注释时请谨慎看待:在函数内部工作的开发者可能忘了更新注释;如果你认为注释可能不正确,最好核实一下。

模型 数据的形状应该让错误状态不可能出现。如果一个模型允许实际使用中绝不该同时存在的字段组合,那它就没有尽到职责。每个可选字段都会让代码库的其他部分在每次访问这些数据时都必须作出判断,而每个类型宽松的字段都在邀请调用方传入看似正确、实则不然的值。模型强制保证正确性时,错误会在构造数据时暴露,而不是等到某个毫不相关的流程深处,假设终于崩溃时才出现。模型的名称应该足够精确,让你看到任意字段都能判断它是否属于这个模型——如果名称无法说明这一点,那这个模型就试图承担太多职责。当两个概念经常需要一起使用、但又彼此独立时,应当组合它们,而不是合并它们——例如,UserAndWorkspace { user: User, workspace: Workspace } 保留了两个模型的完整性,而不是把 workspace 的字段摊平到 user 中。像 UnverifiedEmail、PendingInvite 和 BillingAddress 这样的好名称,能清楚说明哪些字段属于它们。如果你在 BillingAddress 上看到 phone_number 字段,就知道出了问题。

形状相同的值可能代表完全不同的领域概念:{ id: "123" } 在一处可能是 DocumentReference,在另一处则可能是 MessagePointer;如果你的函数只接受 { id: String },代码就会毫无异议地接受其中任何一种。品牌类型通过将基本类型包装成不同的类型来解决这个问题,让编译器将它们视为彼此独立的类型:使用 DocumentId(UUID),而不是裸 UUID。采用品牌类型后,意外交换两个 ID 会变成语法错误,而不是一个悄无声息、直到深入三层代码后才浮现的错误。

问题会在哪里出现

当语义明确的函数为了方便而演变成实用型函数时,问题往往就会出现;代码库中依赖它的其他地方随后可能会做出原本无意为之的操作。要解决这个问题,创建函数时应明确命名:不要按函数做什么来命名,而要按它在哪里使用来命名。名称的性质应让其他程序员一眼看出其行为没有严格定义,不应依赖它精确完成某项内部任务;这样也能更轻松地调试由此引发的回归问题。

模型也会以同样的方式出问题,只是过程更缓慢。它们一开始职责明确,随后有人因为创建新模型更麻烦,就“再加一个”可选字段;另一个人也照做;最终,模型变成了一堆松散、彼此只有部分关联的数据,所有使用方都得猜测哪些字段实际有值,以及为什么有值。名称不再描述数据是什么,字段不再围绕单一概念保持一致,而每个涉及该模型的新功能都不得不处理它从未设计来表示的状态。当模型的字段不再与其名称相契合时,这就是应该把它拆分为那些被它耦合在一起的独立概念的信号。

评论

支持 Markdown