讓正規表示式更有趣一些!

2022-04-03 23:16:34 字數 3053 閱讀 5945

將乙個龐大的方法分解為若干個良好命名的小型方法,是我們在編寫可維護性**時非常有效的一種方法。這一技巧被kent beck命名為組合方法模式(composed method pattern)。

當人們能以更加抽象和塊狀的方式理解你的程式細節時,他們就能更快更準確地全面讀懂你的程式。 —— kent beck

通過我的嘗試發現,這樣的分治方式不僅適用於程式中的方法,也同樣適用於困擾人們的正規表示式領域。

假設你手裡有乙份連鎖酒店裡常住客人的名單,並要按指定的規則統計客人獲得的積分。比如說,「在minas tirith airport住宿2晚的將獲得400點積分。」那麼,我們需要從這段規則描述裡,依次取出積分點400、住宿天數2以及入住的酒店名稱minas tirith airport這三項我們關心的內容。

當然,正規表示式對這樣的問題無疑是游刃有餘的。而且我也相信你一定馬上可以寫出類似這樣的乙個正規表示式,並通過分組獲得上述三項內容:

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

要理解這個正規表示式並確定其是否正確,我不知道你是否感覺自然。但換作我,我會很仔細地去揣摩它究竟在講什麼。我會逐個分析小括號,以確定這個正規表示式是如何組織的。(實際上,由於這個例子非常簡單,所以可能無法完全反映類似更複雜的情況。)

也許你曾被建議按下面這樣的方式書寫乙個正規表示式,並且標上相應的注釋。(當在程式中真正要使用這樣的正規表示式時,你通常還需要適當的修改和轉換。)

override

string getpattern()

採用上述方式非常有助於理解,但我一直不太喜歡注釋。當然,我並不是說注釋不好,儘管我時常因此被人們批評。我是想的說,當有更好選擇的時候,為什麼還要使用注釋這種笨拙的方式呢?實際上,我更願意通過良好的命名和結構來表達**的含義,而不是依賴於冗長的注釋。(儘管我不並總會成功,但總勝過什麼都不做。)

儘管人們通常不會嘗試結構化乙個正規表示式,但我發現這樣做非常有益。比如這樣:

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;

我嘗試著把乙個正規表示式分成了若干個小的部分進行表述,稍後再組成乙個完整的正規表示式。如此,我便能以積零成整的方式,更容易地理解整個表示式了。更進一步的,我們還可以把表示空白字元的部分也剔除出來,使之更有意義。就象這樣:

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;

這樣做會使表示空白字元的部分更加清晰,卻也增加了整個表示式的結構複雜度。所以,我更喜歡之前的那個實現。可它也並非完美,因為所有要捕獲的元素都需要以空格分隔,這難免有些拖泥帶水。為此,我增加了乙個用於組合各個子表示式的方法:

於是,getpattern方法變成了這樣:

string numberofpoints = @"(\d+)";

const

string numberofnights = @"(\d+)";

const

string hotelname = @"(.*)";

const

string pattern = composepattern("score", numberofpoints, "for", numberofnights, "nights?", "at", hotelname);

當然,你並一定要完全按我所說的做。我只希望你能盡可能讓你的正規表示式更具語義、更加清晰可讀,而不需要費力的揣度。

之前我使用了區域性變數來儲存組合正規表示式的各個部分。 如果需要在更大範圍內使用它們,則可以再做適當的改進,比如構造更為通用的正規表示式。對此,我的同事carlos villela指出,如果這些組成表示式的各個部分沒有恰當地進行組合,比如括號開閉沒有配對,那麼將會引發程式的bug。而我認為這樣的擔心是多餘的,所以讓我們忽略不計吧。

有些人提出,使用更具語義的fluent api(一種內部dsl語言)來替代正規表示式。我想這完全是兩碼事。只要不是太複雜的情況,我更願意使用乙個輕巧的正規表示式,而不是乙個相比龐雜了許多的fluent api。當然,這取決於你如何選擇。

還有一些人提出使用命名捕獲即可。就象對待注釋的態度一樣,我認為這樣確實比原生的正規表示式好,但仍舊比不上結構化的正規表示式。因為被分割成若干碎片的正規表示式,總是比乙個完整的表示式更易理解。

一些正規表示式

要嚴格的驗證手機號碼,必須先要清楚現在已經開放了哪些數字開頭的號碼段,目前國內號碼段分配如下 移動 134 135 136 137 138 139 150 151 157 td 158 159 187 188 聯通 130 131 132 152 155 156 185 186 電信 133 153...

一些正規表示式

判斷是否是正整數if isnan paramvalue paramvalue 0 else 金額的格式判斷輸入金額的要求 整數字最多十位,小數為最多為兩位,可以無小數字 0 9 1 9 0 9 0 9 function checkmoney str 0 9 if re.test str else 手...

一些正規表示式

記錄一下 以防忘記 string hello 123 4567 world this is a regsssss res re.match w s d s d s w string 匹配到 hello 123 4567 world this 其中 代表乙個字串的開始 代表乙個字串的結尾 w 匹配字母...