Рефакторинг
Коли функція працює, варто подумати, як краще структурувати код. Мета — зробити плагін максимально зрозумілим і зручним у підтримці. Покращення структури існуючого коду без зміни поведінки називається рефакторингом.
Ось знову початкова функція:
(define (scheme-hello-world)
;; Виводити повідомлення в діалогове вікно GUI
(lumi-message-set-handler 0)
(lumi-message "Hello world!\n")
;; Виводити повідомлення в консоль помилок
(lumi-message-set-handler 2)
(lumi-message "Hello world!\n")
;; Надіслати повідомлення в термінал — вікно ОС, з якого запущено Lumi
(display "Hello world!\n"))Ім’я функції — це її назва; параметр — те, що функція приймає на вході. Тіло — блок коду, який виконується під час виклику.
Форма визначення:
(define (function-name parameter)
body)Повторення коду
Приберіть повторення на ранньому етапі. (lumi-message "Hello world!\n") повторюється двічі, а рядок повідомлення — тричі. Змінна вирішує проблему повторюваного рядка.
Змінні
У Scheme у змінної є область видимості — частина програми, де вона відома. Її задає оператор let: у частині прив’язки змінна пов’язана зі значенням, а в тілі let має область видимості лише всередині цього блоку.
(let ((variable value))
body)Введемо змінну message:
(define (scheme-hello-world)
(let ((message "Hello world!\n"))
;; Виводити повідомлення в діалогове вікно GUI
(lumi-message-set-handler 0)
(lumi-message message)
;; Виводити повідомлення в консоль помилок
(lumi-message-set-handler 2)
(lumi-message message)
;; Надіслати повідомлення в термінал
(display message)))Змінна message прив’язана до рядка "Hello world!\n". Текст можна змінити в одному місці замість трьох — менше помилок і більше гнучкості.
Виділення функцій
У функціональному програмуванні типовою практикою є виділення повторюваної логіки в окремі функції. Тоді основна функція стає простішою і зосереджується на меті високого рівня, а виділена функція містить детальну логіку — навмисно. Це узгоджується з модульністю, поділом завдань і читабельністю.
Ось рефакторинг «Hello world!» після виділення.
Виділена логіка:
#!/usr/bin/env lumi-scheme-interpreter-0.1
;; Головна функція
(define (scheme-hello-world)
(let ((message "Hello world!\n"))
(send-message message 'gui)
(send-message message 'error-console)
(send-message message 'terminal)))
;; Надсилання повідомлення на різні виходи
(define (send-message message output)
(cond
;; Консоль помилок
((eq? output 'error-console)
;; Обробник — консоль помилок
(lumi-message-set-handler 2)
(lumi-message message))
;; Діалогове вікно GUI
((eq? output 'gui)
;; Обробник — діалог GUI
(lumi-message-set-handler 0)
(lumi-message message))
;; Вікно терміналу
((eq? output 'terminal)
;; Вивід у термінал через display
(display message)))
;; Повернути обробник за замовчуванням — консоль помилок
(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")Символи
У прикладі використовується тип даних символ, наприклад 'gui. Символи передаються в send-message і допомагають приймати прості умовні рішення. Як і ключі в асоціативних структурах, вони є унікальними ідентифікаторами. Докладніше — на цій сторінці.
Спрощення основної функції
У початковій scheme-hello-world вся логіка надсилання на різні виходи (GUI, консоль помилок, термінал) була змішана в одній функції. Після рефакторингу основна функція зосереджується на що потрібно зробити — надіслати те саме повідомлення в кілька місць.
Перероблена основна функція стала простішою:
- чітко видно призначення: надіслати одне повідомлення на кілька виходів;
- основна логіка не захаращена повторюваним кодом на кшталт перемикання обробників повідомлень;
- легше читати з першого погляду.
Складність виділеної функції
Натомість у send-message зосереджена детальна логіка — варіанти поведінки для кожного виходу (GUI, консоль помилок, термінал). Функція трохи складніша, але тепер централізована та ізольована.
Зв’язок із функціональним програмуванням
У функціональному програмуванні функції — first-class: їх можна повторно використовувати, передавати та комбінувати. Мета:
- Розбити задачу на менші незалежні частини.
- Ізолювати складність у менших функціях з конкретними завданнями, наприклад
send-message. - Тримати функції верхнього рівня простими — вони організують потік даних і дій, не знаючи деталей кожного кроку.
- Розділити відповідальність:
send-messageзнає, як надсилати повідомлення залежно від типу виходу, ізолюючи цю логіку від основної функції. - Модульність: зміни (наприклад, нові виходи) вносяться в одному місці, без зміни основної функції.
- Повторне використання:
send-messageможна викликати з будь-якої частини коду замість дублювання логіки.
Після рефакторингу основна функція стає декларативним описом («надіслати повідомлення в три місця»), а те, як це робиться, абстрагується в send-message.