正则越长越难读——同事写的 200 字符密码强度正则、Stack Overflow 抄来的 URL 解析正则、RFC 5322 完整邮箱正则——光看字符串脑子要爆。把它们画成铁路图,几秒看清楚结构。这篇用四个工业级例子带你练熟读图。
例 1:密码强度正则
最常见的”必须含大小写字母 + 数字 + 至少 8 位”:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,}$
字符串看不懂在干啥。画铁路图:
^ → [前瞻:.*[a-z]] → [前瞻:.*[A-Z]] → [前瞻:.*\d] → [A-Za-z\d]×{8,} → $
核心结构是三个零宽前瞻 + 一个主匹配:
(?=.*[a-z])— 任意位置后面有小写字母(不消耗字符)(?=.*[A-Z])— 任意位置后面有大写字母(?=.*\d)— 任意位置后面有数字[A-Za-z\d]{8,}— 实际匹配:8 个或更多字母数字
零宽前瞻不前进,所以三个断言都从字符串开头检查”整段里有没有这种字符”。这是 lookhead 最经典的用法——并行表达多个独立条件。
铁路图上零宽断言会画成粉色标题的子图,跟主路径平行——视觉上一眼能看出”这段是断言不是消耗”。
变体:要求还含特殊字符 ^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z\d])[A-Za-z\d!@#$%^&*]{8,}$ —— 多一个前瞻,主匹配字符集多一段。
例 2:URL 正则
简化版 URL 匹配:
^(https?):\/\/([^\s\/]+)(\/[^\s?#]*)?(\?[^\s#]*)?(#\S*)?$
画图后:
^ → (https?) → :// → ([^\s/]+) → (/[^\s?#]*)? → (\?[^\s#]*)? → (#\S*)? → $
[捕获 1] [捕获 2] [捕获 3] [捕获 4] [捕获 5]
scheme host path query fragment
结构是 5 段顺序拼接——前两段必须,后三段可选(带 ? 跳过箭头):
| 捕获组 | 名字 | 内容 |
|---|---|---|
| 1 | scheme | http 或 https(s? 让 s 可选) |
| 2 | host | 任意非空白非斜杠字符 |
| 3 | path | / 开头,到 ? 或 # 前 |
| 4 | query | ? 开头,到 # 前 |
| 5 | fragment | # 开头,到字符串末 |
铁路图上绿色框就是捕获组——画图能直接数出捕获组个数,写代码时取 match[1]、match[2] 不会数错。
完整 RFC 3986 URI 解析器要复杂十倍——支持 IPv6 字面量、用户名密码、端口、相对路径、保留字符——但 80% 业务用上面这个简版就够。
例 3:SemVer 正则
semver.org 官方提供 的 Semantic Versioning 正则:
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
200+ 字符。画图后:
^ → MAJOR(数字) → . → MINOR(数字) → . → PATCH(数字)
→ ( -PRERELEASE )? ← 可选预发布
→ ( +BUILD )? ← 可选构建元数据
→ $
铁路图把整段拆成三段必须 + 两段可选,每段内部有自己的子结构:
- 数字段
0|[1-9]\d*— “0 或非零开头的数字”,避免01.2.3这种 leading zero - 预发布段
-(...(.....)*)— 减号 + 至少一个标识符 + 可选用.分隔的更多标识符 - 构建段
+(...(.....)*)— 类似预发布但用+
关键洞察——SemVer 的复杂度来自”标识符”的精细规则(数字段不能有前导 0、字母数字混合段允许更宽松)。画图后能看到三种数字 / 字母段的字符类差异,比硬看正则强 10 倍。
例 4:RFC 5322 邮箱(简化版)
完整 RFC 5322 大约 6400 字符。工业实务里通常用这个简化版:
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
画图:
^ → [a-zA-Z0-9._%+-]+ → @ → [a-zA-Z0-9.-]+ → . → [a-zA-Z]{2,} → $
本地部分 主域 顶级域
三段顺序拼接 + 一个 @ 分隔:
| 段 | 字符集 | 量词 |
|---|---|---|
| 本地部分 | 字母数字 + ._%+- | 1 次或多次 |
| 主域 | 字母数字 + .- | 1 次或多次 |
| 顶级域 | 纯字母 | 至少 2 次 |
铁路图上看一目了然——实际 90% 邮箱合法性检查这正则就够,剩下 10% 边角(+addressing 已支持,但带引号的本地部分、IP 字面量没支持)实务里没人用。
重要原则——能用 5 行解决的,别上 RFC 5322 完整版。复杂的正则写错的概率远大于覆盖罕见场景的收益。
读图三原则
经过四个例子,总结读图心法:
1. 先看骨架,再钻细节
坏读法:从左到右一个节点一个节点死磕
好读法:先看整张图——几条主路径?几个循环?几个零宽断言?建立宏观印象
骨架明白了,每段子图就是简单的”匹配什么字符 + 重复几次”。
2. 嵌套层级 = 视觉缩进
复杂正则的难点在嵌套——((a|b)+(c|d)*){2,5}。铁路图天然把嵌套画成层级框——外层框包内层框,跟代码缩进一样直观。
3. 量词节点上方的注释
* + ? 这些量词在图上有专门的回环 / 跳过箭头,上方还有文字注释 —— “0 次或多次”、“1 次或多次”、“懒惰:尽量少匹配”。贪婪是默认不会标,懒惰会显式标——很多性能 bug 来自意外的贪婪,看图能直接确认。
什么时候用铁路图,什么时候用测试器
| 任务 | 用什么 |
|---|---|
| 接手别人的复杂正则 | 铁路图(看结构) |
| 验证某个输入能否匹配 | 测试器(看高亮) |
| PR 评审 / 教学讲解 | 铁路图(导出 SVG 贴评论) |
| 调试 catastrophic backtracking | 测试器的 Debugger 步数 |
| 写新正则 | 测试器(边写边验) |
| 给文档画规范 | 铁路图(自带视觉标准) |
两个工具是互补的,不是替代关系。
实战练习
下次再看到陌生的复杂正则,按这个流程走:
- 复制粘进 regex-railroad
- 看整体——分支数、循环数、断言数
- 从左到右拆每一段子图
- 写一行注释总结:“这段是 XXX,由 N 个段拼接”
- 跑几个测试输入,用 正则测试 验证
- 把图导出 SVG,连同正则一起 commit 进项目
正则一旦超过 50 字符,铁路图就是收益最大的可视化方式。练熟读图,从此再不怕同事写的”看了三天还没懂”的正则。
配套阅读
- 正则回溯灾难 — 嵌套量词为什么会让一行正则拖垮服务
- 常见 10 个正则模板 — 邮箱 / 电话 / 身份证 / IP 等高频模式
- 正则方言:PCRE / POSIX / JS / Python — 各家正则语法的差异