奇葩的正则表达式
1、常规学习过程很痛苦
计算机领域中有一些非常基础、重要以及应用广泛,但却又特别容易让人困惑、非常难以理解的主题,这包括了字符编码、字节序(即大小端表示)、浮点数实现、日期时间处理以及正则表达式等。
正则表达式目前市面上并不缺乏专业著作,比如那本被誉为正则表达式学习圣经的《精通正则表达式》就很值得一读,另外该书的译者余晟先生所写的《正则指引》也不错。
如果仅用于入门,则《正则表达式必知必会》肯定不能错过,还有网上流传极广的《正则表达式30分钟入门教程》也是不错的入门资料。
但是,结合我自身痛苦的正则表达式学习经历和运用体会,仅有这些是远远不够的。
记得被大家称之为“轮子哥”的大神级程序员vczh在知乎上说过,当初被正则表达式虐得一气之下,干脆自己写了一个正则引擎(源码托管在Github上),才算真正彻底搞懂正则表达式(于是被戏称为“一言不合”就造轮子)。
当然不是每个程序员都能像“轮子哥”这样生猛,但即便都有这么生猛,似乎也没必要都像他一样自己花费大量时间再造一个正则引擎的“轮子”。
那到底应该怎样才能最高性价比(主要是最高时间-成本比)地掌握正则表达式这个神器呢?
2、一旦用过了再也回不去的“神器”
正则表达式是典型的那种你没用过的话,不觉得对自己有什么影响,可是一旦用过了,就再也回不去了的“神器”。
当然,我这里所说的“用过”,不是指简单地会用一些基本功能,而是指能够熟练地运用正则表达式的基本功能和高级功能。只要你用得越熟练,你就会越惊叹于其高效、强大与神奇。
看到这里,我相信某些接触过正则表达式、会使用一些基本功能的童鞋,心里或许在犯嘀咕了:神器是神器,可这玩意儿看起来就像天书一样,也太难学、太难懂了,要达到熟练运用的程度,谈何容易!
短短的一个正则表达式,或许不到10个字符,其中的每个字符都认识,但它们连在一起,却让人越看越迷惑,越想越迷糊……
其实,这正是我学习正则表达式时的真实心情。
是的,正则表达式既然被捧上了神器级别的高度,自然是有着相当强大的功能,而强大的功能就意味着其有非常深厚的内涵,也就意味着有很多需要注意的细节。
3、关键在于掌握其独特的“性格”和“脾气”
注意,我这里并没有说正则表达式是由于复杂而难以理解,这是因为,深厚的内涵不等于复杂,细节很多不意味着难以理解。
看到这里,或许有人有意见了,正则表达式还不算复杂?还不够难理解?
其实,我真正想说的是,繁复或许是真的,杂乱倒未必。因此,简单地说正则表达式复杂,似乎不够准确而客观。
正如跟一个牛叉而又性格独特的人打交道,关键不在于纠结其性格的独特、脾气的古怪,而是重在充分了解并理解其独特的性格、古怪的脾气,然后在此基础上与Ta进行良好的沟通,以便能好好发挥其牛叉之处。
学习并熟练掌握正则表达式的过程也是如此——关键在于先要摸透其“性格”到底独特在哪里,其“脾气”又究竟古怪在何方。一旦摸清楚了其“性格”,其“脾气”,学习起来就事半功倍了。
因此,我下面准备从我自己的角度,先尝试着来分析一下正则表达式那独特的“性格”与古怪的“脾气”,看看究竟为什么正则表达式给那么多人的感觉都是那么难以“亲近”。
正则表达式为什么这么难?
1、高度简洁、高度抽象是难学的关键
正则表达式有一个非常明显的特点:高度简洁、高度抽象。正则表达式中短短的几个字符,或许就代表了一段复杂的处理逻辑和匹配算法。
我们知道,程序代码是对现实事务处理逻辑的抽象,而正则表达式则是对复杂的字符匹配程序代码的进一步抽象;也就是说,高度简洁的正则表达式,其背后所对应的是字符匹配程序代码,而字符匹配程序代码,背后对应的是字符匹配处理逻辑。
因此可以这么认为,字符匹配处理逻辑,抽象为字符匹配程序代码;字符匹配程序代码,再进一步抽象为高度简洁的正则表达式。
所以说,高度简洁的正则表达式也是高度抽象的。
2、其他难学的原因
当然,正则表达式之所以难学、难理解,除了由于正则表达式具有高度简洁、高度抽象的特点之外,大致上应该还有以下几个原因:
1)学习者不求甚解,不了解正则引擎内部的基本原理
作为正则表达式的使用者,不需要深入了解正则引擎内部原理的技术实现细节,那是正则引擎开发者更应该了解的;但若完全不了解其基本工作原理和运行机制,也是不足取的。
2)有多个多义元字符,特别容易使人混淆、迷乱
比如-、+、?、^,尤其是元字符?,既可以作为量词表示其所限定的子表达式为可选(即匹配0次或1次),也可以置于量词之后表示懒惰匹配,而且还有很多特殊分组结构中用到它。
比如(?<name>sub-regex)
、(?:sub-regex)
、(?>sub-regex)
、(?=sub-regex)
、(?!sub-regex)
、(?<=sub-regex)
、(?<!sub-regex)
、(?|sub-regex)
、(?modifier-modifier)
、(?(condition)|)
、(?R)
、(?num)
、(?#comment)
等。
还记得我自己当初刚开始学习的时候,一看到正则表达式中的问号?,就有一种独自在风中凌乱的感觉。
3)转义也是难点
什么情况下需要转义,什么情况下不需要转义,貌似复杂得令人抓狂。
当然,其实是有一定的规律的,掌握了这些规律,再遇到转义问题,就不至于心潮澎湃了。
4)学习期望与学习方法不对
不应该期望一次性记住、学会并熟练运用,正确的学习姿势应该是先简单入门,对一些基本的规则与元字符大致了解一遍,有个印象就好,在需要时再回过头来看,不用刻意去强行记忆。
然后接下来就是在工作中多实践、多运用,边学、边深入、边熟练。
5)有用于入门的好教程、备忘单,也有用于深入的大部头专著,但却缺乏好的速查手册
由于需要边学、边深入、边熟练,因此,平时手头边更需要的不是简单的入门教程、备忘单(Cheat Sheet),也不仅仅是知识点分散于各处的大部头专著(知识点分散导致查找起来不方便,用于深入学习原理是不错,但不够实用),而是一本速查手册。
速查手册按语法元素将知识点综合在一起进行编排的、在需要回过头来看时能够随时快速翻查。
这样,在实践运用中遇到问题就可方便随时快速翻查,而这一点恰恰对于正则表达式这种不可能短期内快速掌握并熟练运用的专业工具的学习与使用非常重要。
6)没有使用好的学习工具
你知道regex101.com
、regexper.com
、RegexBuddy
等正则表达式的专业网站和专业工具吗?
这些堪称学习正则表达式的神器,可令学习事半功倍,但很多人不知道,或知道但很少使用。
什么才是更好的学习姿势
1、从两个不同的角度和维度来看
对于正则表达式的分析和解读,目前大多数文章和书籍多集中在正则表达式自身,比如对正则表达式的各个元字符、元转义序列以及匹配原理的分析和解读上。
当然,这些自然也是很有必要的,而且是学习的主要内容,是理解正则表达式所必需的。然而,很多人在看了大量这类文章和书籍之后,仍然觉得正则表达式很难看懂,不好理解。
难道就没有更好的学习姿势了吗?
其实,理解一个事物,往往可以有不同的角度和维度。多个角度和多个维度相结合,对于事物的理解可以更为深入,而且经常还会有“原来如此”这样豁然开朗的畅快感。
下面是我理解一个事物通常所用到的两个不同的角度和维度:
一是,深入事物本身去理解它内在的原理和机制等底层逻辑; 二是,跳出事物本身,站在一个更高的维度和层面,横向地与其他同类事物进行比较,纵向地去分析它的发展与演变。
正如苏轼那首著名的哲理诗《题西林壁》所说的,“不识庐山真面目,只缘身在此山中”。很多时候往往就是这样,当你只从该事物本身来看的话,就如在云里雾里,是远远不够的。
而一旦跳出到该事物之外,站在更高的一个维度来看,则又正如王安石的《登飞来峰》中所说:“不畏浮云遮望眼,只缘身在最高层”,登高望远,则一览无余了。
2、刨根究底正则表达式的根源和本质
对正则表达式而言,前者正是目前大多数文章和书籍在做的;而后者,却很少有文章和书籍能够跳出正则表达式,站在更高的维度或层面来分析和解读正则表达式。这里就包括了《精通正则表达式》和《正则指引》两书。
这里需要特别强调一下的是,我绝没有贬低上述这两本专著及其作者和/或译者之意,而且恰恰相反,这两本专著正是本系列文章的重要参考书。尤其无论是作为《精通正则表达式》的译者,还是作为《正则指引》的著者,余晟先生都得上是专业而又严谨的。
即便是对于前者而言,目前大多数文章和书籍也多半是一上来就正则表达式本身来说正则表达式,而往往没有去深挖正则表达式背后的根源和本质。
从根源上来讲,正则表达式是为了解决一个基本问题——文本的查找问题(也称为匹配问题);从本质上来讲,正则表达式也是一门编程语言,并且具有典型的编程范式。
当然,正则表达式的功能除了查找文本之外,还包括提取、验证、替换、切分文本等,但这些功能都是在查找文本功能基础上衍生出来的。
然而文本查找问题的解决方案有其历史演变的过程,只有理解了该过程,以及将正则表达式放在该历史过程的背景上,才能更好、更彻底地理解正则表达式。
而站在正则表达式也是一门编程语言的角度,如果能够从纵向编程语言发展史和横向编程范式的角度来看正则表达式,则会有高屋建瓴、纲举目张之感。
后面的文章,我会先从文本查找功能的历史演变过程讲起,随后再从编程语言发展史角度来看看正则表达式到底是一门怎样的语言,以及具有什么典型的编程范式。
然后,在此基础上,再来深入学习正则表达式,我相信,你一定会有跟以往完全不一样的感觉和理解。