让正则表达式更有趣一些!
2015-05-07 20:56
162 查看
原文链接:ComposedRegex
作者:MartinFowler
译者:Abbey(转载请注明出处及译作者)
当人们能以更加抽象和块状的方式理解你的程序细节时,他们就能更快更准确地全面读懂你的程序。——KentBeck
作者:
译者:Abbey(转载请注明出处及译作者)
[2009年7月24日首撰]
将一个庞大的方法分解为若干个良好命名的小型方法,是我们在编写可维护性代码时非常有效的一种方法。这一技巧被KentBeck命名为组合方法模式(ComposedMethodPattern)。当人们能以更加抽象和块状的方式理解你的程序细节时,他们就能更快更准确地全面读懂你的程序。——KentBeck
通过我的尝试发现,这样的分治方式不仅适用于程序中的方法,也同样适用于困扰人们的正则表达式领域。
假设你手里有一份连锁酒店里常住客人的名单,并要按指定的规则统计客人获得的积分。比如说,“在MinasTirithAirport住宿2晚的将获得400点积分。”那么,我们需要从这段规则描述里,依次取出积分点400、住宿天数2以及入住的酒店名称MinasTirithAirport这三项我们关心的内容。
当然,正则表达式对这样的问题无疑是游刃有余的。而且我也相信你一定马上可以写出类似这样的一个正则表达式,并通过分组获得上述三项内容:
conststringpattern=@"^score\s+(\d+)\s+for\s+(\d+)\s+nights?\s+at\s+(.*)";
要理解这个正则表达式并确定其是否正确,我不知道你是否感觉自然。但换作我,我会很仔细地去揣摩它究竟在讲什么。我会逐个分析小括号,以确定这个正则表达式是如何组织的。(实际上,由于这个例子非常简单,所以可能无法完全反映类似更复杂的情况。)
也许你曾被建议按下面这样的方式书写一个正则表达式,并且标上相应的注释。(当在程序中真正要使用这样的正则表达式时,你通常还需要适当的修改和转换。)
protectedoverridestringGetPattern()
{
conststringpattern=
@"^score
\s+
(\d+)#points
\s+
for
\s+
(\d+)#numberofnights
\s+
night
s?#optionalplural
\s+
at
\s+
(.*)#hotelname
";
returnpattern;
}
采用上述方式非常有助于理解,但我一直不太喜欢注释。当然,我并不是说注释不好,尽管我时常因此被人们批评。我是想的说,当有更好选择的时候,为什么还要使用注释这种笨拙的方式呢?实际上,我更愿意通过良好的命名和结构来表达代码的含义,而不是依赖于冗长的注释。(尽管我不并总会成功,但总胜过什么都不做。)
尽管人们通常不会尝试结构化一个正则表达式,但我发现这样做非常有益。比如这样:
conststringscoreKeyword=@"^score\s+";
conststringnumberOfPoints=@"(\d+)";
conststringforKeyword=@"\s+for\s+";
conststringnumberOfNights=@"(\d+)";
conststringnightsAtKeyword=@"\s+nights?\s+at\s+";
conststringhotelName=@"(.*)";
conststringpattern=scoreKeyword+numberOfPoints+forKeyword
+numberOfNights+nightsAtKeyword+hotelName;
我尝试着把一个正则表达式分成了若干个小的部分进行表述,稍后再组成一个完整的正则表达式。如此,我便能以积零成整的方式,更容易地理解整个表达式了。更进一步的,我们还可以把表示空白字符的部分也剔除出来,使之更有意义。就象这样:
conststringspace=@"\s+";
conststringstart="^";
conststringnumberOfPoints=@"(\d+)";
conststringnumberOfNights=@"(\d+)";
conststringnightsAtKeyword=@"nights?\s+at";
conststringhotelName=@"(.*)";
conststringpattern=start+"score"+space+numberOfPoints+space
+"for"+space+numberOfNights+space+nightsAtKeyword
+space+hotelName;
这样做会使表示空白字符的部分更加清晰,却也增加了整个表达式的结构复杂度。所以,我更喜欢之前的那个实现。可它也并非完美,因为所有要捕获的元素都需要以空格分隔,这难免有些拖泥带水。为此,我增加了一个用于组合各个子表达式的方法:
privateStringcomposePattern(paramsString[]arg)
{
return"^"+String.Join(@"\s+",arg);
}
于是,GetPattern方法变成了这样:
conststringnumberOfPoints=@"(\d+)";
conststringnumberOfNights=@"(\d+)";
conststringhotelName=@"(.*)";
conststringpattern=composePattern("score",numberOfPoints,"for",numberOfNights,"nights?","at",hotelName);
当然,你并一定要完全按我所说的做。我只希望你能尽可能让你的正则表达式更具语义、更加清晰可读,而不需要费力的揣度。
[2014年7月31日更新]
之前我使用了局部变量来保存组合正则表达式的各个部分。如果需要在更大范围内使用它们,则可以再做适当的改进,比如构造更为通用的正则表达式。对此,我的同事CarlosVillela指出,如果这些组成表达式的各个部分没有恰当地进行组合,比如括号开闭没有配对,那么将会引发程序的Bug。而我认为这样的担心是多余的,所以让我们忽略不计吧。有些人提出,使用更具语义的FluentAPI(一种内部DSL语言)来替代正则表达式。我想这完全是两码事。只要不是太复杂的情况,我更愿意使用一个轻巧的正则表达式,而不是一个相比庞杂了许多的FluentAPI。当然,这取决于你如何选择。
还有一些人提出使用命名捕获即可。就象对待注释的态度一样,我认为这样确实比原生的正则表达式好,但仍旧比不上结构化的正则表达式。因为被分割成若干碎片的正则表达式,总是比一个完整的表达式更易理解。
相关文章推荐
- 一些有趣的js正则表达式
- 一些常用的正则表达式
- Java正则表达式的总结和一些小例子
- 发一些常用正则表达式!
- 转 Javascript的一些正则表达式 教程
- 正则表达式 (一些比较常用到的!!!)
- 一些用到过的正则表达式
- 正则表达式的一些基本知识
- 总结的一些正则表达式(c#版)
- 收集一些常用的正则表达式
- 一些常用的正则表达式
- 一些常用正则表达式
- js正则表达式的一些研究,截取两个字符串中间的字符串
- javascript的一些常用正则表达式
- 【Java】正则表达式对字符串的一些常用处理
- 自己写的一些小函数.用正则表达式实现一些小功能~
- 用正则表达式过滤脚本的一些研究(asp.net + C#)
- PHP中一些正则表达式的应用
- 常用的一些正则表达式
- 一些常用的 正则表达式,区别于网上其他式子。