2009-04-02 10 views
39

Ho iniziato a pensare che l'uso di espressioni regolari diminuisca la manutenibilità del codice. C'è qualcosa di malvagio riguardo alla chiarezza e al potere delle espressioni regolari. Perl lo mescola con effetti collaterali come gli operatori di default.Come scrivere più espressioni regolari mantenibili?

I DO ha l'abitudine di documentare le espressioni regolari con almeno una frase che fornisce l'intento di base e almeno un esempio di ciò che corrisponderebbe.

Poiché vengono create espressioni regolari, ritengo sia assolutamente necessario commentare i componenti più grandi di ciascun elemento nell'espressione. Nonostante questo anche le mie stesse espressioni regolari mi fanno grattarmi la testa come se stessi leggendo Klingon.

Hai intenzionalmente ridotto le tue espressioni regolari? Scomponete forse quelli più corti e più potenti in passi più semplici? Ho rinunciato ad annidare espressioni regolari. Esistono costrutti di espressioni regolari evitate a causa di problemi di maininabilità?

Non lasciare che questo esempio offuschi la domanda.

Se quanto segue da Michael Ash aveva una sorta di bug in esso, avresti qualche possibilità di fare qualsiasi cosa se non di buttare tutto via?

^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$ 

Per richiesta lo scopo esatto può essere trovato tramite collegamento del signor Ash sopra.

corrispondenze 01.1.02 | 11-30-2001 | 29/2/2000

Non corrispondenti 02/29/01 | 13/01/2002 | 11/00/02

+0

In attesa di risposte su questo. Tendo a pensare che non c'è una buona risposta, tanto quanto amo regex;). – RedBlueThing

+4

Per mantenere le vostre espressioni regolari e mantenibili suggerisco succo di prugna. – jerebear

+0

Per quanto riguarda la regex grande e pelosa: sì, lo farei. Alcune prospettive, almeno. Non è davvero più illeggibile del capitolo medio di Dostoevskij. – chaos

risposta

17

Di solito provo solo a racchiudere tutte le mie chiamate di espressioni regolari all'interno della propria funzione, con un nome significativo e alcuni commenti di base. Mi piace pensare alle espressioni regolari come a un linguaggio di sola scrittura, leggibile solo da chi l'ha scritto (a meno che non sia molto semplice). Mi aspetto che qualcuno debba probabilmente riscrivere completamente l'espressione se dovessero cambiare il suo intento e questo è probabilmente il modo migliore per mantenere in vita l'allenamento delle espressioni regolari.

+0

Incapsula ciò che fa con significato funzionale. Questa è una buona pratica che non ho impiegato. – ojblass

+0

Sì, ha funzionato abbastanza bene per molti dei progetti più grandi con cui sono stato coinvolto. – James

+1

In realtà, questo è un buon approccio. Se correttamente denominato e documentato, è possibile rilasciare un'opzione non RE se il RE non è più raggiungibile. Non sono d'accordo che dovrebbero essere solo scritti, ma +1 in ogni caso. – paxdiablo

0

Non mi aspetto che le espressioni regolari per essere leggibile, quindi ho solo lasciare così come sono, e riscrivere se necessario.

+0

Non pensi di aver introdotto bug con questa abitudine? – ojblass

+0

Potrebbe rappresentare un rischio in alcuni scenari ma non in altri.Per esempio, mi sentirei sicuro di farlo da solo se fossero presenti test unitari completi riguardanti ogni caso d'uso per l'espressione regolare. Senza questi test, sarebbe sicuramente spaventoso! –

+3

affatto, cambiarli è più rischioso che riscriverli nella maggior parte dei casi, devi capire l'intero ambito. Unittest è d'obbligo ogni volta che usi espressioni regolari complesse –

1

Potrei ancora lavorarci. Vorrei solo usare Regulator. Una cosa che ti permette di fare è salvare la regex insieme ai dati di test per questo.

Naturalmente, potrei anche aggiungere commenti.


Ecco cosa ha prodotto Expresso. Non avevo mai usato prima, ma ora, regolatore è senza lavoro:

 
// using System.Text.RegularExpressions; 

