Refactoring
Una volta che abbiamo una funzione funzionante, possiamo fare un passo indietro e pensare a come strutturare al meglio il nostro codice. L’obiettivo è rendere il nostro plug-in il più chiaro, comprensibile e gestibile possibile. Questo processo di miglioramento e perfezionamento della struttura del codice esistente senza modificarne il comportamento è noto come refactoring.
Ecco di nuovo la funzione iniziale:
(define (scheme-hello-world)
;; Impostare il gestore dei messaggi per inviare il messaggio a una finestra di dialogo GUI
(lumi-message-set-handler 0)
(lumi-message "Hello world!\n")
;; Impostare il gestore dei messaggi per inviare il messaggio alla console degli errori
(lumi-message-set-handler 2)
(lumi-message "Hello world!\n")
;; Inviare il messaggio al terminal, la finestra del SO che ha avviato Lumi
(display "Hello world!\n"))Il nome-funzione è il nome della funzione e il parametro è ciò che la funzione accetta come input. Il corpo è il blocco di codice che viene eseguito quando viene chiamata la funzione.
Forma astratta:
(define (function-name parameter)
body)Ripetizione del codice
Rimuovere la ripetizione in anticipo. (lumi-message "Hello world!\n") viene ripetuto due volte e la stringa del messaggio viene ripetuta tre volte. Una variabile risolve la stringa ripetuta.
Variabili
In Scheme una variabile ha un “ambito”, di cui è nota, e tale ambito viene impostato utilizzando un’istruzione let. La variabile è associata a un valore nella parte vincolante e la variabile ha ambito nel corpo let. La variabile è nota solo all’interno del blocco let e non è possibile accedervi al di fuori di esso.
(let ((variable value))
body)Introducendo una variabile chiamata “messaggio”:
(define (scheme-hello-world)
(let ((message "Hello world!\n"))
;; Impostare il gestore dei messaggi per inviare il messaggio a una finestra di dialogo GUI
(lumi-message-set-handler 0)
(lumi-message message)
;; Impostare il gestore dei messaggi per inviare il messaggio alla console degli errori
(lumi-message-set-handler 2)
(lumi-message message)
;; Inviare il messaggio al terminal, la finestra del SO che ha avviato Lumi
(display message)))Nel nostro esempio abbiamo utilizzato una variabile chiamata “messaggio” legata ad una stringa “Ciao mondo!\n”. Questo ci permette di modificare il contenuto del messaggio una volta anziché tre, riducendo la possibilità di errori e rendendo il codice più flessibile.
Funzioni di estrazione
Nella programmazione funzionale, il refactoring del codice per estrarre la logica riutilizzabile in funzioni separate è una pratica comune. In questo modo, la funzione principale diventa molto più semplice e più focalizzata sul suo obiettivo di alto livello, mentre la funzione estratta appare più complessa perché gestisce la logica dettagliata. Ciò è intenzionale e in linea con i principi fondamentali della programmazione funzionale, come modularità, separazione delle preoccupazioni e leggibilità. Ecco il refactoring Ciao mondo! dopo l’estrazione.
Estrarre la logica:
#!/usr/bin/env lumi-scheme-interpreter-0.1
;; Funzione principale
(define (scheme-hello-world)
(let ((message "Hello world!\n"))
(send-message message 'gui)
(send-message message 'error-console)
(send-message message 'terminal)))
;; Funzione per gestire l'output dei messaggi verso varie destinazioni
(define (send-message message output)
(cond
;; Invia alla console degli errori
((eq? output 'error-console)
;; Impostare il gestore su console degli errori
(lumi-message-set-handler 2)
(lumi-message message))
;; Invia alla finestra di dialogo GUI
((eq? output 'gui)
;; Impostare il gestore sulla finestra di dialogo GUI
(lumi-message-set-handler 0)
(lumi-message message))
;; Invia alla finestra del terminale
((eq? output 'terminal)
;; L'output del terminal è gestito con display
(display message)))
;; Ripristinare il gestore dei messaggi predefinito alla console degli errori
(lumi-message-set-handler 2))
(scheme-register-procedure "scheme-hello-world"
"Hello world!"
"A Scheme procedure plug-in"
"Mark Sweeney"
"Under GNU GENERAL PUBLIC LICENSE Version 3"
"2024")
(scheme-menu-register
"scheme-hello-world"
"<Image>/Funky")Simboli
Nell’esempio sopra viene utilizzato un tipo di dati chiamato simbolo, come “gui”. I simboli vengono passati come parametri alla funzione di invio del messaggio e possono essere utilizzati per prendere semplici decisioni condizionali. Come le chiavi simboliche, sono identificatori univoci. Per ulteriori informazioni sui simboli, visitare questa pagina.
Semplificazione della funzione principale
Nella funzione originale (scheme-hello-world), tutta la logica per l’invio di messaggi a diversi output (GUI, Console errori, Terminale) era mescolata nella funzione principale. Dopo il refactoring, la funzione principale si concentra semplicemente su cosa è necessario fare, inviando il messaggio a destinazioni diverse.
La funzione main refactoring è più semplice:
- Dichiara chiaramente il suo scopo: inviare lo stesso messaggio a più uscite.
- Evita di ingombrare la logica principale con codice ripetitivo come l’impostazione di gestori di messaggi per output diversi.
- È più facile da leggere e comprendere a colpo d’occhio.
La complessità della funzione estratta
Al contrario, la funzione (invia messaggio) è dove risiede la logica dettagliata. Ora gestisce le variazioni di comportamento per ciascun output (GUI, Console errori, Terminale). La funzione è un po’ più complessa di prima, ma ora è centralizzata e isolata.
Relativo a ciò con la programmazione funzionale
Nella programmazione funzionale, le funzioni sono viste come cittadini di prima classe, nel senso che possono essere riutilizzate, scambiate e combinate per formare comportamenti più complessi. L’obiettivo è:- Scomponi i problemi in parti più piccole e indipendenti.
- Isolare la complessità in funzioni più piccole che gestiscono attività specifiche, come
send-message. - Mantieni semplici le funzioni di livello superiore in modo che possano concentrarsi sull’orchestrazione del flusso di dati e azioni, senza dover conoscere i dettagli di come viene eseguita ciascuna attività.
- Separazione degli interessi: la funzione si occupa di come viene inviato il messaggio in base al tipo di output, isolando questa logica dalla funzione principale.
- Modularità: gestendo tutta la logica di invio dei messaggi in un unico posto, possiamo facilmente apportare modifiche (come aggiungere nuove opzioni di output) senza alterare la funzione principale.
- Riutilizzabilità: la funzione
send-messageè riutilizzabile, il che significa che se dobbiamo inviare un messaggio a più output altrove nel nostro codice, possiamo semplicemente chiamare questa funzione anziché riscrivere una logica simile.
Mediante il refactoring, la funzione principale in questo esempio diventa una dichiarazione dichiarativa di ciò che sta accadendo (“invia un messaggio a tre posti”), mentre la complessità di come inviare tali messaggi viene astratta nella funzione send-message.