关于 clojure-write
面向 Metabase 贡献者的 Clojure 开发技能,指导通过 REPL 编写和测试代码,并提供文档字符串、代码编辑和模块边界方面的项目规范。
主要功能
- 01优先使用可用的 clojure-mcp 工具
- 02通过 REPL 编写并测试函数
- 03编写面向调用者的文档字符串
- 04Clojure 编辑与模块边界检查
适用场景
- 01开发和测试小型 Clojure 函数
- 02编写或修改函数文档字符串
- 03按项目规范编辑代码
MCP 相关
该技能建议在可用时使用 `clojure_eval`、`clojure_edit` 等 clojure-mcp 工具;否则使用 `./bin/mage`。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ metabase/ metabase --skill clojure-write安装后在 Claude Code 中输入/clojure-write确认已加载。
详细介绍
Clojure 开发技能
工具偏好
当 clojure-mcp 工具可用时(例如 clojure_eval、clojure_edit),始终使用它们,
而不是像 ./bin/mage -repl 这样的 shell 命令。MCP 工具提供:
- 直接集成 REPL,没有 shell 转义问题
- 更好的错误信息和反馈
- 结构化 Clojure 编辑,防止语法错误
只有在 clojure-mcp 不可用时,才回退到 ./bin/mage 命令。
@./../_shared/development-workflow.md @./../_shared/clojure-style-guide.md @./../_shared/clojure-commands.md
REPL 驱动开发工作流
- 从小而基础的功能开始:
- 确定任务所需的核心功能或特性。
- 将每个特性拆分为可以独立开发和测试的最小、最基础的功能。
- 在 REPL 中编写并测试:
- 直接在 REPL(Read-Eval-Print Loop)中编写每个小功能的代码。
- 使用各种输入对其进行充分测试,包括典型用例和相关边界情况,以确保其 行为符合预期。
- 集成到源代码中:
- 一旦某个功能在 REPL 中正常工作,就将其从 REPL 环境移到你的源代码文件中(例如, 放入适当的命名空间)。
- 逐步增加复杂度:
- 在已测试的基础功能之上构建更复杂的功能或组件。
- 将较小的功能组合在一起,在 REPL 中测试每一个新的组合,逐步验证正确性。
- 确保依赖测试:
- 确保每个功能在被其他功能依赖之前,都已在 REPL 中经过充分测试。
- 这样可以确保应用程序的每一层在你在其之上构建之前都是可靠的。
- 充分利用 REPL:
- 将 REPL 作为你的主要工具,用来尝试不同方法、快速迭代,并即时 获得代码反馈。
- 遵循函数式编程原则:
- 保持功能小而专注、可组合。
- 使用 Clojure 的函数式编程特性——例如不可变性、高阶函数和标准 库——来编写简洁、有效的代码。
如何评估代码
自底向上的开发循环
- 将代码写入文件。
- 评估该文件的命名空间,并确保它能正确加载:
./bin/mage -repl --namespace metabase.app-db.connection
- 使用测试输入调用该命名空间中的功能,并观察输出是否正确
随时可以使用
deftest和is将这些 REPL 会话试验复制到实际测试用例中。 - 一旦你确认这些功能没问题,就回到第 1 步,将它们组合成你需要构建的任务。
编写文档字符串
文档字符串是面向调用方的契约,而不是实现者的日记。它说明函数做什么、接收什么、返回什么、抛出什么,以及调用方必须遵守的前置条件/不变式。这些保证和要求理应放在那里 —— 它们正是调用方需要在 IDE 中看到的内容。
当你发现文档字符串中包含实现上下文时,默认做法是将其迁移,而不是删除 —— 把它移到函数体中真正相关位置的inline comment。这些上下文往往确实有价值;只是放错了地方(调用方不应该读它;站在那一行的实现者才应该读它)。只有当它是废话时才直接删除:自我吹嘘、复述显而易见的内容,或记录一个本应默认具备的属性。
关于最后一种情况 —— 像"可在所有受支持的 appdb 间移植"这样的属性叙述不配占一句话。如果它不可移植,那要么是个 bug,要么意味着调用方必须自行处理每种情况 —— 而在那种情况下,需要记录的是该属性的缺失。记录与预期的偏差,而不是对预期的符合。
启发式方法 —— 只有当一句话同时通过以下两项测试时,它才配出现在文档字符串中:
-
重写测试(必要但不充分):如果用具有相同契约的不同实现重写函数体会使该句变为假,那么它描述的是实现,而不是契约。将它迁移到函数体中它适用的位置,作为inline comment。
-
归属测试:如果该句陈述的是关于此函数并不拥有的代码的事实 —— 被调方的行为、某个库的保证 —— 那么它在任何重写下都轻松成立,这正是仅靠重写测试不够的原因。如果此函数体依赖该事实,那它就是实现上下文:将它迁移到调用点。如果这里没有任何代码依赖它,那它就是关于别人代码的废话:删除它,让被调方自己的文档字符串来回答。
每个事实只说一次。重复的文字就是提前写好的过期注释:当事实发生变化时,一处被更新,其余各处悄悄腐烂。这甚至适用于你自己拥有的代码 —— 一个命名空间文档字符串反复总结自己的多重方法,三个同级函数带着关于重试行为的同一段文字。给每个事实一个归宿,其他地方提及它即可,而不是复述它。
多行文档字符串并非被禁止 —— 代码不得不应对的真正不显而易见的约束可能值得解释。但要谨慎;失败模式是细节过多。当你想写多段式解释性文档字符串时,先与用户确认。并且宁可用测试而非文字:如果未来的读者认为"这是个愚蠢的做法"并改了它,测试应该失败并告诉他们原因。如果这种破坏反复发生,那才是需要注释的信号。
编辑的关键规则
- 编辑 Clojure 代码时要小心括号计数
- 每次修改 Clojure 代码后,用
-check-readable验证可读性 - 所有文件以换行符结尾
- 编辑表格化代码时,若各列对齐,尽量保持对齐
- 行末没有内容时不允许有空格
- 更改模块边界后(添加/删除/重命名
src命名空间、跨模块require或:model/X引用,或新增模块),运行./bin/mage fix-modules-config以重新生成.clj-kondo/config/modules/config.edn并保持metabase.core.modules-test通过。若无偏差则为无操作; 参见项目CLAUDE.md中的"Module Boundaries"。