/// 
/// Regular expression built for C# on: Thu, Apr 2, 2009, 12:51:56 AM 
/// Using Expresso Version: 3.0.3276, http://www.ultrapico.com 
/// 
/// A description of the regular expression: 
/// 
/// Select from 3 alternatives 
///  ^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$ 
///   Beginning of line or string 
///   Match expression but don't capture it. [(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2)] 
///    Select from 2 alternatives 
///     (?:(?:0?[13578]|1[02])(\/|-|\.)31)\1 
///      Match expression but don't capture it. [(?:0?[13578]|1[02])(\/|-|\.)31] 
///       (?:0?[13578]|1[02])(\/|-|\.)31 
///        Match expression but don't capture it. [0?[13578]|1[02]] 
///         Select from 2 alternatives 
///          0?[13578] 
///           0, zero or one repetitions 
///           Any character in this class: [13578] 
///          1[02] 
///           1 
///           Any character in this class: [02] 
///        [1]: A numbered capture group. [\/|-|\.] 
///         Select from 3 alternatives 
///          Literal/
///          - 
///          Literal . 
///        31 
///      Backreference to capture number: 1 
///     (?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2) 
///      Return 
///      New line 
///      Match expression but don't capture it. [(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2] 
///       (?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2 
///        Match expression but don't capture it. [0?[13-9]|1[0-2]] 
///         Select from 2 alternatives 
///          0?[13-9] 
///           0, zero or one repetitions 
///           Any character in this class: [13-9] 
///          1[0-2] 
///           1 
///           Any character in this class: [0-2] 
///        [2]: A numbered capture group. [\/|-|\.] 
///         Select from 3 alternatives 
///          Literal/
///          - 
///          Literal . 
///        Match expression but don't capture it. [29|30] 
///         Select from 2 alternatives 
///          29 
///           29 
///          30 
///           30 
///        Backreference to capture number: 2 
///   Return 
///   New line 
///   Match expression but don't capture it. [(?:1[6-9]|[2-9]\d)?\d{2}] 
///    (?:1[6-9]|[2-9]\d)?\d{2} 
///     Match expression but don't capture it. [1[6-9]|[2-9]\d], zero or one repetitions 
///      Select from 2 alternatives 
///       1[6-9] 
///        1 
///        Any character in this class: [6-9] 
///       [2-9]\d 
///        Any character in this class: [2-9] 
///        Any digit 
///     Any digit, exactly 2 repetitions 
///   End of line or string 
///  ^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$ 
///   Beginning of line or string 
///   Match expression but don't capture it. [0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))] 
///    0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))) 
///     0, zero or one repetitions2 
///     [3]: A numbered capture group. [\/|-|\.] 
///      Select from 3 alternatives 
///       Literal/
///       - 
///       Literal . 
///     29 
///     Backreference to capture number: 3 
///     Match expression but don't capture it. [(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))] 
///      Match expression but don't capture it. [(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)] 
///       Select from 2 alternatives 
///        (?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26]) 
///         Match expression but don't capture it. [1[6-9]|[2-9]\d], zero or one repetitions 
///          Select from 2 alternatives 
///           1[6-9] 
///            1 
///            Any character in this class: [6-9] 
///           [2-9]\d 
///            Any character in this class: [2-9] 
///            Any digit 
///         Match expression but don't capture it. [0[48]|[2468][048]|[13579][26]] 
///          Select from 3 alternatives 
///           0[48] 
///            0 
///            Any character in this class: [48] 
///           [2468][048] 
///            Any character in this class: [2468] 
///            Any character in this class: [048] 
///           [13579][26] 
///            Any character in this class: [13579] 
///            Any character in this class: [26] 
///        (?:(?:16|[2468][048]|[3579][26])00) 
///         Return 
///         New line 
///         Match expression but don't capture it. [(?:16|[2468][048]|[3579][26])00] 
///          (?:16|[2468][048]|[3579][26])00 
///           Match expression but don't capture it. [16|[2468][048]|[3579][26]] 
///            Select from 3 alternatives 
///             16 
///              16 
///             [2468][048] 
///              Any character in this class: [2468] 
///              Any character in this class: [048] 
///             [3579][26] 
///              Any character in this class: [3579] 
///              Any character in this class: [26] 
///           00 
///   End of line or string 
///  ^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$ 
///   Beginning of line or string 
///   Match expression but don't capture it. [(?:0?[1-9])|(?:1[0-2])] 
///    Select from 2 alternatives 
///     Match expression but don't capture it. [0?[1-9]] 
///      0?[1-9] 
///       0, zero or one repetitions 
///       Any character in this class: [1-9] 
///     Match expression but don't capture it. [1[0-2]] 
///      1[0-2] 
///       1 
///       Any character in this class: [0-2] 
///   Return 
///   New line 
///   [4]: A numbered capture group. [\/|-|\.] 
///    Select from 3 alternatives 
///     Literal/
///     - 
///     Literal . 
///   Match expression but don't capture it. [0?[1-9]|1\d|2[0-8]] 
///    Select from 3 alternatives 
///     0?[1-9] 
///      0, zero or one repetitions 
///      Any character in this class: [1-9] 
///     1\d 
///      1 
///      Any digit 
///     2[0-8] 
///      2 
///      Any character in this class: [0-8] 
///   Backreference to capture number: 4 
///   Match expression but don't capture it. [(?:1[6-9]|[2-9]\d)?\d{2}] 
///    (?:1[6-9]|[2-9]\d)?\d{2} 
///     Match expression but don't capture it. [1[6-9]|[2-9]\d], zero or one repetitions 
///      Select from 2 alternatives 
///       1[6-9] 
///        1 
///        Any character in this class: [6-9] 
///       [2-9]\d 
///        Any character in this class: [2-9] 
///        Any digit 
///     Any digit, exactly 2 repetitions 
///   End of line or string 
/// 
/// 
/// 
public static Regex regex = new Regex(
     "^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.)31)\\1|\r\n(?:(?:0?[13-9]"+ 
     "|1[0-2])(\\/|-|\\.)(?:29|30)\\2))\r\n(?:(?:1[6-9]|[2-9]\\d)?\\d"+ 
     "{2})$|^(?:0?2(\\/|-|\\.)29\\3(?:(?:(?:1[6-9]|[2-9]\\d)?(?:0["+ 
     "48]|[2468][048]|[13579][26])|\r\n(?:(?:16|[2468][048]|[3579][2"+ 
     "6])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))\r\n(\\/|-|\\.)(?:0?[1-9"+ 
     "]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$", 
    RegexOptions.CultureInvariant 
    | RegexOptions.Compiled 
    ); 

