# 01 | 我是怎么学习和使用正则的?

我经常在⽹上看到,许多⼈抱怨正则表达式「难学」,我知道,它确实不好学。但同时我也仔细看过⼤家的抱怨,发现和我之前的做法⼀样:⽤到什么功能,就去⽹上搜⼀个例⼦来改改,能跑通就满意。⾄于这例⼦到底如何构成的,⾃⼰是不是都懂了,其实⼼⾥没底,能⼤概看懂五六分,就已经很满⾜了。

这样浮光掠影的使⽤⽅法或许能解决眼前的问题,但⼀定不算「学会」。它有点像打井,每次挖到⼀点⽔就满⾜了,根本不管有没有持续性,也不关⼼挖没挖到含⽔层。结果就是,每次要喝⽔的时候,你都得重新打⼀眼井。

那么对于正则表达式,我们有没有可能「打出⼀⼝永不⼲涸的深井」呢?当然有,那就是 ⼀次性多投⼊点时间,由表及里,由术及道。一旦掌握了方法,之后就会简单很多了

按照我的经验,如果每天花一刻钟或者半小时,坚持个把礼拜,通常都能登堂入室,达到「不会忘」的境界。不要以为这时间很多,我知道有些人很喜欢找「正则表达式五分钟入门」,其实每次都没有入门,日积月累,反而浪费了几十甚至上百个五分钟。

那多投⼊时间很好理解,但是什么叫掌握⽅法呢?⽤我的话说,就是摆脱了字符的限制,深⼊到概念思维的层⾯。不要盯着那些⻤画桃符⼀般的字符和表示法皱眉,⽽要摆脱桃符,把真正的「⻤」给认出来——虽然它们不那么容易看⻅。 也正因为这样,我们才需要⼀次性多投⼊点时间。

那最终怎样才算入门了呢?按照我的经验,就是通过学习掌握方法,后来无论用正则表达式解决什么问题,都能自发遵循下⾯的流程去走,甚至能达到不需要这个流程,也能做到解决问题,那基本上就算入门了。

# 写正则流程

第⼀步,做分解。 拿到一个问题后,我们要先思考:这个问题可以分为⼏个⼦问题?每个⼦问题是否独⽴?我们拿最常⻅的电⼦邮件地址匹配来说。从文本结构来看,它可以分为username + @ + domain name 这三个独⽴的部分。怎么画呢?我们可以先画出逻辑结构图。通过这个过程来厘清思路。当然,这是软件⼯程最基本的思路,相信你做起来应该问题不大。

第⼆步,分析各个子问题。 某个位置上可能有多个字符?那就⽤字符组。某个位置上可能有多个字符串?那就⽤多选结构。出现的次数不确定?那就用量词。对出现的位置有要求?那就⽤锚点锁定位置…… 某种程度上,这就像武术⾥的⻅招拆招,每个问题都有对应的解法,只要熟练掌握了,知道什么时候用字符组,什么时候用多选结构,什么时候用量词,什么时候用锚点,就很容易搭建起完整的概念模型。

第三步,套⽪。 你大概注意到了,到现在,我们还没有谈论正则表达式的典型标志,比如方括号、星号、花括号。要知道,这些典型标志无非只是一些符号而已,真正重要的是字符组、多选结构、量词等等这些概念。一旦你的概念模型清楚了,写出正则表达式就非常简单了,无非是查阅语法⼿册,把之前得到的概念模型按照对应语⾔或⼯具的约定写下来而已。

许多人觉得正则表达式难懂,总是纠缠于「这里为什么要多一个星号?那里为什么是方括号而不是花括号?」,原因恰恰在于对概念模型不清楚。虽然各种语⾔或⼯具对正则表达式的⽀持⼤同⼩异,但细微差别仍然不可忽视。不过只要你⼼怀正念,洞若观⽕,这些差异其实并不是⼤问题。

第四步,调试。 很多人都说,正则表达式的麻烦之处在于它像个黑箱子,很难调适,迄今为⽌仍然没有特别好⽤的⼯具,所以我们没法⼀步步跟进去看匹配的具体过程,只能笼统地知道「匹配了」或者「没匹配」。

那到底怎么调试呢?我的经验是,复杂⼀点的正则表达式不能⼀次写对,这是很正常的。与其纠结这个正则表达式看起来这么复杂,此处到底要用星号 * 还是加号 +,不如先搞清楚,星号( * )或加号( + )限定的到底是正则表达式中的哪一部分,对应要匹配文本中的哪一部分。这两个问题搞清楚了,整个问题就迎刃而解了。

另外,还有⼀点统摄全局的经验想和你说一下, 那就是学会了正则表达式之后,务必要保持克制 。写正则表达式很容易上瘾,毕竟它的功能那么强⼤,处理速度那么快,⼜像天书符咒那样充满了神秘色彩。于是,「写⼀条其他⼈看不懂的正则表达式,⼀次性解决所有问题」,就成了某些程序员的执念。但是,从软件⼯程的⻆度来看,这种办法绝对是噩梦,不但其他⼈⽆法理解,⾃⼰过⼀段时间也会挠头。

# 如何克制

那到底该怎么「克制」呢?我的经验有以下三点。

第⼀,能⽤普通字符串处理的,坚决用普通字符串处理 。字符串处理的速度不⻅得差,可读性却好上很多。如果要在大段文本中定位所有的 today 或者 tomorrow,用最简单的字符串查找,直接找两遍,明显比 to(day|morrow) 看起来更清楚。

第⼆,能写注释的正则表达式,⼀定要写注释。 正则表达式的语法非常古老,不够直观,为了便于阅读和维护,如今⼤部分语⾔⾥都可以通过 x 打开注释模式。有了注释,复杂正则表达式的结构也能⼀⽬了然。

第三,能⽤多个简单正则表达式解决的,⼀定不要苛求⽤⼀个复杂的正则表达式 。这里最明显的例子就是输⼊条件的验证。比如说,常见的密码要求「必须包含数字、小写字母、大写字母、特殊符号中的至少两种,且长度在 8 到 16 之间」。

你当然可以绞尽脑汁用一个正则表达式来验证,但如果放下执念,⽤多个正则表达式分别验证

  • 包含数字
  • 包含小写字母
  • 包含大写字母
  • 包含特殊符号

这四个条件,要求验证成功结果数大于等于 2,再配合一个正则表达式验证长度,这样做也是可行的。虽然看起来繁琐,但可维护性绝对远远强于单个正则表达式。

# 小结

这些年,很多人问过我,我当时到底是怎么学会正则的?说实话,我那会儿根本没想什么,纯粹出于干一行爱一行的朴素想法。要用得多,就找书来,哪怕是囫囵吞枣,也要一鼓作气看完。我一直觉得,真正值得学的东西,没有什么平滑学习曲线。在前面的阶段,你总得狠下心来,过了一个又一个坎儿,然后才能有一马平川。

我觉得,正则表达式属于「没有维护成本」的技能。⼀旦学会了,每⼀次遇到这类问题都可以「零成本出击」。所以,⻓期来看,这绝对是一笔⽆本万利的⽣意。希望你能通过这个专栏早日达到一马平川!