手头刚把 office2pdf 这个项目的 CLAUDE.md 规则文件整理完,正好趁着热乎劲儿把整个思路沉淀下来。office2pdf 这名字很直白,就是把 Office 文档(Word、Excel、PPT)批量转成 PDF 的转换服务,而 CLAUDE.md 是给 AI 编程助手读的项目规则文件。这俩放在一起,本质上就是一件事:怎么把"能跑的代码"变成"别人(或者 AI)也能安全维护的工程"。
我在维护这个项目的大半年里踩了不少坑,从 LibreOffice 无头模式的字体设置,到并发转换时内存爆炸,再到后来引入 CLAUDE.md 后 AI 生成的代码风格终于稳定统一,每一步都有值得记录的细节。这篇文章既是项目复盘,也是写给两类人看的:一类是想自己搭 Office 转 PDF 服务的开发者,另一类是正在纠结"到底要不要给项目写 CLAUDE.md、写了又该怎么写"的团队。两部分内容我会穿插着讲,因为对我来说,它们其实是同一件事——把隐性规则显性化。
1. office2pdf 到底在解决什么问题
1.1 一个看似简单却处处是坑的需求
Office 转 PDF,听起来不就是"另存为"一下吗?但在服务端做这件事,复杂度完全不在"转换"本身,而在稳定性、保真度和资源管理上。我接到这个需求时,业务方的原话是:"帮我把销售团队每周交上来的提案、报价单、行业报告统一转成 PDF,方便归档和发给客户。"
当时我心想这不简单?给服务器装个 LibreOffice,写个脚本循环调用不就完了。结果真正做起来才发现:
- 文档里用到的字体服务器上没有,转换后中文全变成方块
- 一个 200 页带复杂样式的 PPT,转出来排版全乱
- 转换进程偶发崩溃,任务队列里的文件卡死
- 多个转换任务同时跑,服务器内存直接被打爆
所以 office2pdf 项目从来不是一个"转换脚本",而是一个健壮的文件处理服务。它涉及文件上传、格式检测、任务排队、并发控制、转换执行、文件清理、错误重试、结果回调等多个环节。你在设计架构时,至少要把它当成一个"会吞掉各种奇怪输入"的消息处理系统来对待,而不是一个简单的工具函数。
1.2 典型应用场景盘点
我梳理了一下实际使用中最高频的几个场景,基本可以覆盖大多数团队对 office2pdf 的需求:
- 在线预览:浏览器不能直接渲染 docx / pptx,统一转成 PDF 后通过
pdf.js或浏览器内置 View 预览,省去为每种 Office 格式单独做解析器的成本 - 文档归档:合同、方案、报告等需要长期保存,PDF 的版式固定性远强于 Office 格式,转完后可以作为唯一可信版本
- 邮件附件处理:把多份 Office 文档合并转成一个 PDF 再作为附件发送,客户不需要安装 Office 也能查看
- 内容安全管控:转为 PDF 后可以做水印、加密、权限控制,防止原始文档被随意编辑
你会发现这些场景有一个共同点:使用者并不关心 Office 源文件的编辑能力,只关心最终看到的内容是否完整、准确、美观。这就是 PDF 作为"内容交换格式"的价值——它在几乎所有设备上的渲染结果都是一致的。
1.3 项目范围的边界划分
在写 CLAUDE.md 之前,我做的第一件事是明确项目的边界。这不是文档形式主义,而是为了后续所有开发决策有一个判断基准。我给 office2pdf 划定的边界是:
- 核心能力:接收 Office 文件,输出 PDF 文件
- 输入范围:
.doc、.docx、.xls、.xlsx、.ppt、.pptx - 输出要求:尽可能与 Office 中的排版一致,字体嵌入,页面尺寸正确
- 非目标:不做文档内容解析、不做 OCR、不做 PDF 编辑、不做格式间的任意转换
这个边界很重要。因为在后续开发中,会不断有人提"能不能顺便把 PDF 转回 Word"或者"能不能顺便提取一下文档里的文字做全文搜索"。有了明确的边界,你就可以礼貌地拒绝,或者把它们拆成独立项目,而不是让一个转换服务变得越来越臃肿。
2. CLAUDE.md:AI 时代的项目规则文件
2.1 它是给谁看的
CLAUDE.md 这个名字来自 Anthropic 的 Claude Code——一个能在终端里运行的 AI 编程助手。它的工作方式是读你项目里的文件、理解代码结构、帮你实现功能。但 AI 再聪明,它对你的项目是陌生的,不知道你习惯怎么组织代码、测试要跑什么命令、目录结构有什么约定。CLAUDE.md 就是你写给 AI 看的项目操作手册。
说白了,这和我们团队之前写"新人入职文档"是一个思路。新人第一天来,不可能自己把代码库全读完才动手,你需要给他一份"快速上手指南":构建命令是什么、代码风格如何、哪个目录放什么、踩过哪些坑要注意。CLAUDE.md 就是给 AI 新人的那份入职文档,只不过它的读者是 Claude、Cursor、Copilot 这类编程助手。
2.2 为什么一个转换小服务也需要项目规则
你可能觉得,office2pdf 这种项目总共没几个文件,AI 看一眼就懂了,写 CLAUDE.md 是不是小题大做?我一开始也是这么想的,直到我让 AI 帮我加一个"队列深度上限配置",它自作主张把整个系统从同步处理改成了异步架构——虽然功能没错,但风格和现有代码完全不一致,测试也全没跑过。
那一刻我明白了,AI 最缺的不是能力,而是约束。一个没有规则文件的项目,AI 每次生成代码都是一次"自由发挥",代码风格飘忽不定,实现路径五花八门。而 CLAUDE.md 就是给这套"自由发挥"戴上枷锁,让它在你划定的轨道上干活。
具体到 office2pdf 这种工程项目,CLAUDE.md 至少要解决这几个问题:
- 告诉 AI不要重新发明轮子,转换引擎已经选定,不要引入新的依赖
- 告诉 AI构建和测试的命令格式,让它改完代码后自己验证
- 告诉 AI异常处理的约定,比如转换失败时是重试还是直接丢弃
- 告诉 AI代码风格的偏好,对齐项目里已有的写法
2.3 CLAUDE.md 与 AGENTS.md 的关系
这里多说一句,目前市面上有几种类似的约定文件,AGENTS.md 是比较通用的一种,Claude Code 认 CLAUDE.md,Cursor 认.cursor/rules,GitHub Copilot 认.github/copilot-instructions.md。
如果你在维护一个开源项目,又希望不同 AI 工具都能读,我的建议是以 CLAUDE.md 为主,里面加上一句"本文件同时适用于所有 AI 编程助手,请遵循其中的规则",然后通过软链接或复制生成一份 AGENTS.md。我在 office2pdf 里就是这么做的,虽然没有硬性证据 AI 一定都读,但至少把姿态摆出来了。
3. 一份合格 CLAUDE.md 的骨架与编写要点
3.1 先看一个精简的目录模板
我给 office2pdf 写的 CLAUDE.md 大概有 120 行,不算长,但每一行都有信息量。它的结构大致是这样的:
# office2pdf 项目规则 ## 项目概述 (两句话说明项目做什么、技术栈、核心约束) ## 常用命令 - 安装依赖: pip install -r requirements.txt - 运行服务: python app.py - 运行测试: pytest tests/ -q - 格式化: black src/ - 类型检查: mypy src/ ## 目录结构 (说明 src / tests / data / output 各自放什么) ## 技术约束 - 转换引擎固定使用 LibreOffice headless 模式 - 禁止直接调用 Office COM(Windows 依赖) - 文件处理必须走统一的转换队列 - 临时文件必须在使用后清理 ## 代码风格 - 类型标注必须完整 - 错误处理使用自定义异常体系 - 日志使用 logging 模块,禁止 print ## 测试要求 - 新增转换格式必须补集成测试 - 测试夹具放在 tests/fixtures/ - 禁止把大文件(>10MB)放入测试夹具 ## 常见坑 - 字体缺失导致中文乱码 - 高并发下 soffice 进程内存泄漏 - 文件路径包含空格和中文3.2 每个段落要回答什么问题
写 CLAUDE.md 时我有一个原则:每一段都要回答 AI 读了之后会问的一个具体问题。
"项目概述"回答的是"我是谁、我在哪、做什么"——AI 需要先对项目建立整体认知,才能避免做出方向性错误。"常用命令"回答的是"我怎么验证代码是对的"——AI 改完代码如果不知道跑什么测试,它就不会主动验证。"技术约束"回答的是"我能用什么、不能用什么"——这是最容易让 AI 跑偏的部分,它可能会因为觉得 LibreOffice 不好用而擅自换成别的方案。"代码风格"回答的是"我写出来的代码应该长什么样"——让 AI 生成的代码和已有的代码混在一起时不突兀。"常见坑"回答的是"这个项目哪里容易翻车"——这些都是你实际踩过的坑,AI 看不到这些历史教训,但它读了文档就能提前避开。
3.3 编写中容易犯的三种错误
第一,写得像读书笔记而不是执行手册。有人会把 CLAUDE.md 写成"本项目是一个基于 Flask 的文档转换服务,支持多种格式……"这种废话。规则文件里的每句话都应该能约束 AI 的某个具体行为,而不是描述性的信息。
第二,规则太多太琐碎。如果 CLAUDE.md 几千行,AI 的上下文窗口有限,真正关键的规则反而可能被遗漏。我的经验是控制在 150~200 行为宜,只写那些不遵守就会出问题的规则。
第三,只写规则不写原因。同样一条"禁止使用 print 调试",如果你加一句"项目日志统一走 logging,方便采集到 ELK",AI 遵守的意愿和准确度都会明显提升。规则+原因是黄金组合。
4. office2pdf 核心实现:转换方案选型与对比
4.1 四大路线的优劣分析
如果要自建 office2pdf,转换引擎的选择是第一个要做的决定。我调研了四类路线,简单说说它们的取舍。
LibreOffice headless 模式(我最终选用的方案):通过soffice --headless --convert-to pdf命令行完成转换,支持几乎所有 Office 格式,免费开源,跨平台。缺点是每次启动 soffice 进程有冷启动开销,需要常驻服务模式来优化,复杂文档的排版保真度与 Office 原生有差距。
Apache POI + Apache PDFBox:纯 Java 生态,不需要安装任何 office 软件,POI 负责解析 Office 文件结构,PDFBox 负责生成 PDF。但这个路线的工程量非常大,需要自己处理段落、表格、图片、样式等等,维护成本极高,一般只适合对特定格式做深度定制的场景。
Aspose / Spire 等商业库:转换保真度最好,API 设计也友好,但需要购买授权,商用成本不低。如果你的业务对排版还原度有硬性要求且预算充足,这个路线最省心。
调用 Office 的 COM 自动化(Windows 上):保真度最高,因为就是 Office 自己导出的 PDF。但强依赖 Windows 环境,服务端不能部署在 Linux 上,而且稳定性差,Office 弹个更新对话框就能让转换挂掉。
我推荐你做一个简单的决策矩阵:把成本、平台兼容性、保真度、维护复杂度四个维度列出来,按项目实际需求打分。对大多数服务端场景来说,LibreOffice 是性价比最高的选择,这也是我最终选它的原因。
4.2 LibreOffice 常驻服务的必要性和配置
一开始我用的是"每次转换都调用一次 soffice"的方式,简单是简单,但性能很差。一个 10MB 的 PPT 转 PDF,光启动 soffice 就要 5 秒,加上转换时间,总共要 15 秒左右。后来我改成了常驻监听模式,启动一个soffice --headless --accept="socket,host=localhost,port=2002;urp;"进程,然后用 UNO API 通过 socket 连接它做转换,冷启动时间直接降为 0。
但也有代价:常驻进程如果被某些文档搞崩溃了,所有后续请求都会失败。所以我在代码里加了一个 watchdog,定期发一个健康检查(随便转一个内置小文档),如果超时或报错就直接 kill 掉重启。这个逻辑一定要有,否则线上会出"莫名其妙所有转换都失败"的诡异事故。
配置方面有几个参数值得注意:
soffice --headless --convert-to pdf --outdir /tmp/output input.docx如果遇到字体问题,需要修改 LibreOffice 的字体配置或安装系统字体;如果并发量上来了,可以考虑用--env:UserInstallation=file:///tmp/lo_profile隔离用户配置目录,避免多实例冲突。这两个细节我在后面的排查部分细说。
4.3 服务层架构:把转换包成可扩展的流程
单纯的转换引擎只解决了"文件从 A 变 B",一个可运营的转换服务还得包一层。我的架构分四层:
- 接口层:HTTP API,接收上传文件和参数,返回任务 ID
- 队列层:内存队列 + 多消费者线程(或者 Celery / RabbitMQ),控制并发度
- 执行层:调用转换引擎,处理生命周期
- 存储层:输入文件的临时存储、输出 PDF 的持久化存储
代码上用 Python + FastAPI 的话,核心执行流程大概是这样的:
@app.post("/convert") async def convert(file: UploadFile, output_format: str = "pdf"): task_id = str(uuid.uuid4()) input_path = save_to_temp(file) queue.put((task_id, input_path)) return {"task_id": task_id, "status": "queued"} def worker(): while True: task_id, input_path = queue.get() try: output_path = converter.convert_to_pdf(input_path) notify_callback(task_id, output_path) except ConversionError as e: record_failure(task_id, str(e)) finally: cleanup_temp_files(input_path, output_path)这里注意几个设计:任务 ID 是幂等的关键,回调通知用异步方式避免阻塞,临时文件必须在 finally 里清理。我第一次上线时忘了清理临时文件,结果 /tmp 被撑爆,整个服务挂了,教训很深刻。
5. 实操:构建一个带队列与重试的转换服务
5.1 FastAPI 脚手架与目录规划
如果从零开始搭,我建议项目结构直接规划成下面这样,后面扩展不会痛苦:
office2pdf/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── config.py # 全局配置(并发数、超时、存储路径) │ ├── converter/ │ │ ├── base.py # 转换器抽象接口 │ │ ├── libreoffice.py # LibreOffice 实现 │ │ └── registry.py # 格式路由(docx->pdf, xlsx->pdf...) │ ├── queue/ │ │ ├── manager.py # 队列管理 │ │ └── worker.py # 消费线程 │ ├── models/ │ │ └── task.py # 任务状态模型 │ └── storage/ │ ├── temp.py # 临时文件管理 │ └── output.py # 输出文件管理 ├── tests/ │ ├── fixtures/ # 测试用文档 │ ├── test_api.py │ └── test_converter.py ├── CLAUDE.md ├── requirements.txt └── README.md目录规划至少要满足两个目的:第一,业务代码之间解耦,队列、转换、存储各自独立,替换任何一个实现都不影响其他;第二,边界清晰,AI 拿到这份 CLAUDE.md 后,能快速定位"我要改的是哪个模块",不会在错误的位置用力。
5.2 队列设计:并发数、重试与超时
队列是整个服务的核心,这块设计不好,其他部分再稳都没用。我用的是一套简化版设计,但思路可以复用到任何任务队列系统上。
并发控制:LibreOffice 常驻进程是单例的,多个转换同时打进去会排队甚至报错。我用一个信号量(threading.Semaphore(4))控制同时进行的转换任务数。这个 4 是压测出来的:4 个并发时 CPU 和内存都还在合理范围,吞吐量也最大。并发数不是越大越好,要根据服务器配置和文档大小实测。
重试策略:转换失败不一定是永久错误,比如网络抖动导致临时文件没下载完,或者 soffice 进程刚好在 restart。我的重试逻辑是:前两次间隔 2 秒、5 秒,第三次失败就直接标记失败放进死信队列。超过重试次数的任务不能无限堆积,死信队列可以留着人工排查。
超时控制:一个 200MB 的诡异文档可能转换 10 分钟不结束,必须设置超时。LibreOffice 命令行模式可以用--convert-to pdf --outdir /tmp/out input.docx & sleep 60; kill %1这种粗糙方式,更优雅的是用subprocess.run(..., timeout=120)给进程加外部超时。超时后把进程和它的子进程全部杀掉,否则僵尸进程会占着资源不放。
5.3 环境兼容性:Windows、Linux、Docker
我强烈建议直接用 Docker 打包,因为 LibreOffice 的依赖链太复杂了,不同系统上安装方式和版本差异很大。我踩过一个坑:本地 Ubuntu 20.04 上装好的 LibreOffice 转换正常,一到 CentOS 7 的服务器上就缺各种系统库,而且字体渲染效果也不一样。
Dockerfile 里核心的是这几步:
FROM python:3.11-slim RUN apt-get update && \ apt-get install -y --no-install-recommends libreoffice-core \ libreoffice-writer libreoffice-calc libreoffice-impress \ fonts-noto-cjk fonts-noto-color-emoji && \ rm -rf /var/lib/apt/lists/* COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app/ /app/ WORKDIR /app CMD ["python", "main.py"]注意fonts-noto-cjk这个包,没有它中文社区文档转换出来就是满屏方块。具体的字体问题我在下一节展开。
5.4 接口设计细节
接口层有几个容易被忽略但实际很重要的细节:
文件大小限制:必须设置上传文件大小上限,我用的是 50MB。没有限制的接口会被恶意或误操作的大文件拖垮。
格式白名单:接收文件时只允许扩展名在.doc, .docx, .xls, .xlsx, .ppt, .pptx列表里的文件,其他一律拒绝。不然有人传个.exe进来,你的转换器会尝试用 LibreOffice 打开它,轻则报错重则被利用。
异步化:转换是耗时操作,接口不能同步等待结果。我采用"提交任务 -> 返回 task_id -> 客户端轮询或接收回调"的模型。回调 URL 由客户端上传时指定,任务完成后服务端主动 POST 通知,避免客户端频繁轮询浪费资源。
class ConvertRequest(BaseModel): callback_url: str | None = None priority: str = "normal" # high / normal / low class ConvertResponse(BaseModel): task_id: str status: str5.5 别忘了写测试
CLAUDE.md 里强调测试要求不是空话,因为这类项目的回归风险非常高。一次 LibreOffice 升级可能导致所有旧文档的转换效果变化,测试就是你的安全网。
我的测试分三层:
- 单元测试:队列逻辑、文件路径处理、超时判断等不涉及真实转换的纯逻辑
- 集成测试:真实调用转换引擎,用一个小型 docx 转 PDF 验证流程跑通
- 快照对比:每次变更后固定转换一组标准文档,对比 PDF 页数和关键文本是否一致,用
pypdf提取文本做断言
最后这层做个简单的页面数断言就够了:assert page_count == expected_pages。太精细的像素级比对维护成本太高,做成可选的人工巡检更现实。
6. 常见问题排查与避坑实录
6.1 中文乱码与字体缺失
这是 office2pdf 项目里遇到最多的一个问题,说白了就是转换环境没有目标字体。你 Windows 上做的文档用的微软雅黑,服务器 Linux 上没有,LibreOffice 只能找一个替代字体,替代效果通常很糟糕。
排查思路分三步:
- 先确认服务器上装了哪些字体:
fc-list | grep -i "yahei\|noto\|simsun" - 确认 Office 文档里用的字体名:用
unzip -p document.docx word/fontTable.xml查看 - 在服务器上安装对应字体,路径一般是
/usr/share/fonts/,装完执行fc-cache -f刷新
经验之谈:直接装上 fonts-noto-cjk 只能解决一部分问题。如果业务方大量使用特定字体(比如方正小标宋、微软雅黑、宋体),你需要手动把字体文件拷到服务器并注册。我在 Dockerfile 里加了一步:
COPY fonts/ /usr/share/fonts/custom/ RUN fc-cache -f6.2 格式错位:转出来和 Word 里不一样
这种问题的根因通常是字体的度量(metrics)不同,导致字体换行位置变化,段落排版跟着偏移。严格来说,这很难 100% 解决,但要尽量靠近。我的做法有几个:
- 优先使用 LibreOffice 7.x 以上版本,低版本的渲染引擎弱不少
- 转换时指定
--infilter="Microsoft Word 2007-2019 XML (.docx)",让 LibreOffice 用正确的解析过滤器 - 对于 PPT,可以在调用时加上
--convert-to pdf:impress_pdf_Export指定导出配置
如果客户反馈"某份文档转完和源文件差距很大",先确认是不是字体问题,再确认是不是 LibreOffice 本身对某些复杂样式支持不佳。遇到这种极端情况,我的方案是单独微调该文档,而不是改全局配置。
6.3 高并发下的内存与稳定性
LibreOffice 常驻模式虽然解决了冷启动问题,但内存管理并不完美。我压测时发现,连续转换几十个大文件后,soffice 进程的 RSS 内存肉眼可见地涨,不及时处理就会崩。
对策有三板斧:
- 定期重启:每晚低峰期自动重启一次 soffice 进程,把内存打回初始状态
- 内存上限监控:用
psutil监控 soffice 的 RSS,超过阈值(比如 1.5GB)就主动重启 - 失败自动恢复:如果某个文档导致 soffice 进程崩溃,必须能自动拉起新进程并重新排队任务
代码上,我在 worker 里包了一层:
def safe_convert(task): for attempt in range(3): try: return self.converter.convert_to_pdf(task) except ConverterCrashError: self.converter.restart() continue raise ConversionError("soffice crashed 3 times")6.4 文件路径、权限与清理
很多转换失败不是转换器的问题,是文件系统的问题。我整理过一份清单:
- 路径含空格:LibreOffice 命令行传参时,路径必须用引号包裹,否则会被解析成多个参数
- 路径含中文:确保环境变量
LANG或LC_ALL为 UTF-8,否则文件路径里的中文可能乱码导致找不到文件 - 临时目录权限:运行服务的用户对
/tmp和输出目录要有写入权限,用 Docker 时尤其注意挂载卷权限 - 文件清理:不清理临时文件是最大的隐患,建议用
tempfile.NamedTemporaryFile(delete=False)创建,然后在finally里删除,再加一个定期任务扫掉超过 24 小时的残留文件
7. CLAUDE.md 中的项目规则如何真正落地
7.1 一个反例:没有规则文件时的 AI 代码
说回 CLAUDE.md。为了让你直观感受规则文件的价值,我拿真实发生过的一次对话举例。当时我没有写规则,让 Claude 给 office2pdf"加一个转换超时时间配置项"。它给我的方案是:
# AI 生成的"自由发挥"版本 def convert_with_timeout(file, timeout=120): # 先用 pdf2image 把文档渲染成图片?不,等等 # 我应该用 subprocess 调 soffice,但需要处理更多 edge case # 要不我可以先检查文件类型: import mimetypes ...这个回答在功能方向上是对的,但暴露了很多问题:它引入了一个项目里从未用过的库mimetypes,没有遵循已有的异常处理模式,也没有跑测试就交差了。原因很简单——它不知道项目的规则,只能按它训练数据里的"普遍常识"来写。
7.2 有了规则文件之后的效果
写上 CLAUDE.md 之后,同样的需求变成这样:它先读取 CLAUDE.md 里的技术约束(转换引擎固定是 LibreOffice、自动日志、类型标注、跑测试),然后给出的实现就靠谱得多——在我已有的config.py里加字段,复用既有的converter抽象,报错抛我自定义的ConfigError,最后还会主动说"我跑一下pytest tests/ -q验证"。
当然,AI 也不是读了规则就完美,但规则文件把错误的发生率从"很高"降到了"偶发",这个差距已经足以影响你是否敢让 AI 参与项目维护了。我的判断标准是:如果 AI 按规则做,即使有错,错误类型是可预期的、可 review 的;如果不按规则做,那几乎是不可预期的。
7.3 规则的按需更新:把踩坑沉淀回文档
CLAUDE.md 不是一次性写完就完事的。我每踩一个新的坑,只要这个坑对后续开发有约束价值,就会补进"常见坑"一节。比如有一次我发现并发数从 4 调到 8 之后,soffice 反而更容易崩溃,而且整体吞吐量没提升,于是在 CLAUDE.md 里写了一条硬性规则:"并发转换数不得超过 4,具体压测数据见 docs/concurrency-test.md"。
这种"坑 -> 规则 -> 文档"的闭环,让 CLAUDE.md 成为项目中的活文档。它记录的每一行都不是拍脑袋想出来的,而是真实发生过、需要被约束的。这也是我推荐每个项目都写 CLAUDE.md 的核心原因——它强制你把项目的隐性经验变成显性规则,这对人、对 AI 都有价值。
7.4 结合具体场景:CLAUDE.md 里还有哪些值得补充的内容
除了前面提到的 6 个核心段落,我在实际使用中还加了几个补充块,供你参考:
安全约定部分:明确"转换过程中不执行文档内嵌宏""不解析文档里的外部链接",避免引入安全风险。生产环境说明部分:写清楚生产环境是 Docker 部署、监听端口、日志位置,AI 做运维类改动时不会瞎改。CI/CD 流程部分:告诉 AI "提交前跑pre-commit,格式和 Lint 不过不能提交",这样 AI 生成的代码一开始就能过 CI。
我还加了一段"业务语义约定":比如"任务状态流转只能是 queued -> processing -> done/failed",防止 AI 随意扩展状态枚举导致回调逻辑混乱。这类约束对小项目可能多余,但对要长期演进的项目,越早写越省心。
8. 一些小习惯:让项目规则文件持续保鲜
最后分享几个我实际操作中养成的习惯,不一定适合所有团队,但效果确实不错。
第一,给 CLAUDE.md 写个变更日志。每次增删规则都记录一行日期+变更原因,这样你回看的时候能清楚地知道"为什么有这条规则"。三个月后你看到"禁用并发数 > 4"这条,如果没有变更日志,可能已经想不起当初压测的结论了。
第二,把"反向约束"也写进去。所谓反向约束,就是明确告诉 AI"这个项目不适合做什么"。我在 office2pdf 里写了一条:"本服务不负责转换 Excel 中的图片提取,不建议在此项目内实现该功能,请推荐到独立 OCR 服务。"AI 有时会过度发挥,反向约束能有效拦住它。
第三,定期审视规则是否过时。我每个季度会过一遍 CLAUDE.md,删除已经不成立的规则,更新技术选型决策。比如最初我写了"禁止使用任何新依赖",但后来为了性能优化引入了psutil,就需要同步更新这条规则。规则文件和代码一样,不维护就会腐烂。
我记得有一次,团队新增了一个同事专门负责 office2pdf,他上手第一天看完代码后问我"这个项目怎么这么多规矩"。我让他先按规矩跑一遍测试,他跑完回来跟我说了句"这些规矩保住了我一下午的头发"。这个反馈比任何开发方法论都让我欣慰。
如果你也在维护类似的文档转换项目,或者正打算把 CLAUDE.md 引入团队工作流,我的建议是从小做起:先写下构建命令、测试命令、三条最重要的技术约束,然后让 AI 在项目里改一个小功能试试。你会直观感受到,当 AI 不再靠猜来干活时,它才真正变成一个靠谱的同事。