+0

Sarebbe interessante vedere che cosa questo strumento genera per l'esempio RE nella domanda. Stavo per provare ma non potevo essere disturbato a scaricare e installare. Vuoi fare un tentativo? – paxdiablo

+0

Ho provato molto velocemente. Non gli piace molto, almeno non nell'analizzatore. Ha analizzato bene. Cosa significa abbinare? –

+0

esacamente hai un bug in esso ... cosa fai?!?!?! – ojblass

1

Penso che la risposta al mantenimento delle espressioni regolari non è tanto con commenti o costrutti regex.

Se mi è stato chiesto di eseguire il debug dell'esempio fornito, mi siederei di fronte a uno strumento di debug regex (come Regex Coach) e passo attraverso l'espressione regolare sui dati che deve elaborare.

+0

Sarebbe interessante vedere a cosa serve questo strumento per l'esempio RE nella domanda. Stavo per provare ma non potevo essere disturbato a scaricare e installare. Vuoi fare un tentativo? – paxdiablo

+0

Ancora così tanto male tutto in una riga di codice ... Sono sicuro che averlo nel mio codice non vale la pena. – ojblass

+0

Farò un tentativo Pax (bella risposta a proposito, preferisco la tua alla mia;)). Suppongo che avrò bisogno di alcuni dati di test. ojblass, puoi postare alcuni nella domanda ? – RedBlueThing

7

Alcune persone usano ER per le cose sbagliate (sto aspettando la prima domanda SO su come individuare un programma valido C++ utilizzando un unico RE).

solito trovo che, se non posso andare bene il mio RE all'interno di 60 caratteri, è meglio essere un pezzo di codice dal momento che sarà quasi sempre più leggibile.

In ogni caso, I sempre documento, nel codice, ciò che l'RE dovrebbe ottenere, in grande dettaglio. Questo perché, dall'esperienza amara, so quanto sia difficile per qualcun altro (o anche me, sei mesi dopo) entrare e cercare di capire.

Non credo che siano cattivi, anche se credo che alcune persone che li usano siano malvagie (non ti guardano, Michael Ash :-). Sono un ottimo strumento ma, come una motosega, ti taglierai le gambe se non sai come usarle correttamente.

