自己动手写一个 Claude Code Skill——从一个真实需求开始
自己动手写一个 Claude Code Skill——从一个真实需求开始
适读人群:用 Claude Code 做日常开发、想把重复工作自动化的工程师 | 阅读时长:约15分钟 | 核心价值:完整记录一个自定义 skill 从需求到可用的全过程,有真实的 skill 代码
我有一个毛病:写代码很快,但 Code Review 做得不认真。
不是不知道 Code Review 重要,是每次自己 review 自己的代码,都有一种盲区——你知道自己写的东西是干什么的,所以你下意识跳过了很多"理所当然"的地方,而这些地方恰恰是别人 review 时会发现问题的。
我试过在 Claude Code 里直接说"帮我 review 这段代码",效果不稳定。有时候给出很有价值的问题,有时候只是说"这段代码看起来不错,有几个小建议",然后给一堆无关痛痒的格式建议。
差异在哪?在于我没给 Claude 一个明确的 review 框架,每次它自己决定要从什么角度 review,结果每次不一样。
这就是我决定做一个自定义 Code Review skill 的原因。
需求分析:我想要什么样的 Review
在动手之前,我先想清楚了这个 skill 要干什么。
我做 Java 后端开发,review 的重点和前端或者算法开发不一样。我关心的核心问题有这几类:
一、正确性
- 边界条件处理:null 值、空列表、越界
- 并发安全:是否有竞态条件、共享状态是否被正确保护
- 事务边界:数据库操作的事务是否正确使用
二、性能隐患
- N+1 查询
- 不必要的循环嵌套
- 大对象的内存使用
三、可维护性
- 方法职责是否单一(一个方法做太多事)
- 命名是否准确(名字和实际行为是否一致)
- 魔法数字和硬编码
四、安全性
- SQL 注入风险(虽然用了 MyBatis,但还是要确认参数传递方式)
- 权限校验是否遗漏
- 敏感信息是否泄露到日志
我之前让 Claude review 的时候,它有时候只关注一两个方面,有时候四个方面混在一起说,没有结构。我想要的是:每次 review 都覆盖这四个维度,分维度输出,不遗漏。
Skill 设计
明确需求之后,开始设计 skill 文件。
Claude Code 的 skill 文件格式是 Markdown,通过 CLAUDE.md 加载,或者用 skill 文件格式单独存放。我选择单独写成一个 skill 文件,通过 /plugins 安装。
先写一个草稿版本,然后测试,然后迭代。
第一版 skill 代码
---
name: java-code-review
description: Java 后端代码 Review,覆盖正确性、性能、可维护性、安全性四个维度
triggers:
- "review 这段代码"
- "帮我 review"
- "code review"
- "代码审查"
- "帮我检查代码"
- "这段代码有没有问题"
---
# Java Code Review 规范
当用户请求代码 Review 时,按照以下框架进行系统性分析。
## 输出格式要求
使用以下固定结构输出 Review 结果:Code Review 报告
总体评估
[一句话总结:代码质量整体如何,有无阻塞性问题]
正确性
[严重] 或 [建议] 标注每个问题
- 问题描述
- 具体代码位置(行号或方法名)
- 修复建议(附代码示例)
性能隐患
(同上格式)
可维护性
(同上格式)
安全性
(同上格式)
综合建议
[如果各维度都有问题,给出一个优先级排序]
## 各维度检查清单
### 正确性检查
- [ ] null 值处理:方法参数、集合元素、数据库查询结果
- [ ] 边界条件:空集合遍历、数组越界、字符串空值
- [ ] 并发安全:静态变量、共享实例变量、集合类型选择
- [ ] 异常处理:是否捕获了不该捕获的异常、异常信息是否有意义
- [ ] 事务边界:@Transactional 的使用是否正确,传播行为是否合适
### 性能检查
- [ ] N+1 查询:循环中是否有数据库查询
- [ ] 大查询:是否在没有条件的情况下查询大表
- [ ] 对象创建:是否在循环中创建大量临时对象
- [ ] 字符串拼接:循环中是否用 + 拼接字符串
- [ ] 集合操作:是否频繁扩容或有不必要的类型转换
### 可维护性检查
- [ ] 方法长度:超过 50 行的方法检查是否可以拆分
- [ ] 方法职责:方法名和实际行为是否一致
- [ ] 魔法数字:是否有未命名的数字或字符串常量
- [ ] 注释质量:注释是否说明了"为什么"而非"是什么"
- [ ] 重复代码:是否有可以抽取为公共方法的重复逻辑
### 安全性检查
- [ ] 参数校验:接口入参是否经过校验
- [ ] 权限校验:敏感操作是否有权限检查
- [ ] SQL 安全:MyBatis 的 ${} 使用是否必要(可能的 SQL 注入)
- [ ] 日志安全:是否有密码、token、手机号等敏感信息写入日志
- [ ] 异常信息:error response 是否暴露了内部实现细节
## 注意事项
- 标注 **[严重]** 的问题:影响正确性或有安全漏洞,必须修复
- 标注 **[建议]** 的问题:影响质量但不阻塞功能,可选修复
- 如果用户没有提供足够的上下文(比如缺少相关类的定义),主动说明哪些检查因为信息不足无法完成
- 给出修复建议时,提供具体的代码示例而非泛泛而谈测试:拿一段真实代码跑一下
装上 skill 之后,我拿了一段真实项目里的代码测试。是一个订单服务里的方法:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Autowired
private UserMapper userMapper;
public List<OrderVO> getUserOrders(Long userId, String status) {
List<Order> orders = orderMapper.selectByUserId(userId);
List<OrderVO> result = new ArrayList<>();
for (Order order : orders) {
if (status != null && !order.getStatus().equals(status)) {
continue;
}
OrderVO vo = new OrderVO();
vo.setOrderId(order.getId());
vo.setStatus(order.getStatus());
// 查产品信息
Product product = productMapper.selectById(order.getProductId());
vo.setProductName(product.getName());
vo.setProductImage(product.getImageUrl());
// 查用户信息
User user = userMapper.selectById(userId);
vo.setUserName(user.getName());
result.add(vo);
}
return result;
}
}Review 结果(节选):
## Code Review 报告
### 总体评估
代码存在严重的性能问题(N+1 查询)和两处潜在的 NPE 风险,需要修复后上线。
### 正确性
**[严重]** NPE 风险 #1:productMapper.selectById() 返回值未做 null 检查
- 位置:`Product product = productMapper.selectById(order.getProductId());`
- 如果商品已下架或被删除,selectById 返回 null,下一行 product.getName() 将抛出 NullPointerException
- 修复建议:
Product product = productMapper.selectById(order.getProductId());
if (product == null) {
vo.setProductName("商品已下架");
vo.setProductImage("");
} else {
vo.setProductName(product.getName());
vo.setProductImage(product.getImageUrl());
}
**[建议]** 字符串比较使用 equals 正确,但 status 的合法值没有枚举约束,调用方可以传任意字符串。
### 性能隐患
**[严重]** N+1 查询问题
- 位置:循环内的 productMapper.selectById() 和 userMapper.selectById()
- 如果用户有 100 条订单,这个方法会执行 1(查订单列表)+ 100(查产品)+ 100(查用户)= 201 次数据库查询
- userMapper.selectById(userId) 在每次循环都执行,但 userId 是固定的,这是完全不必要的重复查询
- 修复建议(分两步):
1. 将用户查询移到循环外
2. 对产品查询做批量:先收集所有 productId,一次性 selectBatchIds,然后在循环里从 Map 中取
**[建议]** 分页缺失:没有分页的情况下,如果用户订单量大,可能查出大量数据。这个 review 结果非常准确。N+1 查询确实是我写这段代码的时候没注意的,NPE 风险也是真实存在的。
迭代:发现的问题和修复
测了几次之后,发现了两个问题:
问题一:skill 有时候不会触发
我说"帮我看下这个方法",它没触发 review skill,直接普通回答了。触发词不够覆盖。
在 triggers 里加了更多变体:
- "帮我看下这段代码"
- "这段代码有什么问题"
- "review 一下"
- "看看有没有 bug"
问题二:对于很短的工具方法,review 报告显得过于重量级
一个 5 行的工具方法,它也生成了完整的四维度报告,大部分维度写"无问题",显得很冗余。
在 behavior 里加了一条:
如果代码量少于 20 行且逻辑简单,简化 Review 输出格式,只报告发现的问题,没有问题的维度直接跳过,不需要逐一列出"无问题"。
最终版本的效果
用了两周之后,这个 skill 已经是我日常开发流程的一部分了。
每次写完一个方法或者一段比较复杂的逻辑,跑一次 code review。捕获的问题里,有真实的 NPE 风险、有性能隐患,不是没用的建议。
最重要的是:review 是有结构的,四个维度每次都会检查,不会因为我今天心情好就跳过安全性检查。
这是单靠"让 Claude 随机 review"做不到的。
给想自己做 skill 的人
如果你也想做类似的自定义 skill,几点实际建议:
需求要从真实的痛点出发,不要为了做 skill 而做。
先把 skill 的核心行为用自然语言描述清楚,写成文档,然后再翻译成 skill 格式。顺序反了(先想 skill 格式,再想内容),容易做出来的东西空洞。
做完一版马上测试,不要等做"完整版"再测。早期测试帮你发现 skill 里的逻辑漏洞,晚测不如早测。
触发词要尽量覆盖你自然说话的方式,而不只是正式的命令词。
