[翻译] 让正则表达式更有趣一些!

原文链接:Composed Regex

作者:Martin Fowler

译者:Abbey(转载请注明出处及译作者)


[2009年7月24日 首撰]

将一个庞大的方法分解为若干个良好命名的小型方法,是我们在编写可维护性代码时非常有效的一种方法。这一技巧被Kent Beck命名为组合方法模式(Composed Method Pattern)。

当人们能以更加抽象和块状的方式理解你的程序细节时,他们就能更快更准确地全面读懂你的程序。 —— Kent Beck

通过我的尝试发现,这样的分治方式不仅适用于程序中的方法,也同样适用于困扰人们的正则表达式领域。

假设你手里有一份连锁酒店里常住客人的名单,并要按指定的规则统计客人获得的积分。比如说,“在Minas Tirith Airport住宿2晚的将获得400点积分。”那么,我们需要从这段规则描述里,依次取出积分点400、住宿天数2以及入住的酒店名称Minas Tirith Airport这三项我们关心的内容。

当然,正则表达式对这样的问题无疑是游刃有余的。而且我也相信你一定马上可以写出类似这样的一个正则表达式,并通过分组获得上述三项内容:

const string pattern = @"^score\s+(\d+)\s+for\s+(\d+)\s+nights?\s+at\s+(.*)"; 

要理解这个正则表达式并确定其是否正确,我不知道你是否感觉自然。但换作我,我会很仔细地去揣摩它究竟在讲什么。我会逐个分析小括号,以确定这个正则表达式是如何组织的。(实际上,由于这个例子非常简单,所以可能无法完全反映类似更复杂的情况。)

也许你曾被建议按下面这样的方式书写一个正则表达式,并且标上相应的注释。(当在程序中真正要使用这样的正则表达式时,你通常还需要适当的修改和转换。)

protected override string GetPattern()
{
    const string pattern =
        @"^score
        \s+
        (\d+)          # points
        \s+
        for
        \s+
        (\d+)          # number of nights
        \s+
        night
        s?             #optional plural
        \s+
        at
        \s+
        (.*)           # hotel name
        ";

      return pattern;
}

采用上述方式非常有助于理解,但我一直不太喜欢注释。当然,我并不是说注释不好,尽管我时常因此被人们批评。我是想的说,当有更好选择的时候,为什么还要使用注释这种笨拙的方式呢?实际上,我更愿意通过良好的命名和结构来表达代码的含义,而不是依赖于冗长的注释。(尽管我不并总会成功,但总胜过什么都不做。)

尽管人们通常不会尝试结构化一个正则表达式,但我发现这样做非常有益。比如这样:

const string scoreKeyword = @"^score\s+";
const string numberOfPoints = @"(\d+)";
const string forKeyword = @"\s+for\s+";
const string numberOfNights = @"(\d+)";
const string nightsAtKeyword = @"\s+nights?\s+at\s+";
const string hotelName = @"(.*)";

const string pattern = scoreKeyword + numberOfPoints + forKeyword
                     + numberOfNights + nightsAtKeyword + hotelName;

我尝试着把一个正则表达式分成了若干个小的部分进行表述,稍后再组成一个完整的正则表达式。如此,我便能以积零成整的方式,更容易地理解整个表达式了。更进一步的,我们还可以把表示空白字符的部分也剔除出来,使之更有意义。就象这样:

const string space = @"\s+";
const string start = "^";
const string numberOfPoints = @"(\d+)";
const string numberOfNights = @"(\d+)";
const string nightsAtKeyword = @"nights?\s+at";
const string hotelName = @"(.*)";

const string pattern = start + "score" + space + numberOfPoints + space
                     + "for" + space + numberOfNights + space + nightsAtKeyword
                     + space + hotelName;

这样做会使表示空白字符的部分更加清晰,却也增加了整个表达式的结构复杂度。所以,我更喜欢之前的那个实现。可它也并非完美,因为所有要捕获的元素都需要以空格分隔,这难免有些拖泥带水。为此,我增加了一个用于组合各个子表达式的方法:

private String composePattern(params String[] arg)
{
    return "^" + String.Join(@"\s+", arg);
}

于是,GetPattern方法变成了这样:

const string numberOfPoints = @"(\d+)";
const string numberOfNights = @"(\d+)";
const string hotelName = @"(.*)";

const string pattern = composePattern("score", numberOfPoints, "for", numberOfNights, "nights?", "at", hotelName);

当然,你并一定要完全按我所说的做。我只希望你能尽可能让你的正则表达式更具语义、更加清晰可读,而不需要费力的揣度。


[2014年7月31日 更新]

之前我使用了局部变量来保存组合正则表达式的各个部分。 如果需要在更大范围内使用它们,则可以再做适当的改进,比如构造更为通用的正则表达式。对此,我的同事Carlos Villela指出,如果这些组成表达式的各个部分没有恰当地进行组合,比如括号开闭没有配对,那么将会引发程序的Bug。而我认为这样的担心是多余的,所以让我们忽略不计吧。

有些人提出,使用更具语义的Fluent API(一种内部DSL语言)来替代正则表达式。我想这完全是两码事。只要不是太复杂的情况,我更愿意使用一个轻巧的正则表达式,而不是一个相比庞杂了许多的Fluent API。当然,这取决于你如何选择。

