Dovrei refactificare le grandi funzioni che consistono principalmente in una regex? [chiuso]


15

Ho appena scritto una funzione che si estende su circa 100 righe. Sentendo ciò, probabilmente sei tentato di parlarmi delle singole responsabilità e di esortarmi a riflettere. Anche questo è il mio istinto, ma ecco il problema: la funzione fa una cosa. Esegue una complessa manipolazione di stringhe e il corpo della funzione è costituito principalmente da una regex verbosa, suddivisa in molte righe documentate. Se avessi suddiviso la regex in più funzioni, mi sentirei che avrei perso la leggibilità, poiché sto cambiando le lingue in modo efficace e non sarei in grado di trarre vantaggio da alcune funzionalità offerte da regex. Ecco ora la mia domanda:

Quando si tratta di manipolare le stringhe con espressioni regolari, i corpi di funzioni di grandi dimensioni sono ancora anti-pattern?Sembra che i gruppi di acquisizione denominati abbiano uno scopo molto simile alle funzioni. A proposito, ho dei test per ogni flusso attraverso la regex.


3
Non penso che ci sia qualcosa di sbagliato nella tua funzione, considerando che gran parte di essa è documentazione . Tuttavia, in primo luogo potrebbe esserci un problema di manutenibilità con l'utilizzo di un'espressione regolare di grandi dimensioni.
Joel Cornett,

2
Sei sicuro che una regex gigante sia la migliore soluzione al tuo problema? Hai preso in considerazione alternative più semplici, come una libreria parser o la sostituzione di un formato file personalizzato con uno standard (XML, JSON ecc.)?
Lortabac,

2
Ci sono altre funzioni che usano una versione modificata / migliorata / semplificata di questo regex? Sarebbe un indicatore importante che dovrebbe avvenire il refactoring. Altrimenti, lo lascerei così com'è. La necessità di una complessa manipolazione di stringhe come quella è una bandiera gialla a sé stante (beh non conosco il contesto, quindi solo il giallo), e il refactoring della funzione down mi sembra più un rituale da riscattare dal senso di colpa di cui ci si sente in colpa ;)
Konrad Morawski

8
Come può una regexp a 100 righe fare solo 1 cosa?
Pieter B,

@lortabac: l'input è un testo generato dall'utente (prosa.)
DudeOnRock

Risposte:


36

Quello che stai incontrando è la dissonanza cognitiva che deriva dall'ascolto di persone che favoriscono l'adesione servile alle linee guida sotto le sembianze di "migliori pratiche" rispetto al processo decisionale ragionato.

Hai fatto chiaramente i tuoi compiti:

  • Lo scopo della funzione è compreso.
  • Il funzionamento della sua implementazione è compreso (cioè leggibile).
  • Esistono test di copertura completa dell'implementazione.
  • Questi test superano, il che significa che ritieni che l'implementazione sia corretta.

Se qualcuno di questi punti non fosse vero, sarei il primo a dire che la tua funzione ha bisogno di lavoro. Quindi c'è un voto per lasciare il codice così com'è.

Il secondo voto deriva dall'esame delle opzioni e di ciò che ottieni (e perdi) da ciascuno:

  • Refactor.Questo ti fa rispettare l'idea di qualcuno per quanto tempo dovrebbe essere una funzione e sacrifica la leggibilità.
  • Fare niente. Ciò mantiene la leggibilità esistente e sacrifica la conformità con l'idea di qualcuno di quanto dovrebbe durare una funzione.

Questa decisione scende alla quale apprezzi di più: leggibilità o lunghezza. Cado nel campo che crede che la lunghezza sia buona, ma la leggibilità è importante e prenderà la seconda rispetto alla prima ogni giorno della settimana.

Bottom line: se non è rotto, non aggiustarlo.


10
+1 per "Se non è rotto, non aggiustarlo."
Giorgio

