用 Claude Code 编写、测试和审查正则表达式
用 Claude Code 生成、调试和审查正则表达式,覆盖邮箱、电话、日志抽取、命名捕获和常见陷阱。
正则表达式最容易出错的地方,不是符号太难背,而是需求没有说清楚。一个能匹配 user@example.com 的表达式,可能会误拒 user+tag@sub.example.co.jp,也可能误放 user@.com。如果后来又增加几个捕获组,代码审查也会变得很痛苦。
Claude Code 适合把这些模糊要求整理成一套可验证的流程:允许的例子、拒绝的例子、正则表达式、可执行测试和审查清单。正则表达式就是用一组规则描述文本形状的小语言;命名捕获则是给抽取出来的部分取名,例如 timestamp、requestId。
如果你刚开始使用 Claude Code,可以先读 Claude Code 入门指南。想把需求写得更稳定,可以配合 5 个更好的提示词技巧。官方资料建议同时打开 Claude Code overview 和 MDN JavaScript 正则表达式参考。
工作流
flowchart LR
A["应该匹配的例子"] --> C["交给 Claude Code"]
B["不应该匹配的例子"] --> C
C --> D["正则和辅助函数"]
D --> E["Node.js 测试"]
E --> F["审查陷阱"]
不要只说“写一个邮箱 regex”。要告诉 Claude Code 这是用于验证、抽取、替换还是日志分析。例子越具体,测试就越像真实合同。
第一个提示词
请为邮箱地址、日本电话号码和应用日志创建一个 JavaScript 正则辅助模块。
要求:
- 可以直接在 Node.js 中运行
- 允许 user+tag@sub.example.co.jp
- 拒绝 user..name@example.com 和 user@.com
- 允许 090-1234-5678, 03-1234-5678, 05012345678
- 用命名捕获从日志中取出 timestamp, level, service, requestId, message
- 使用 node:test 添加测试
- 如果某些规则不应该只靠正则完成,请解释原因
这里的重点是限制范围。完整的邮箱规范很大,注册表单通常不需要也不适合完全实现。更务实的做法是拦住明显错误,再用确认邮件或后端逻辑验证可达性。
示例1: 邮箱、电话和日志辅助模块
把下面代码保存为 regex-helper.mjs,执行 node regex-helper.mjs。
import { fileURLToPath } from "node:url";
export const emailRegex =
/^(?!.*\.\.)[A-Z0-9_%+-]+(?:\.[A-Z0-9_%+-]+)*@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,63}$/i;
export const emailSearchRegex =
/[A-Z0-9_%+-]+(?:\.[A-Z0-9_%+-]+)*@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,63}/gi;
export const normalizedJapanesePhoneRegex = /^0(?:[5789]0\d{8}|[1-9]\d{8,9})$/;
export const looseJapanesePhoneSearchRegex =
/0\d{1,4}[-\s]?\d{1,4}[-\s]?\d{3,4}/g;
export const appLogRegex =
/^\[(?<timestamp>\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z)\]\s+(?<level>INFO|WARN|ERROR)\s+(?<service>[a-z][a-z0-9-]*)\s+requestId=(?<requestId>[A-Za-z0-9_-]+)\s+message="(?<message>[^"]*)"$/;
export function isEmail(input) {
return emailRegex.test(input.trim());
}
export function normalizePhone(input) {
return input.replace(/[()\s-]/g, "");
}
export function isJapanesePhone(input) {
return normalizedJapanesePhoneRegex.test(normalizePhone(input));
}
export function extractContacts(text) {
const emails = [...text.matchAll(emailSearchRegex)]
.map((match) => match[0])
.filter(isEmail);
const phones = (text.match(looseJapanesePhoneSearchRegex) ?? []).filter(
isJapanesePhone,
);
return {
emails: [...new Set(emails)],
phones: [...new Set(phones)],
};
}
export function parseLogLine(line) {
const match = line.match(appLogRegex);
if (!match?.groups) return null;
return {
timestamp: match.groups.timestamp,
level: match.groups.level,
service: match.groups.service,
requestId: match.groups.requestId,
message: match.groups.message,
};
}
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const text = "Contact: user+tag@sub.example.co.jp / 090-1234-5678";
const log =
'[2026-06-02T10:15:30.000Z] ERROR billing-api requestId=req_123 message="payment failed"';
console.log(extractContacts(text));
console.log(parseLogLine(log));
}
电话号码先做标准化,再用正则验证。这样可以把 090-1234-5678、090 1234 5678 处理成同一种形式。日志解析使用命名捕获,审查时能直接看到 match.groups.requestId,不用猜 match[4] 是什么。
示例2: 用测试固定行为
把下面代码保存为 regex-helper.test.mjs,执行 node --test regex-helper.test.mjs。
import test from "node:test";
import assert from "node:assert/strict";
import {
extractContacts,
isEmail,
isJapanesePhone,
parseLogLine,
} from "./regex-helper.mjs";
test("validates practical email addresses", () => {
assert.equal(isEmail("user@example.com"), true);
assert.equal(isEmail("user+tag@sub.example.co.jp"), true);
assert.equal(isEmail("user..name@example.com"), false);
assert.equal(isEmail("user@.com"), false);
assert.equal(isEmail("@example.com"), false);
});
test("validates Japanese phone numbers after normalization", () => {
assert.equal(isJapanesePhone("090-1234-5678"), true);
assert.equal(isJapanesePhone("03-1234-5678"), true);
assert.equal(isJapanesePhone("05012345678"), true);
assert.equal(isJapanesePhone("123-4567-8901"), false);
assert.equal(isJapanesePhone("090-123-456"), false);
});
test("extracts contacts from free text", () => {
assert.deepEqual(
extractContacts("support: user+tag@example.com, tel: 090-1234-5678"),
{
emails: ["user+tag@example.com"],
phones: ["090-1234-5678"],
},
);
});
test("parses application logs with named captures", () => {
const parsed = parseLogLine(
'[2026-06-02T10:15:30.000Z] WARN auth-service requestId=req_abc message="retry required"',
);
assert.deepEqual(parsed, {
timestamp: "2026-06-02T10:15:30.000Z",
level: "WARN",
service: "auth-service",
requestId: "req_abc",
message: "retry required",
});
});
继续让 Claude Code 运行测试,而不是只生成测试。
运行 node --test regex-helper.test.mjs。
如果测试失败,先说明是正则错了还是测试数据错了,再修改。
如果要放宽邮箱规则,先新增一个允许例子和一个拒绝例子。
示例3: 日志抽取提示词
日志格式由应用控制,因此很适合用正则抽取。
读取 logs/app.log,只抽取 ERROR 行,并把 requestId 和 message 写入 CSV。
使用 regex-helper.mjs 中相同的 appLogRegex。
不能静默丢弃无法解析的行,最后输出数量。
不要把邮箱地址或电话号码写入 CSV。
“无法解析的行”很重要。真实日志里会有旧格式、截断行和紧急调试输出。返回 null 的解析函数,比静默忽略数据的脚本更容易审查。
审查模板
## Regex review request
Files:
- regex-helper.mjs
- regex-helper.test.mjs
Review:
- Are allowed and rejected examples covered by tests?
- Are email, phone, and log responsibilities separated?
- Are named capture names readable?
- Is there any ambiguous repetition that could cause ReDoS?
- Could personal data leak into logs or CSV output?
Output:
- For each issue, include file, line, reason, and suggested fix
- Ask a question instead of guessing when the business rule is unclear
- Run node --test regex-helper.test.mjs after changes
ReDoS 指的是正则在特制输入上耗时异常,可能造成性能故障。只要模式里有多层重复,就应该让 Claude Code 明确检查。
常见陷阱
| 陷阱 | 更好的做法 |
|---|---|
| 只给成功例 | 至少给 3 个拒绝例 |
到处使用 .* | 使用明确边界,例如 [^"]* |
用带 g 的正则反复 test() | 验证正则和搜索正则分开 |
| 依赖捕获编号 | 使用命名捕获或非捕获组 |
| 把正则当成业务验证 | 可达性、存在性和权限交给后端 |
正则适合整理输入形状和从受控文本里抽取信息。它不能证明邮箱真的能收到邮件,也不能证明电话号码仍在使用。
什么时候不要用正则
Claude Code 的价值不只是写出一个更复杂的正则,也包括提醒你换工具。处理 URL 时,如果要拆出协议、域名、路径、查询参数和 hash,JavaScript 的 URL 类通常比手写正则可靠。处理 JSON 日志时,应该先 JSON.parse,而不是用正则匹配大括号。处理 CSV 时,也不要自己用逗号切分,因为引号、换行和转义规则很容易漏掉。可以在提示词里直接写:“只在纯文本抽取时使用正则;如果平台有更安全的 parser,请优先使用并说明原因。”
在审查时,把问题拆成三层也很有用。第一层是输入形状,正则适合解决。第二层是业务规则,例如这个邮箱域名是否允许注册、这个电话是否属于目标地区。第三层是存储规则,例如是否可以写入日志、分析事件或 CSV 导出。把三层分开后,正则 helper 就不会偷偷变成难以维护的业务策略中心。
对于多语言站点,还要注意本地格式差异。日本电话号码、美国电话号码、印度手机号、欧洲带国家码的号码,不应该用同一个表达式硬套。Claude Code 可以为每个市场生成样例表,但你要明确“本文只验证日本国内号码”或“这里允许 E.164 格式”。范围说得越清楚,生成结果越容易测试,也越不容易误导读者。
如果文章面向初学者,还要把这些限制写在正文里,而不是只藏在代码注释中。读者复制代码前先理解边界,后续咨询和模板销售的信任感也会更高。
转化引导(CTA)
正则改造常常贴近收入路径:线索表单、结账日志、客服录入、资料下载注册。个人练习可以从 免费 Claude Code 速查表 开始;需要可复用提示词和模板,可以看 ClaudeCodeLab 产品;团队要把验证、日志审查和代码审查门禁落地,可以使用 Claude Code 培训与咨询。
总结
用 Claude Code 写正则的关键,不是把符号知识完全交给 AI,而是同时给出例子、反例、测试和审查标准。Masa 实测后发现,“请同时生成正则和测试”比“写一个正则”更稳定,尤其能提前发现 user+tag 邮箱、带连字符电话号码和命名日志捕获这类容易回归的问题。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
从Obsidian到CLAUDE.md的Claude Code流程:不再反复解释上下文
把 Obsidian 工作笔记整理成 CLAUDE.md 运行说明,让 Claude Code 每次都带着正确上下文开始。
Claude Code 收入 CTA 路由:从文章分流到 PDF、Gumroad 与咨询
用 Claude Code 按读者意图把文章流量分到免费 PDF、Gumroad 教材或咨询入口。
Claude Code 团队交接规则: 把审查证据、权限、回滚和收入路径一起交付
面向团队的 Claude Code 交接格式: 证据、权限、回滚、免费 PDF、Gumroad 与咨询路径都要可审查。