Рефакторинг

Коли функція працює, варто подумати, як краще структурувати код. Мета — зробити плагін максимально зрозумілим і зручним у підтримці. Покращення структури існуючого коду без зміни поведінки називається рефакторингом.

Ось знову початкова функція:

(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.