Infatti. Le regole di Sandy Metz ( gist.github.com/henrik/4509394 ) sono buone e tutte, ma su youtube.com/watch?v=VO-NvnZfMA4#t=1379 parla di come sono nate e del perché le persone stanno prendendo troppo seriamente.
Amadan,

@Amdan: con l'ulteriore contesto del video, ciò che Metz aveva senso. La sua raccomandazione a quel cliente era intenzionalmente estrema da un lato per contrastare comportamenti estremi dall'altro come modo per trascinarlo nel mezzo più ragionevole. Il resto di quella discussione si riduce alla spinta della mia risposta: il ragionamento, non la fede, è il modo per determinare il miglior modo di agire.
Blrfl

19

Onestamente, la tua funzione può "fare una cosa", ma come hai affermato da solo

Potrei iniziare a spezzare la regex in più funzioni,

il che significa che il tuo codice di registrazione fa molte cose. E suppongo che potrebbe essere suddiviso in unità più piccole, testabili individualmente. Tuttavia, se questa è una buona idea non è facile rispondere, (soprattutto senza vedere il codice reale). E la risposta corretta potrebbe non essere né "sì" o "no", ma "non ancora, ma la prossima volta devi cambiare qualcosa in quel registro".

ma mi sento come se in realtà perderei la leggibilità in quel modo, poiché sto effettivamente cambiando lingua

