站長資訊網
        最全最豐富的資訊網站

        [php]正則表達式的五個成功習慣

        正則表達式難于書寫、難于閱讀、難于維護,經常錯誤匹配意料不到的文本或者錯過了有效的文本,這些問題都是由正則表達式的表現和能力引起的。每個元字符(metacharacter)的能力和細微差別組合在一起,使得代碼不借助于智力技巧就無法解釋。 
             許多包含一定特性的工具使閱讀和編寫正則表達式變得容易了,但是它們又很不符合習慣。對于很多程序員來說,書寫正則表達式就是一種魔法藝術。他們堅持自己所知道的特征并持有絕對樂觀的態度。如果你愿意采用本文所探討的五個習慣,你將可以讓你設計的正則表達式經受的住反復試驗。 
            本文將使用Perl、PHP和Python語言作為代碼示例,但是本文的建議幾乎適用于任何替換表達式(regex)的執行。 

            一、使用空格和注釋 
            對于大部分程序員來說,在一個正則表達式環境里使用空格和縮進排列都不成問題,如果他們沒有這么做一定會被同行甚至外行人士看笑話。幾乎每個人都知道把代碼擠在一行會難于閱讀、書寫和維護。對于正則表達式又有什么不同呢? 
            大部分替換表達式工具都具有擴展的空格特性,這允許程序員把他們的正則表達式擴展為多行,并在每一行結尾加上注釋。為什么只有少部分程序員利用這個特性呢?Perl 6的正則表達式默認就是擴展空格的模式。不要再讓語言替你默認擴展空格了,自己主動利用吧。 
            記住擴展空格的竅門之一就是讓正則表達式引擎忽略擴展空格。這樣如果你需要匹配空格,你就不得不明確說明。 
            在Perl語言里面,在正則表達式的結尾加上x,這樣“m/foo|bar/”變為如下形式: 
        m/ 
          foo 

          bar 
         /x 
            在PHP語言里面,在正則表達式的結尾加上x,這樣“”/foo|bar/””變為如下形式: 
        “/ 
          foo 

          bar 
         /x” 
            在Python語言里面,傳遞模式修飾參數“re.VERBOSE”得到編譯函數如下: 
        pattern = r”’ 
         foo 

         bar 
        ”’ 
        regex = re.compile(pattern, re.VERBOSE) 
            處理更加復雜的正則表達式時,空格和注釋就更能體現出其重要性。假設下面的正則表達式用于匹配美國的電話號碼: 
        (?d{3})? ?d{3}[-.]d{4} 
             這個正則表達式匹配電話號碼如“(314)555-4000”的形式,你認為這個正則表達式是否匹配“314-555-4000”或者“555- 4000”呢?答案是兩種都不匹配。寫上這么一行代碼隱蔽了缺點和設計結果本身,電話區號是需要的,但是正則表達式在區號和前綴之間缺少一個分隔符號的說明。 
            把這一行代碼分成幾行并加上注釋將把缺點暴露無疑,修改起來顯然更容易一些。 
            在Perl語言里面應該是如下形式: 
        /   
            (?     # 可選圓括號 
              d{3} # 必須的電話區號 
            )?     # 可選圓括號 
            [-s.]? # 分隔符號可以是破折號、空格或者句點 
              d{3} # 三位數前綴 
            [-.]    # 另一個分隔符號 
              d{4} # 四位數電話號碼 
        /x 
            改寫過的正則表達式現在在電話區號后有一個可選擇的分隔符號,這樣它應該是匹配“314-555-4000”的,然而電話區號還是必須的。另一個程序員如果需要把電話區號變為可選項則可以迅速看出它現在不是可選的,一個小小的改動就可以解決這個問題。 

            二、書寫測試 
            一共有三個層次的測試,每一層為你的代碼加上一層可靠性。首先,你需要認真想想你需要匹配什么代碼以及你是否能夠處理錯誤匹配。其次,你需要利用數據實例來測試正則表達式。最后,你需要正式通過一個測試小組的測試。 
             決定匹配什么其實就是在匹配錯誤結果和錯過正確結果之間尋求一個平衡點。如果你的正則表達式過于嚴格,它將會錯過一些正確匹配;如果它過于寬松,它將會產生一個錯誤匹配。一旦某個正則表達式發放到實際代碼當中,你可能不會兩者都注意到。考慮一下上面電話號碼的例子,它將會匹配“800-555-4000  = -5355”。錯誤的匹配其實很難發現,所以提前規劃做好測試是很重要的。 
            還是使用電話號碼的例子,如果你在Web表單里面確認一個電話號碼,你可能只要滿足于任何格式的十位數字。但是,如果你想從大量文本里面分離電話號碼,你可能需要很認證的排除不符合要求的錯誤匹配。 
            在考慮你想匹配的數據的時候,寫下一些案例情況。針對案例情況寫下一些代碼來測試你的正則表達式。任何復雜的正則表達式都最好寫個小程序測試一下,可以采用下面的具體形式。 
            在Perl語言里面: 
        #!/usr/bin/perl 

        my @tests = ( “314-555-4000”, 
                      “800-555-4400”, 
               “(314)555-4000”, 
                      “314.555.4000”, 
                      “555-4000”, 
                      “aasdklfjklas”, 
                      “1234-123-12345”           
                    ); 

        foreach my $test (@tests) { 
            if ( $test =~ m/ 
                           (?     # 可選圓括號 
                             d{3} # 必須的電話區號 
                           )?     # 可選圓括號 
                           [-s.]? # 分隔符號可以是破折號、空格或者句點 
                             d{3} # 三位數前綴 
                           [-s.]  # 另一個分隔符號 
                             d{4} # 四位數電話號碼 
                           /x ) { 
                print “Matched on $testn”; 
             } 
             else { 
                print “Failed match on $testn”; 
             } 

            在PHP語言里面: 
        <?php 
        $tests = array( “314-555-4000”, 
                   “800-555-4400”, 
                   “(314)555-4000”, 
                   “314.555.4000”, 
                   “555-4000”, 
                   “aasdklfjklas”, 
                   “1234-123-12345” 
                  ); 

        $regex = “/ 
                    (?     # 可選圓括號 
                      d{3} # 必須的電話區號 
                    )?     # 可選圓括號 
                    [-s.]? # 分隔符號可以是破折號、空格或者句點 
                      d{3} # 三位數前綴 
                    [-s.]  # 另一個分隔符號 
                      d{4} # 四位數電話號碼 
                   /x”; 

        foreach ($tests as $test) { 
            if (preg_match($regex, $test)) {  
                echo “Matched on $test<br />;”; 
            } 
            else { 
                echo “Failed match on $test<br />;”; 
             } 

        ?>; 

                在Python語言里面: 
        import re 

        tests = [“314-555-4000”, 
                 “800-555-4400”, 
                 “(314)555-4000”, 
                 “314.555.4000”, 
                 “555-4000”, 
                 “aasdklfjklas”, 
                 “1234-123-12345”         
                ] 

        pattern = r”’ 
        (?                 # 可選圓括號 
                      d{3} # 必須的電話區號 
                    )?     # 可選圓括號 
                    [-s.]? # 分隔符號可以是破折號、空格或者句點 
                      d{3} # 三位數前綴 
                    [-s.]  # 另一個分隔符號 
                      d{4} # 四位數電話號碼 
                   ”’ 

        regex = re.compile( pattern, re.VERBOSE ) 

        for test in tests: 
            if regex.match(test): 
                print “Matched on”, test, “n” 
            else: 
                print “Failed match on”, test, “n” 

            運行測試代碼將會發現另一個問題:它匹配“1234-123-12345”。 
             理論上,你需要整合整個程序所有的測試到一個測試小組里面。即使你現在還沒有測試小組,你的正則表達式測試也會是一個小組的良好基礎,現在正是開始創建的好機會。即使現在還不是創建的合適時間,你也應該在每次修改以后運行測試一下正則表達式。這里花費一小段時間將會減少你很多麻煩事。 

            三、為交替操作分組 
            交替操作符號(|)的優先級很低,這意味著它經常交替超過程序員所設計的那樣。比如,從文本里面抽取Email地址的正則表達式可能如下: 
        ^CC:|To:(.*) 
            上面的嘗試是不正確的,但是這個bug往往不被注意。上面代碼的意圖是找到“CC:”或者“To:”開始的文本,然后在這一行的后面部分提取Email地址。 
             不幸的是,如果某一行中間出現“To:”,那么這個正則表達式將捕獲不到任何以“CC:”開始的一行,而是抽取幾個隨機的文本。坦白的說,正則表達式匹配 “CC:”開始的一行,但是什么都捕獲不到;或者匹配任何包含“To:”的一行,但是把這行的剩余文本都捕獲了。通常情況下,這個正則表達式會捕獲大量 Email地址,所有沒有人會注意這個bug。 
            如果要符合實際意圖,那么你應該加入括號說明清楚,正則表達式如下: 
        (^CC:)|(To:(.*)) 
            如果真正意圖是捕獲以“CC:”或者“To:”開始的文本行的剩余部分,那么正確的正則表達式如下: 
        ^(CC:|To:)(.*) 
            這是一個普遍的不完全匹配的bug,如果你養成為交替操作分組的習慣,你就會避免這個錯誤。 

            四、使用寬松數量詞 
            很多程序員避免使用寬松數量詞比如“*?”、“+?”和“??”,即使它們會使這個表達式易于書寫和理解。 
             寬松數量詞可以盡可能少的匹配文本,這樣有助于完全匹配的成功。如果你寫了“foo(.*?)bar”,那么數量詞將在第一次遇到“bar”時就停止匹配,而不是在最后一次。如果你希望從“foo###bar+++bar”中捕獲“###”,這一點就很重要。一個嚴格數量詞將捕獲“###bar++ +”。 
            假設你要從HTML文件里面捕獲所有電話號碼,你可能會使用我們上文討論過的電話號碼正則表達式的例子。但是,如果你知道所有電話號碼都在一個表格的第一列里面,你可以使用寬松數量詞寫出更簡單的正則表達式: 
        <tr>;<td>;(.+?)<td>; 
            很多剛起步的程序員不使用寬松數量詞來否定特定種類。他們能寫出下面的代碼: 
        <tr>;<td>;([^>;]+)</td>; 
            這種情況下它可以正常運行,但是如果你想捕獲的文本包含有你分隔的公共字符(這種情況下比如</td>;),這將會帶來很大麻煩。如果你使用了寬松數量詞,你只要花上很少的時間組裝字符種類就能產生新的正則表達式。 
            在你知道你要捕獲文本的環境結構時,寬松數量詞是具有很大價值的。 

            五、利用可用分界符 
            Perl 和PHP語言常常使用左斜線(/)來標志一個正則表達式的開頭和結尾,Python語言使用一組引號來標志開頭和結尾。如果在Perl和PHP中堅持使用左斜線,你將要避免表達式中的任何斜線;如果在Python中使用引號,你將要避免使用反斜線()。選擇不同的分界符或引號可以允許你避免一半的正則表達式。這將使得表達式易于閱讀,減少由于忘記避免符號而潛在的bug。 
            Perl和PHP語言允許使用任何非數字和空格字符作為分界符。如果你切換到一個新的分界符,在匹配URL或HTML標志(如“http://”或“<br/>;”)時,你就可以避免漏掉左斜線了。 
            例如,“/http://(S)*/”可以寫為“#http://(S)*#”。 
            通用分界符是“#”、“!”和“|”。如果你要使用方括號、尖括號或者花括號,只要保持前后配對出現就可以了。下面就是一些通用分界符的示例: 
        #…# !…! {…} s|…|…| (Perl only) s[…][…] (Perl only) s<…>;/…/ (Perl only)  
             在Python中,正則表達式首先會被當作一個字符串。如果你使用引號作為分界符,你將漏掉所有反斜線。但是你可以使用“r””字符串避免這個問題。如果針對“re.VERBOSE”選項使用三個連續單引號,它將允許你包含換行。例如 regex = “(\w+)(\d+)”可以寫出下面的形式: 
        regex = r”’ 
                   (w+) 
                   (d+) 
                 ”’ 

            小結:本文的建議主要著眼于正則表達式的可讀性,在開發中養成這些習慣,你將會更加清晰的考慮設計和表達式的結構,這將有助于減少bug和代碼的維護,如果你自己就是這個代碼的維護者你將倍感輕松。 

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 国产精品主播一区二区| 九九久久精品国产| 国产叼嘿久久精品久久| 欧美精品中文字幕亚洲专区| 国产精品亚洲w码日韩中文| 91精品一区国产高清在线| 亚洲精品视频在线观看你懂的| 国产VA免费精品高清在线| 99久久这里只有精品| 欧洲精品99毛片免费高清观看| 精品国产不卡一区二区三区 | 欧美精品国产日韩综合在线| 四虎国产精品永久在线观看| 久久久99精品一区二区| 99久久精品国产一区二区蜜芽| 91精品国产乱码久久久久久 | 亚洲日本精品一区二区| 国产成人vr精品a视频| 亚洲精品无码久久久影院相关影片 | 亚洲国产美女精品久久久久∴ | 欧美成人精品欧美一级乱黄一区二区精品在线 | 久久99精品综合国产首页| 国产欧美亚洲精品A| 欧洲精品99毛片免费高清观看| 尤物yw午夜国产精品视频| 欧美激情精品久久久久久久九九九| 国产玖玖玖九九精品视频| 91自慰精品亚洲| 欧美高清在线精品一区| 国产精品亚洲精品| 久久国产精品久久国产精品| 538国产精品一区二区在线| 精品人妻人人做人人爽| 国产精品熟女高潮视频| 国产麻豆精品一区二区三区v视界 国产麻豆一精品一AV一免费 | 2022国内精品免费福利视频| 国产精品毛片VA一区二区三区| 国产人妖乱国产精品人妖| 精品国产午夜理论片不卡| 国内精品久久久久影院优| 国产伦精品一区二区三区|