Quando una regex non corrisponde, controlla prima queste tre cose

Scrivere una regex richiede dieci minuti. Capire perché non corrisponde spesso richiede un'ora. Qui fissiamo l'ordine delle verifiche.

La regex è pronta: metà dei casi di test corrisponde, gli altri no. La parte costosa non è mai scriverla, ma capire perché. Le tre trappole qui sotto coprono la maggior parte dei casi e ognuna ha un ordine di verifica preciso.

Distinguere i due tipi di fallimento

Nessuna corrispondenza e corrispondenza con contenuto sbagliato sono problemi diversi. Il primo di solito indica una classe di caratteri o un flag errato; il secondo quasi sempre dipende da quantificatori golosi e dalla posizione delle parentesi. Capire quale dei due hai davanti fa risparmiare metà del tempo: prima verifica se c'è corrispondenza, poi cosa ha catturato il gruppo 1.

Trappola 1: il quantificatore goloso si mangia il resto

.* è goloso per impostazione predefinita: arriva a fine riga e poi torna indietro. Perciò title="(.*)" su title="a" id="b" cattura a" id="b, non a.

Due rimedi: rendere il quantificatore pigro (.*?) oppure usare una classe di caratteri precisa ([^"]*). La seconda è di solito più veloce e prevedibile — i quantificatori pigri tornano comunque indietro, le classi no.

Trappola 2: un livello di escaping di troppo (o mancante)

Se la regex sta in un literal stringa, i backslash passano per due parser. In JavaScript scrivi \\d per ottenere la regex \d; copiando \\\\d da altrove il pattern cerca un backslash letterale seguito da d. Lo stesso vale per \s, \. e \/.

Verifica rapida: stampa la stringa finale del pattern nel debugger e confrontala con quella che pensavi di aver scritto. Questo risolve gran parte dei casi «l'ho scritto, ma non corrisponde».

Trappola 3: i flag m e g

Senza m, ^ e $ corrispondono solo all'inizio e alla fine dell'intera stringa, quindi il confronto riga per riga su testi multiriga fallisce. Con m rispettano i confini di ogni riga.

Con g, l'oggetto regex ricorda dove è finita l'ultima corrispondenza (lastIndex). Riusando la stessa regex globale in un ciclo, la seconda chiamata riparte da lì e sembra saltare delle corrispondenze. Crea una nuova istanza per iterazione oppure azzera lastIndex.

Metodo in quattro passi

  1. Riduci l'input: taglia la stringa alla versione più corta che riproduce l'errore
  2. Dividi il pattern: separa su | o elimina le parti opzionali finché non trovi il punto che fallisce
  3. Guarda i gruppi: controlla cosa contiene davvero ogni gruppo
  4. Prova altri input: aggiungi una stringa che deve corrispondere e una che non deve

Il quarto passo è quello che si salta più spesso. Una corrispondenza non dimostra che il pattern rifiuti il resto — le corrispondenze di troppo fanno danni maggiori, per esempio sostituendo un intero frammento HTML al posto del solo testo.

Una nota sulle prestazioni

Quantificatori annidati come (a+)+$ su input lunghi e non corrispondenti causano backtracking catastrofico e possono bloccare il browser per secondi. Il test è semplice: raddoppia la lunghezza dell'input e guarda se il tempo cresce più di quattro volte. Se succede, non usare quel pattern direttamente sull'input dell'utente.

In una frase

Prima stabilisci se manca la corrispondenza o se il contenuto è sbagliato, poi controlla golosità, escaping e flag in quest'ordine.

I tre numeri da leggere per primi

Quando aprite un tester di espressioni regolari, ignorate per un momento l'evidenziazione colorata e guardate prima queste tre cose:

  • Il numero totale di corrispondenze. Zero significa «non ha corrisposto nulla» — un problema di classe di caratteri o di flag. Più di zero ma meno del previsto di solito indica il flag g mancante, oppure una parte del pattern che inghiotte tutto ciò che segue.
  • La posizione iniziale e finale di ogni corrispondenza. Le posizioni spiegano più del contenuto: se la prima corrispondenza arriva fino a fine testo, c'è un quantificatore greedy al lavoro.
  • Cosa ha davvero catturato il gruppo 1. Guardare il gruppo isolato separa subito «non ha corrisposto» da «ha corrisposto ma ha preso la cosa sbagliata».

L'ordine conta: prima il conteggio, poi le posizioni, infine i gruppi. Al contrario, l'evidenziazione colorata trascinerà lo sguardo finché tutto sembrerà sbagliato.

Tre punti ciechi con CJK e Unicode

w è la scorciatoia per [A-Za-z0-9_] e non contiene alcun carattere CJK: w+ su «张三» non restituisce nulla. Per il testo cinese usate [一-龥] o l'intervallo più ampio [一-鿿].

. non corrisponde a un a capo senza il flag s. Su testo multiriga, .* si ferma a fine prima riga. O usate [sS]* o attivate s.

I confini di parola  sono quasi inutili per il cinese: le parole non sono separate da spazi e non esiste un confine da trovare. La segmentazione richiede una libreria dedicata, non una regex.

Un'altra cosa: la punteggiatura cinese è a larghezza piena. , e , sono punti di codice diversi, quindi un modulo che valida sulla virgola a mezza larghezza rifiuterà chi ne digita una a larghezza piena.

Fissate il pattern con una tabella di casi

«Passava durante il test» non significa che la regex sia corretta. L'approccio duraturo è da tre a cinque casi per pattern, scritti in una piccola tabella:

  • Deve corrispondere: un input normale più i casi limite — il più corto, il più lungo, esattamente sul bordo.
  • Non deve corrispondere: stringa vuota, solo spazi, dati sporchi con prefissi o suffissi in più.
  • Facile da far corrispondere per errore: input simili ma di classe diversa — per un numero di telefono, uno con prefisso o un fisso che inizia per 1.

La tabella ripaga più avanti: quando qualcuno modificherà il pattern sei mesi dopo, eseguire i casi mostrerà subito se il comportamento precedente si è rotto. Senza tabella, ogni modifica è alla cieca.

Dove non usare una regex

Le regex non sono uno strumento universale, e in certi punti falliscono per costruzione:

  • Analizzare HTML o XML annidati. I tag si annidano arbitrariamente e le regex non hanno ricorsione. <div>.*?</div> si fermerà sempre al primo tag di chiusura sbagliando la struttura esterna. Serve un parser DOM.
  • Analizzare JSON. Le stringhe possono contenere escape e parentesi graffe arbitrarie, che una regex fraintende. Serve JSON.parse.
  • Validare completamente un indirizzo email. La RFC 5322 ammette una varietà enorme di forme, e inseguire la precisione al 100% è difficile da scrivere e da mantenere. In pratica: limitate la lunghezza, richiedete una @ e almeno un punto, poi inviate una mail di conferma.
  • Input molto lunghi. Un pattern con quantificatori annidati fa backtracking esponenziale su decine di migliaia di caratteri non corrispondenti e blocca il browser. Limitate prima la lunghezza.

Il criterio è semplice: la struttura di destinazione ammette annidamento arbitrario o ricorsione? Se sì, non usate una regex.

Strumenti correlati