写了个 Claude Code 插件:AI 写的 Java 违反阿里规约,就写不进文件

写 Java 的人大多装过阿里的 p3c 插件,IDE 里一写 Executors.newFixedThreadPool 就给你标黄。

但现在很多代码是 Agent 写的。它不看 IDE 的黄线,Executors、new Thread()、Integer == Integer、foreach 里 remove,这些规约明令禁止的写法都可能出现在它提交的文件里。把规约写进 CLAUDE.md 或者做成 Skill 能缓解,但那要靠模型自己记得去遵守。

所以我做了 p3c-guard:一个 Claude Code 插件,在文件真正写入之前拦一道。

效果

我让它用 Executors.newFixedThreadPool(4) 写一个订单服务,再用 == 比较两个 Integer。写入被拒绝,Agent 收到的是:

text 复制代码
p3c-guard:这次写入会给 OrderService.java 新增 2 处阿里 Java 规约违规,未写入。
- 第 8 行 [ThreadPoolCreationRule] 手动创建线程池,效果会更好哦。 改法:用 new ThreadPoolExecutor(...) 显式给出核心数、最大数、有界队列和带名字的 ThreadFactory;Spring 项目注入已配置的 ThreadPoolTaskExecutor。
- 第 10 行 [WrapperTypeEqualityRule] 应使用equals方法代替== 改法:用 Objects.equals(a, b) 比较包装类型。
行号是写入后文件里的行号。请修正后重新写入;文件原有的违规不在此列。

然后它自己换成 ThreadPoolExecutor 和 Objects.equals,第二次写入通过。

它是怎么做的

Claude Code 的 Mod 可以挂在工具调用上,在调用真正执行之前返回一个拒绝。核心逻辑是这样的(简化过,完整代码在仓库的 hooks/register.ts):

ts 复制代码
on('tool.call', { tool: 'Write' }, async ($, e, next) => {
  if (!e.file_path.endsWith('.java')) {
    return next(e)            // 不是 Java,放行
  }

  const before = await readIfExists($, e.file_path)
  const deny = await judge($, e.file_path, before, e.content)

  return deny === undefined ? next(e) : { deny }
})

judge 做三件事:

  1. 把「写入前的文件」和「写入后的文件」各放进一个临时目录。
  2. 用 PMD 跑一次 p3c 规则集,拿到两份违规列表。
  3. 两份相减,只留下这次写入新增的、级别够高的违规。

Edit 也一样,只是要先读原文件、把替换套上去,算出写入后的完整内容。

检查不经过模型,是 PMD 的语法树规则,同样的代码每次结果一样。在我的机器上单个文件约 1 秒,写入前后两份放在同一次运行里也是 1 秒左右。

几个取舍

只拦新增的违规。 这是能不能日常用的关键。老项目的文件里本来就有违规,全拦的话 Agent 连改一行都会被拒。我拿一个 65 个文件的 Spring Boot 项目扫了一遍,一共 65 条违规,其中 37 条是「缺 @author」。

默认只拦严重的。 p3c 的规则分三个优先级,一共 57 条:

优先级 条数 例子 默认
1 11 线程池创建、包装类用 ==、foreach 里修改集合、不写大括号 拦
2 21 显式建线程、ThreadLocal 不 remove、finally 里 return、命名 拦
3 25 缺 @author、魔法值、HashMap 不指定容量 不拦

上面那个项目里,优先级 1 是 0 条,1 和 2 合计 4 条。如果连优先级 3 也拦,65 条里有 61 条是注释和风格问题,那就没法用了。

守卫自己出错时放行。 没装 Java、规则包下载失败、PMD 崩了,都放行并在状态栏提示。把所有 Java 写入卡死,比漏查一次更糟。

语法错误的文件放行。 那是编译器的事。

一个绕不开的坑:官方 p3c 太旧了

alibaba/p3c 最后一次提交是 2024 年 8 月,规则引擎建在 PMD 6.15 上。这个版本解析不了 record、switch 表达式、文本块,而 Agent 写 Spring Boot 3 的代码会大量用这些。

所以规则引擎用的是社区把 p3c 移植到 PMD 7 和 JDK 21 的版本 loong95/p3c-cmd-jdk21(Apache-2.0)。插件本身不包含任何规则实现,首次使用时从 Maven Central 下载那个 13 MB 的 jar,校验 SHA-256 后缓存在 ~/.cache/p3c-guard/。

我用一个同时包含 sealed 接口、record、文本块、带 guard 的 switch 模式匹配的文件测过,能正常解析并查出里面的线程池问题。

没做到的

  • 不是整本手册。 只覆盖 p3c 用代码实现了的 57 条。空 catch 块、日志规范、SQL 规约这些没有对应规则的条目查不到。
  • 个别规则会漏。 比如静态 SimpleDateFormat 写在 lambda 里调用时查不出。
  • 只看 Write 和 Edit。 Agent 用 shell 命令改文件不经过它。
  • 每台机器的第一次 Java 写入不检查,因为规则包还在后台下载。
  • 只在 macOS 上测过。Linux 没测,Windows 不支持。
  • 测试样本不多:自动化测试 11 个,真实项目上只试过一轮,共 7 次写入,拦了 1 次。

安装

在 Claude Code 里输入:

text 复制代码
/plugin install p3c-guard --marketplace alexlifexyz/p3c-guard

需要 JDK 21 以上,Claude Code 2.1.287 以上。更旧的版本里 Mod 默认是关的,要这样启动:

bash 复制代码
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude

装完输入 /p3c,有回应就是生效了。/p3c off 可以随时暂停。

最后

仓库在这里,MIT 协议: github.com/alexlifexyz...

很想知道写 Java 的各位怎么看:哪些规则你觉得拦了反而碍事,哪些该拦但 p3c 里没有。欢迎拿真实项目试,误拦和漏拦都可以直接提 issue。

相关推荐
威哥爱编程2 小时前
【AI全栈12-01】Spring Boot 为何是 AI 全栈后端优选
java·spring boot·后端
威哥爱编程2 小时前
【AI全栈12-03】Spring Boot 用多模型路由把智能客服成本降下来
java·spring boot·后端
威哥爱编程2 小时前
【AI全栈12-02】Spring Boot 跑通第一个 AI 对话接口:HR 政策问答机器人实战
java·spring boot·后端
Wang's Blog2 小时前
Java 中间件之 RabbitMQ 快速入门: 异步通讯的优缺点
java·中间件·java-rabbitmq
用户094248568032 小时前
第31章:JVM字节码解释器与模板解释器源码路径
java·jvm
子非鱼a2 小时前
【WEB】EasySSTI
java·开发语言·前端
m0_587383002 小时前
深圳 24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·小程序·架构·需求分析
Nebula_g2 小时前
JavaSE项目实践:红包雨-线程池/线程
java·开发语言
小羊没烦恼!2 小时前
jQuery1.5的改进细节
java·服务器·开发语言·前端·c#