You are here: Partenza > Dive Into Python > Test delle unità di codice > Rifattorizzazione | << >> | ||||
Dive Into PythonPython per programmatori esperti |
La cosa migliore nel fare test esaustivi delle unità di codice non è la sensazione piacevole che si ha quando tutti i test hanno finalmente successo, e neanche la soddisfazione di quando qualcun altro ti rimprovera di aver scombinato il loro codice e tu puoi effettivamente provare che non è vero. La cosa migliore nell'effettuare i test delle unità di codice è la sensazione che ti lascia la libertà di rifattorizzare senza provare rimorsi.
La rifattorizzazione è il procedimento con il quale si prende del codice funzionante e lo si modifica in modo che funzioni meglio. Di solito, “meglio” significa “più velocemente”, sebbene possa anche significare “che usa meno memoria” oppure “che usa meno spazio su disco” o semplicemente “in modo più elegante”. Qualunque sia il significato per voi, per il vostro progetto e per il vostro ambiente, la rifattorizzazione è importante per la salute a lungo termine di ogni programma.
Nel nostro caso, “meglio” significa “più veloce”. In particolar modo, la funzione fromRoman è più lenta del necessario, a causa di quella lunga e ostica espressione regolare che viene usata per verificare i numeri romani. Probabilmente non vale la pena di cercare di fare a meno completamente delle espressioni regolari (sarebbe difficile, e potrebbe risultare niente affatto più veloce), ma possiamo velocizzare la funzione precompilando l'espressione regolare.
>>> import re >>> pattern = '^M?M?M?$' >>> re.search(pattern, 'M') <SRE_Match object at 01090490> >>> compiledPattern = re.compile(pattern) >>> compiledPattern <SRE_Pattern object at 00F06E28> >>> dir(compiledPattern) ['findall', 'match', 'scanner', 'search', 'split', 'sub', 'subn'] >>> compiledPattern.search('M') <SRE_Match object at 01104928>
Ogni qualvolta si ha intenzione di usare più di una volta una espressione regolare, la si dovrebbe prima compilare per ottenerne il corrispondente oggetto pattern, e quindi chiamare direttamente i metodi di tale oggetto. |
Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.
# toRoman and rest of module omitted for clarity romanNumeralPattern = \ re.compile('^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$') def fromRoman(s): """convert Roman numeral to integer""" if not s: raise InvalidRomanNumeralError, 'Input can not be blank' if not romanNumeralPattern.search(s): raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s result = 0 index = 0 for numeral, integer in romanNumeralMap: while s[index:index+len(numeral)] == numeral: result += integer index += len(numeral) return result
E allora, quanto abbiamo guadagnato in velocità compilando l'espressione regolare? Osservate voi stessi:
............. ---------------------------------------------------------------------- Ran 13 tests in 3.385s OK
Solo una nota di passaggio: questa volta, ho eseguito i test senza l'opzione -v, cosicché invece della completa stringa di documentazione, per ogni test che ha successo è stampato solo un carattere punto. (Se un test fallisce, è stampata una F, se ha un errore, è stampata una E. Viene sempre stampato il traceback completo per ogni fallimento od errore, cosicché possiamo risalire a qual'era il problema.) | |
Abbiamo eseguito 13 test in 3.385 secondi, a fronte dei 3.685 secondi ottenuti senza precompilare l'espressione regolare. Questo è un miglioramento complessivo dell'8%, senza contare che che la maggior parte del tempo speso durante i test è stato utilizzato facendo altre cose. (Separatamente, ho misurato il tempo occorso all'elaborazione delle espressioni regolari a sé stanti, separate dal resto dei test, e ho trovato che compilare l'espressione regolare accellera l'esecuzione della ricerca di una media del 54%.) Non male, per una modifica così semplice. | |
Oh, e nel caso ve lo stesse chiedendo, precompilare la nostra espressione regolare non ha scombinato niente, lo abbiamo appena dimostrato. |
C'è un'altra ottimizzazione di prestazioni che voglio provare. Data la complessità della sintassi delle espressioni regolari, non dovrebbe sorprendere il fatto che spesso ci sia più di un modo per scrivere la stessa espressione. Dopo qualche discussione circa questo modulo nel newsgroup comp.lang.python, qualcuno mi ha suggerito di provare ad usare la sintassi {m,n} per i caratteri opzionali ripetuti.
Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.
# rest of program omitted for clarity #old version #romanNumeralPattern = \ # re.compile('^M?M?M?M?(CM|CD|D?C?C?C?)(XC|XL|L?X?X?X?)(IX|IV|V?I?I?I?)$') #new version romanNumeralPattern = \ re.compile('^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$')
Questa forma dell'espressione regolare è un po' più corta (sebbene non certo più leggibile). Veniamo alla grande domanda: è più veloce?
............. ---------------------------------------------------------------------- Ran 13 tests in 3.315s OK
C'è un altro ritocco che vorrei fare, e quindi prometto che poi smetterò di rifattorizzare questo modulo e lo metterò a nanna. Come abbiamo visto più volte, le espressioni regolari possono rapidamente diventare piuttosto rognose ed illeggibili. Non mi piacerebbe tornare su questo modulo fra sei mesi e cercare di fargli delle modifiche. Certo, i test hanno successo, per cui so che il modulo funziona, ma se non sono in grado di capire come funziona non sarò capace di aggiungere nuove caratteristiche, eliminare nuovi bachi, oppure manutenerlo in altro modo. La documentazione è un fattore critico per la manutenibilità del codice, e Python fornisce un modo di documentare in modo esteso le espressioni regolari.
Se non lo avete ancora fatto, potete scaricare questo ed altri esempi usati in questo libro.
# rest of program omitted for clarity #old version #romanNumeralPattern = \ # re.compile('^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$') #new version romanNumeralPattern = re.compile(''' ^ # beginning of string M{0,4} # thousands - 0 to 4 M's (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), # or 500-800 (D, followed by 0 to 3 C's) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), # or 50-80 (L, followed by 0 to 3 X's) (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), # or 5-8 (V, followed by 0 to 3 I's) $ # end of string ''', re.VERBOSE)
............. ---------------------------------------------------------------------- Ran 13 tests in 3.315s OK
<< Gestire il cambiamento di requisiti |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | |
Postscritto >> |