E questo è il punto centrale: hai un pezzo di codice scritto in linguaggio reg ex . Questo linguaggio non fornisce alcun buon mezzo di astrazione in sé (e non considero i "gruppi di acquisizione nominati" come sostituti delle funzioni). Quindi il refactoring "nella lingua reg ex" non è realmente possibile e intrecciare reg exp più piccoli con la lingua host potrebbe non migliorare effettivamente la leggibilità (almeno, lo senti , ma hai dei dubbi, altrimenti non avresti pubblicato la domanda) . Quindi ecco il mio consiglio

  • mostra il tuo codice a un altro sviluppatore avanzato (magari su /codereview// ) per assicurarti che gli altri pensino alla leggibilità come fai tu. Sii aperto all'idea che gli altri potrebbero non trovare un registro di 100 righe leggibile come te. A volte la nozione di "non è facilmente frammentabile in pezzi più piccoli" può essere superata solo da un secondo paio di occhi.

  • osserva l'effettiva evolvibilità - il tuo lucido reg exp sembra ancora così buono quando arrivano nuovi requisiti e devi implementarli e testarli? Fintanto che il tuo reg exp funzionerà, non lo toccherei, ma ogni volta che qualcosa deve essere cambiato, riconsidererei se fosse davvero una buona idea mettere tutto in questo grande blocco - e (seriamente!) Ripensare se suddividere in pezzi più piccoli non sarebbero un'opzione migliore.

  • osservare la manutenibilità: è possibile eseguire correttamente il debug del registro exp nella forma corrente? Soprattutto dopo aver cambiato qualcosa, e ora i tuoi test ti dicono che qualcosa non va, hai un debugger reg exp che ti aiuta a trovare la causa principale? Se il debug diventa difficile, sarebbe anche l'occasione per riconsiderare il tuo progetto.


Direi che i gruppi di acquisizione denominati (gruppi di acquisizione in generale, in realtà) sono molto simili alle variabili final / write-once o forse alle macro. Consentono di fare riferimento a parti specifiche della corrispondenza, sia dall'oggetto della corrispondenza restituito dal processore regex o successivamente nell'espressione regolare stessa.
JAB

4

A volte una funzione più lunga che fa una cosa è il modo più appropriato per gestire un'unità di lavoro. È possibile accedere facilmente a funzioni molto lunghe quando si inizia a interrogare un database (utilizzando il linguaggio di query preferito). Rendere una funzione (o metodo) più leggibile limitandola allo scopo dichiarato è ciò che considererei il risultato più desiderabile di una funzione.

La lunghezza è uno "standard" arbitrario quando si tratta di dimensioni del codice. Laddove una funzione di 100 linee in C # può essere considerata longeva, sarebbe piccola in alcune versioni di assembly. Ho visto alcune query SQL che rientravano nelle 200 righe dell'intervallo di codice che restituivano un set di dati molto complicato per un report.

Codice completamente funzionante , il più semplice possibile ragionevolmente fare è l'obiettivo.

Non cambiarlo solo perché è lungo.


3

Potresti sempre suddividere la regex in sotto-regex e comporre gradualmente l'espressione finale. Ciò potrebbe aiutare la comprensione di uno schema molto ampio, in particolare se lo stesso schema secondario viene ripetuto più volte. Ad esempio in Perl;

my $start_re = qr/(?:\w+\.\w+)/;
my $middle_re = qr/(?:DOG)|(?:CAT)/;
my $end_re = qr/ => \d+/;

my $final_re = $start_re . $middle_re . $end_re;
# or: 
# my $final_re = qr/${start_re}${middle_re}${end_re}/

Uso la bandiera dettagliata, che è ancora più comoda di quella che stai suggerendo.
DudeOnRock,

1

Direi romperlo se è fragile. dal punto di vista della manutenibilità e forse della resuabilità ha senso romperlo, ma ovviamente devi considerare naturale la tua funzione e il modo in cui ricevi input e cosa restituirà.

Ricordo che stavo lavorando sull'analisi dei dati in streaming di blocchi in oggetti, quindi quello che ho fatto in pratica è stato dividerlo in due parti principali, uno stava costruendo un'unità completa di String dal testo codificato e nella seconda parte analizzando quelle unità nel dizionario dei dati e organizzando (potrebbe essere una proprietà casuale per oggetto diverso) e rispetto all'aggiornamento o alla creazione di oggetti.

Inoltre ho potuto suddividere ciascuna parte principale in diverse funzioni più piccole e più specifiche, quindi alla fine avevo 5 diverse funzioni per fare tutto e potevo riutilizzare alcune delle funzioni in luoghi diversi.


1

Una cosa che potresti aver o meno preso in considerazione è scrivere un piccolo parser nella lingua che stai usando invece di usare una regex in quella lingua. Potrebbe essere più facile da leggere, testare e gestire.


Ci ho pensato anche io. Il problema è che l'input è in prosa e sto prendendo spunti dal contesto e dalla formattazione. Se è possibile scrivere un parser per qualcosa del genere, mi piacerebbe saperne di più! Non sono riuscito a trovare nulla da solo.
DudeOnRock,

1
Se un regex può analizzarlo, puoi analizzarlo. La tua risposta mi sembra che potresti non essere esperto nell'analisi. In tal caso, potresti voler rimanere con la regex. O quello o impara una nuova abilità.
Thomas Eding,

Mi piacerebbe imparare una nuova abilità. Qualche buona risorsa che puoi suggerire? Sono interessato anche alla teoria che sta dietro.
DudeOnRock,

1

Le regex giganti sono una cattiva scelta nella maggior parte dei casi. Nella mia esperienza, sono spesso usati perché lo sviluppatore non ha familiarità con l'analisi (vedi la risposta di Thomas Eding ).

Ad ogni modo, supponiamo che tu voglia attenerti a una soluzione basata su regex.

Poiché non conosco il codice effettivo, esaminerò i due possibili scenari:

  • Il regex è semplice (molta corrispondenza letterale e poche alternative)

    In questo caso le funzionalità avanzate offerte da un singolo regex non sono indispensabili. Ciò significa che probabilmente trarrai beneficio dalla divisione.

  • Il regex è complesso (molte alternative)

    In questo caso non puoi realisticamente avere una copertura completa del test, perché probabilmente hai milioni di possibili flussi. Quindi, per testarlo, è necessario dividerlo.

Potrei mancare di immaginazione, ma non riesco a pensare a nessuna situazione del mondo reale in cui una regex di 100 righe è una buona soluzione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.