UPDATE: In realtà, ho appena seguito il link per quella mostruosità, ed è per convalidare M/G formattare le date y/tra gli anni 1600 e 9999. Questo è un classico caso di in cui il codice conclamata sarebbe più leggibile e mantenibile.

Basta dividerlo in tre campi e controllare i singoli valori. Lo considererei quasi un'offesa degna di essere licenziata se uno dei miei seguaci me lo ha comprato. Li spedirei sicuramente per scriverlo correttamente.

+0

Anche in questo caso, la maggior parte delle piattaforme fornisce funzioni che possono convertire una data per te. Davvero, usa quelli! – strager

+1

Si prega di non lasciare che l'esempio particolare offuschi la domanda. – ojblass

+0

Preferisco di gran lunga una palude di funzioni di manipolazione delle stringhe a una regex. Posso imparare cosa fa il pantano nel debugger. Una regex è solo una scatola nera. – jmucchiello

30

Utilizzare Expresso che fornisce una ripartizione gerarchica in inglese di un'espressione regolare.

O

Questo tip da Darren Neimke:

.NET permette l'espressione regolare modelli da autore con incorporati commenti tramite l'opzione di RegExOptions.IgnorePatternWhitespace compilatore e il (? # ...) sintassi incorporato all'interno di ciascuna riga della stringa di modello .

Questo permette di pseudo-codice-come commenti per essere incorporato in ogni riga e ha il seguente effetto sul leggibilità:

Dim re As New Regex (_ 
    "(?<=  (?# Start a positive lookBEHIND assertion) " & _ 
    "(#|@)  (?# Find a # or a @ symbol) " & _ 
    ")   (?# End the lookBEHIND assertion) " & _ 
    "(?=  (?# Start a positive lookAHEAD assertion) " & _ 
    " \w+  (?# Find at least one word character) " & _ 
    ")   (?# End the lookAHEAD assertion) " & _ 
    "\w+\b  (?# Match multiple word characters leading up to a word boundary)", _ 
    RegexOptions.Multiline Or RegexOptions.IgnoreCase Or RegexOptions.IgnoreWhitespace _ 
) 

Ecco un altro esempio di .NET (richiede l'RegexOptions.Multiline e RegexOptions.IgnorePatternWhitespace opzioni):

static string validEmail = @"\b # Find a word boundary 
       (?<Username>  # Begin group: Username 
       [a-zA-Z0-9._%+-]+ # Characters allowed in username, 1 or more 
       )     # End group: Username 
       @     # The e-mail '@' character 
       (?<Domainname>  # Begin group: Domain name 
       [a-zA-Z0-9.-]+  # Domain name(s), we include a dot so that 
            # mail.somewhere is also possible 
       .[a-zA-Z]{2,4}  # The top level domain can only be 4 characters 
            # So .info works, .telephone doesn't. 
       )     # End group: Domain name 
       \b     # Ending on a word boundary 
       "; 

Se il tuo RegEx è applicabile a un problema comune, un altro L'opzione è di documentarlo e inviare a RegExLib, dove verrà valutato e commentato. Niente batte molte paia di occhi ...

Un altro strumento RegEx è The Regulator

+0

commento in linea ... buon suggerimento – ojblass

+0

Sarebbe interessante vedere che cosa questo strumento genera per l'esempio RE nella domanda. Stavo per provare ma non potevo essere disturbato a scaricare e installare. Vuoi fare un tentativo? – paxdiablo

+0

Ci vogliono secondi per scaricare e installare ... –

16

Ebbene, l'intero scopo nella vita del/x modificatore di PCRE è quello di permettere di scrivere espressioni regolari più essere letti, come in questo esempio banale:

my $expr = qr/ 
    [a-z] # match a lower-case letter 
    \d{3,5} # followed by 3-5 digits 
/x; 
+0

Ho difficoltà a trovare la documentazione su questo ... forse non sto usando i termini giusti o la fonte giusta. Puoi aiutare? – ojblass

+0

http://perldoc.perl.org/perlre.html, circa una pagina in basso, paragrafo che inizia con "Il modificatore/x ha bisogno di un po 'più di spiegazione".Ci sono anche esempi sparsi nel resto della pagina. – chaos

+0

(Ovvero "man perlre", ovviamente). – chaos

4

I ho imparato ad evitare tutto tranne la più semplice regexp. Preferisco di gran lunga altri modelli come la scansione delle stringhe di Icon oi combinatori di parsing di Haskell. In entrambi i modelli è possibile scrivere un codice definito dall'utente che ha gli stessi privilegi e lo stesso status delle opzioni di stringa incorporate. Se stavo programmando in Perl, probabilmente metterei in ordine alcuni combinatori di parsing in Perl --- l'ho fatto per altre lingue.

Un'alternativa molto interessante è l'uso di Grammatiche di Expression di Parsing come ha fatto Roberto Ierusalimschy con il suo pacchetto LPEG, ma a differenza dei parser combinatori questo è qualcosa che non si può montare in un pomeriggio. Ma se qualcuno ha già fatto i PEG per la tua piattaforma, è una valida alternativa alle espressioni regolari.

+0

Stavo componendo una risposta quando la tua arrivava; Invece ho buttato fuori il mio e ho svalutato il tuo. – MarkusQ

+0

Ogni volta che analizzo qualcosa tranne le corde più semplici, commetto errori. – ojblass

+0

@objlass: Ecco perché John Levine odia i parser scritti a mano. –

4

Ho trovato un buon metodo per suddividere semplicemente il processo di abbinamento in più fasi. Probabilmente non viene eseguito altrettanto velocemente, ma hai anche il vantaggio di essere in grado di distinguere a un livello di grana più fine perché la partita non si sta verificando.

Un altro percorso consiste nell'utilizzare l'analisi LL o LR. Alcune lingue non sono espresse come espressioni regolari, probabilmente anche con le estensioni non-fsm di perl.

+0

La letteratura sulla tua affermazione richiederà qualche studio. – ojblass

+0

Essere in grado di abbinare a un livello di grano più fine è essenziale. – ojblass

+0

A questo punto Perl può analizzare qualsiasi cosa con le sue regex (sono ora compatibili con Turing Machine), ma non sono la lingua migliore per l'analisi di strutture complesse. Parse :: RecDescent è molto meglio. –

4

Wow, questo è brutto. Sembra che dovrebbe funzionare, modulo un bug inevitabile che riguarda 00 come un anno a due cifre (dovrebbe essere un anno bisestile un quarto del tempo, ma senza il secolo non hai modo di sapere cosa dovrebbe essere). C'è molta ridondanza che dovrebbe probabilmente essere scomposta nelle sub-regex e creerei tre sub-regex per i tre casi principali (questo è il mio prossimo progetto stasera). Ho anche usato un carattere diverso per il delimitatore per evitare di scappare alle barre, modificare le alternanze di singoli caratteri in classi di caratteri (che ci fa felicemente evitare di dover scappare per periodo), e ho cambiato \d in [0-9] poiché il primo corrisponde a qualsiasi carattere di cifra (incluso MONGOLIAN DIGIT FIVE: & # x1815;) in Perl 5.8 e 5.10.

Attenzione, codice non testato:

#!/usr/bin/perl 

use strict; 
use warnings; 

my $match_date = qr{ 
    #match 29th - 31st of all months but 2 for the years 1600 - 9999 
    #with optionally leaving off the first two digits of the year 
    ^
    (?: 
     #match the 31st of 1, 3, 5, 7, 8, 10, and 12 
     (?: (?: 0? [13578] | 1[02]) ([/-.]) 31) \1 
     | 
     #or match the 29th and 30th of all months but 2 
     (?: (?: 0? [13-9] | 1[0-2]) ([/-.]) (?:29|30) \2) 
    ) 
    (?: 
     (?:      #optionally match the century 
      1[6-9] |   #16 - 19 
      [2-9][0-9]  #20 - 99 
     )? 
     [0-9]{2}     #match the decade 
    ) 
    $ 
    | 
    #or match 29 for 2 for leap years 
    ^
    (?: 
    #FIXME: 00 is treated as a non leap year 
    #even though 2000, 2400, etc are leap years 
     0?2      #month 2 
     ([/-.])     #separtor 
     29      #29th 
     \3      #separator from before 
     (?:      #leap years 
      (?: 
       #match rule 1 (div 4) minus rule 2 (div 100) 
       (?: #match any century 
        1[6-9] | 
        [2-9][0-9] 
       )? 
       (?: #match decades divisible by 4 but not 100 
        0[48]  | 
        [2468][048] | 
        [13579][26] 
       ) 
       | 
       #or match rule 3 (div 400) 
       (?: 
        (?: #match centuries that are divisible by 4 
         16   | 
         [2468][048] | 
         [3579][26] 
        ) 
        00 
       ) 
      ) 
     ) 
    ) 
    $ 
    | 
    #or match 1st through 28th for all months between 1600 and 9999 
    ^
    (?: (?: 0?[1-9]) | (?:1[0-2])) #all months 
    ([/-.])       #separator 
    (?: 
     0?[1-9] |    #1st - 9th or 
     1[0-9] |    #10th - 19th or 
     2[0-8]     #20th - 28th 
    ) 
    \4        #seprator from before 
    (?:        
     (?:      #optionally match the century 
      1[6-9] |   #16 - 19 
      [2-9][0-9]  #20 - 99 
     )? 
     [0-9]{2}     #match the decade 
    ) 
    $ 
}x; 
+0

Anche in questa forma non mi sentirei a mio agio con il codice. – ojblass

+1

Quando ero bambina, mia madre mi diceva "prendi dal bordo e soffia". Ciò che accade per la zuppa è doppia per le regex. Prendili un po 'di tempo, rallenta e assapora i pezzi. In poco tempo, la ciotola sarà vuota. ;-) –

+0

No, non lo userei neanche io. 70 righe (3 pagine) di codice che potrebbero essere sostituite da una singola chiamata a una funzione di libreria, no grazie. Volevo solo vedere come funzionava. E ora sono ossessionato dal renderlo più semplice. –

3

Alcune persone, quando si confronta con un problema , pensare "lo so, io uso espressioni regolari." Ora hanno due problemi. - Jamie Zawinski in comp.lang.emacs.

Mantenere le espressioni regolari il più semplici possibile (KISS). Nell'esempio della tua data, probabilmente utilizzerei un'espressione regolare per ogni tipo di data.

O meglio ancora, sostituito con una libreria (ad esempio una libreria di analisi della data).

Vorrei anche fare in modo che la sorgente di input avesse alcune restrizioni (ad esempio un solo tipo di stringhe data, idealmente ISO-8601).

Inoltre,

  • Una cosa al momento (con la possibile eccezione dei valori estrazione)
  • costrutti avanzati sono ok se usato correttamente (come nel simplying l'espressione e quindi riducendo la manutenzione)

EDIT:

"costrutti avanzati portano a problemi maintainance"

Il mio punto originale era che se usato correttamente dovrebbe portare a semplice espressioni, non quelli più difficili. Le espressioni più semplici dovrebbero ridurre la manutenzione.

Ho aggiornato il testo sopra per dire quanto.

Vorrei sottolineare che le espressioni regolari difficilmente si qualificano come costrutti avanzati in sé e per sé. Non avere familiarità con un certo costrutto non lo rende un costrutto avanzato, solo uno sconosciuto. Il che non cambia il fatto che le espressioni regolari sono potenti, compatte e, se usate correttamente, eleganti. Proprio come un bisturi, si trova interamente nelle mani di chi lo maneggia.

+0

un problema diventa due ... ma devo dire che i costrutti avanzati portano a problemi di manutenzione ... – ojblass

+0

Di tutti i pezi di codice guardo le espressioni regolari, la sincronizzazione e l'overload di operanti in quell'ordine sono i problemi più difficili da ottenere alla causa principale quando viene rilevato un difetto. Non possono essere avanzati ma sono camion con molta energia per la quantità di spazio che occupano. – ojblass

5

Ecco la stessa espressione regolare suddivisa in parti digeribili. Oltre ad essere più leggibili, alcune delle sub-regex possono essere utili da sole. È anche molto più facile cambiare i separatori consentiti.

#!/usr/local/ActivePerl-5.10/bin/perl 

use 5.010; #only 5.10 and above 
use strict; 
use warnings; 

my $sep   = qr{ [/.-] }x;    #allowed separators  
my $any_century = qr/ 1[6-9] | [2-9][0-9] /x; #match the century 
my $any_decade = qr/ [0-9]{2} /x;   #match any decade or 2 digit year 
my $any_year = qr/ $any_century? $any_decade /x; #match a 2 or 4 digit year 

#match the 1st through 28th for any month of any year 
my $start_of_month = qr/ 
    (?:       #match 
     0?[1-9] |    #Jan - Sep or 
     1[0-2]     #Oct - Dec 
    ) 
    ($sep)      #the separator 
    (?: 
     0?[1-9] |    # 1st - 9th or 
     1[0-9] |    #10th - 19th or 
     2[0-8]     #20th - 28th 
    ) 
    \g{-1}      #and the separator again 
/x; 

#match 28th - 31st for any month but Feb for any year 
my $end_of_month = qr/ 
    (?: 
     (?: 0?[13578] | 1[02]) #match Jan, Mar, May, Jul, Aug, Oct, Dec 
     ($sep)     #the separator 
     31      #the 31st 
     \g{-1}     #and the separator again 
     |      #or 
     (?: 0?[13-9] | 1[0-2]) #match all months but Feb 
     ($sep)     #the separator 
     (?:29|30)    #the 29th or the 30th 
     \g{-1}     #and the separator again 
    ) 
/x; 

#match any non-leap year date and the first part of Feb in leap years 
my $non_leap_year = qr/ (?: $start_of_month | $end_of_month) $any_year/x; 

#match 29th of Feb in leap years 
#BUG: 00 is treated as a non leap year 
#even though 2000, 2400, etc are leap years 
my $feb_in_leap = qr/ 
    0?2       #match Feb 
    ($sep)      #the separtor 
    29       #the 29th 
    \g{-1}      #the separator again 
    (?: 
     $any_century?   #any century 
     (?:      #and decades divisible by 4 but not 100 
      0[48]  | 
      [2468][048] | 
      [13579][26] 
     ) 
     | 
     (?:      #or match centuries that are divisible by 4 
      16   | 
      [2468][048] | 
      [3579][26] 
     ) 
     00      
    ) 
/x; 

my $any_date = qr/$non_leap_year|$feb_in_leap/; 
my $only_date = qr/^$any_date$/; 

say "test against garbage"; 
for my $date (qw(022900 foo 1/1/1)) { 
    say "\t$date ", $date ~~ $only_date ? "matched" : "didn't match"; 
} 
say ''; 

#comprehensive test 

my @code = qw/good unmatch month day year leap/; 
for my $sep (qw(/ - .)) { 
    say "testing $sep"; 
    my $i = 0; 
    for my $y ("00" .. "99", 1600 .. 9999) { 
     say "\t", int $i/8500*100, "% done" if $i++ and not $i % 850; 
     for my $m ("00" .. "09", 0 .. 13) { 
      for my $d ("00" .. "09", 1 .. 31) { 
       my $date = join $sep, $m, $d, $y; 
       my $re = $date ~~ $only_date || 0; 
       my $code = not_valid($date); 
       unless ($re == !$code) { 
        die "error $date re $re code $code[$code]\n" 
       } 
      } 
     } 
    } 
} 

sub not_valid { 
    state $end = [undef, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 
    my $date  = shift; 
    my ($m,$d,$y) = $date =~ m{([0-9]+)[-./]([0-9]+)[-./]([0-9]+)}; 
    return 1 unless defined $m; #if $m is set, the rest will be too 
    #components are in roughly the right ranges 
    return 2 unless $m >= 1 and $m <= 12; 
    return 3 unless $d >= 1 and $d <= $end->[$m]; 
    return 4 unless ($y >= 0 and $y <= 99) or ($y >= 1600 and $y <= 9999); 
    #handle the non leap year case 
    return 5 if $m == 2 and $d == 29 and not leap_year($y); 

    return 0; 
} 

sub leap_year { 
    my $y = shift; 
    $y = "19$y" if $y < 1600; 
    return 1 if 0 == $y % 4 and 0 != $y % 100 or 0 == $y % 400; 
    return 0; 
} 
1

Ho pubblicato un question recently about commenting regexes with embedded comments C'erano risposte utili ed in particolare uno da @mikej

Vedi il post di Martin Fowler su ComposedRegex per qualche idea in più su migliorare la leggibilità regexp. Nel riassunto , egli propone di scomporre una regexp complessa in parti più piccole a cui possono essere assegnati nomi significativi della variabile . per esempio.