还有一些人提出使用命名捕获即可。就象对待注释的态度一样,我认为这样确实比原生的正则表达式好,但仍旧比不上结构化的正则表达式。因为被分割成若干碎片的正则表达式,总是比一个完整的表达式更易理解。

时间: 2024-10-10 03:47:01

[翻译] 让正则表达式更有趣一些!的相关文章

华曦达科技:让电视变得更有趣

让电视变得更有趣,是华曦达科技的核心产品设计追求的价值观. 在如今这个时代,重视产品设计价值观的企业已经愈发减少.功利性的大环境下,更多的很浮躁的去追求单一的成本减少,一味的减少减少设计成本,不重视用户的体验已经成为很多中小型公司的不良价值观. 如果一个产品的设计缺乏从消费者.用户的角度出发的生产观点,那么生产时不仅会耗费大量费用来调整和更换设备.物料和劳动力.并且最后生产出来后,带给用户的只是完全失败的使用体验. 相反,好的产品设计,以消费者.用户的需求作为出发点.(我们华曦达科技就是这样做的

判断汉字正则表达式更严谨方法!

一.通常做法 正如网上流传的,判断中文的正则表达式,绝大部分是这么写的(OC语言): NSPredicate* predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",@"[\u4e00-\u9fa5]"]; /*判断是否为中文的正则表达式*/ if([predicate evaluateWithObject:name]){ //是中文 }else{ //不是中文 } 然而上面的正则表达式,逻辑

AAAI 2020论文分享:通过识别和翻译交互打造更优的语音翻译模型

2月初,AAAI 2020在美国纽约拉开了帷幕.本届大会百度共有28篇论文被收录.本文将对其中的机器翻译领域入选论文<Synchronous Speech Recognition and Speech-to-Text Translation with Interactive Decoding>进行解读. 一.研究背景 语音翻译技术是指利用计算机实现从一种语言的语音到另外一种语言的语音或文本的自动翻译过程.该技术可以广泛应用于会议演讲.商业会谈.跨境客服.出国旅游等各个领域和场景,具有重要的研究

用Less定义常用的CSS3效果函数及常用颜色搭配(让CSS写起来更有趣)

定义圆角及调用 /* 定义圆角 @radius 圆角大小 */ .round(@radius:5px){ border-radius:@radius; -webkit-border-radius: @radius; -moz-border-radius: @radius; } .round7{ .round(7px); } 定义盒子阴影及调用 /* 盒子阴影 @right_left 右边阴影为正数 左边负数 @bottom_top 下边阴影为正数 上边负数 @box 阴影大小 @box_colo

WOW.js – 让页面滚动更有趣

官网:http://mynameismatthieu.com/WOW/ 建议去官网一看 下载地址:https://github.com/matthieua/WOW 浏览器兼容 IE10+  Chrome  Firefox  Opera  Safari  IE6.IE7 等老旧浏览器不支持 CSS3 动画,所以没有效果:而 wow.js 也使用了 querySelectorAll 方法,IE 低版本会报错.为了达到更好的兼容,最好加一个浏览器及版本判断. 1.wow.js依赖于animate.cs

PowerBI技巧:玩UniCode函数,让表格更有趣

正文开始:百度百科:UniCode(统一码.万国码.单一码)是计算机科学领域里的一项业界标准,包括字符集.编码方案等.Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言.跨平台进行文本转换.处理的要求.1990年开始研发,1994年正式公布.关于UniCode的历史,大家自行查询. 其实在我们每天生活中,经常遇到它,如:1.输入法中软键盘,含:特殊符号.制表符等2.咱们聊天经常用的表情 它非常丰富,且跨平台.跨国界,在

实体框架6.0(Recipes)翻译系列 1 -----第一章 开始使用实体框架1

微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF版本更新太快,没人愿意去花时间翻译国外关于EF的书籍.使用Entity Framework开发已经有3年多了,但用得很肤浅,最近想深入学习,只好找来英文书<Entity Framework 6 Recipes>慢慢啃.首先需要说明的是,我英文不好,只是为了学习EF.把学习的过程写成博客,一是督促自

[iOS翻译]《The Swift Programming Language》系列:Welcome to Swift-01

本文转载至:http://www.cnblogs.com/yangfaxian/p/3765081.html 全书目录: 一.Welcome to Swift 二.Language Guide 三.Language Reference /* 译者的废话: 几个小时前熬夜看了WWDC,各种激动,今年很有料啊!当看到Swift出来的时候,瞬间傻眼,又要学习新语言了.这篇文章来自苹果官方的<The Swift Programming Language>一书,500页左右,在苹果官网有下载.Swift

《Entity Framework 6 Recipes》翻译系列 (1) -----第一章 开始使用实体框架之历史和框架简述 (转)

微软的Entity Framework 受到越来越多人的关注和使用,Entity Framework7.0版本也即将发行.虽然已经开源,可遗憾的是,国内没有关于它的书籍,更不用说好书了,可能是因为EF版本更新太快,没人愿意去花时间翻译国外关于EF的书籍.使用Entity Framework开发已经有3年多了,但用得很肤浅,最近想深入学习,只好找来英文书<Entity Framework 6 Recipes>第二版,慢慢啃.首先需要说明的是,我英文不好,只是为了学习EF.把学习的过程写成博客,一