Я спостерігав, як Стюарт Сьєрра говорив " Думаючи дані ", і взяв одну з ідей, як принцип дизайну в цій грі, яку я створюю. Різниця в тому, що він працює в Clojure, а я працюю в JavaScript. Я бачу деякі основні відмінності між нашими мовами в тому, що:
- Clojure - це ідіоматично функціональне програмування
- Більшість станів незмінні
Я взяв ідею зі слайда "Все - це карта" (від 11 хвилин, 6 секунд до> 29 хвилин). Деякі речі, які він каже:
- Щоразу, коли ви бачите функцію, яка бере 2-3 аргументи, ви можете зробити випадок, щоб перетворити її на карту і просто передати карту. Є багато переваг у цьому:
- Вам не потрібно турбуватися про порядок аргументів
- Вам не потрібно турбуватися про будь-яку додаткову інформацію. Якщо є додаткові ключі, це насправді не наше занепокоєння. Вони просто перетікають, вони не заважають.
- Не потрібно визначати схему
- На відміну від передачі в Об'єкт немає даних. Але він вважає, що приховування даних може спричинити проблеми і завищено:
- Продуктивність
- Простота реалізації
- Як тільки ви спілкуєтесь через мережу або через різні процеси, ви все одно повинні домовитися про представлення даних обох сторін. Це додаткова робота, яку можна пропустити, якщо ви просто працюєте над даними.
Найбільш актуально для мого питання. Це 29 хвилин: "Зробіть свої функції композиційними". Ось зразок коду, який він використовує для пояснення поняття:
;; Bad (defn complex-process [] (let [a (get-component @global-state) b (subprocess-one a) c (subprocess-two a b) d (subprocess-three a b c)] (reset! global-state d))) ;; Good (defn complex-process [state] (-> state subprocess-one subprocess-two subprocess-three))
Я розумію, що більшість програмістів не знайомі з Clojure, тому я перепишу це в імперативному стилі:
;; Good def complex-process(State state) state = subprocess-one(state) state = subprocess-two(state) state = subprocess-three(state) return state
Ось переваги:
- Легкий для тестування
- Легко дивитися на ці функції ізольовано
- Легко прокоментувати один рядок цього і побачити, який результат, видаливши один крок
- Кожен підпроцес може додати більше інформації про стан. Якщо підпроцесу потрібно щось передавати підпроцесу три, це так само просто, як додавання ключа / значення.
- Немає шаблону для вилучення потрібних вам даних із стану лише для того, щоб ви могли зберегти їх назад. Просто перейдіть у весь стан і нехай підпроцес призначить те, що йому потрібно.
Тепер повернемося до своєї ситуації: я взяв цей урок і застосував його до своєї гри. Тобто майже всі мої функції високого рівня беруть і повертають gameState
об’єкт. Цей об’єкт містить усі дані гри. EG: Список badGuys, список меню, лут на місці тощо. Ось приклад моєї функції оновлення:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
Що я тут хочу запитати, чи створив я якусь гидоту, яка перекрутила ідею, практичну лише для функціональної мови програмування? JavaScript не є ідіоматично функціональним (хоча це може бути написано таким чином), і писати незмінні структури даних дуже складно. Що мене хвилює, це він припускає, що кожен із цих підпроцесів є чистим. Чому потрібно зробити таке припущення? Рідко буває, що будь-яка з моїх функцій є чистою ( gameState
маючи на увазі, я маю на увазі, що вони часто змінюють . У мене немає інших складних побічних ефектів, окрім цього). Чи розпадаються ці ідеї, якщо у вас немає незмінних даних?
Я переживаю, що одного разу я прокинусь і зрозумію, що весь цей дизайн - це шахрайство, і я дійсно щойно реалізував антидіаграму Big Ball Of Mud .
Чесно кажучи, я працював над цим кодом місяцями, і це було чудово. Я відчуваю, що отримую всі переваги, на які він стверджує. Мій код дуже просто для мене міркує. Але я єдина команда, тому я маю прокляття знань.
Оновлення
Я кодую цю схему 6+ місяців. Зазвичай до цього часу я забуваю, що я зробив, і ось де "чи це я написав чисто?" вступає в гру. Якби цього не було, я б справді боровся. Поки що я зовсім не борюся.
Я розумію, як необхідний ще один набір очей, щоб підтвердити його ремонтопридатність. Все, що я можу сказати, - це я переймаюся ремонтом в першу чергу. Я завжди найгучніший євангеліст за чистий код, де б я не працював.
Я хочу відповісти безпосередньо тим, у кого вже є поганий особистий досвід із таким способом кодування. Я тоді цього не знав, але думаю, що ми дійсно говоримо про два різні способи написання коду. Те, що я зробив це, здається більш структурованим, ніж те, що відчували інші. Коли у когось поганий особистий досвід із "Все - це карта", вони говорять про те, як важко підтримувати, оскільки:
- Ви ніколи не знаєте структури карти, якої вимагає функція
- Будь-яка функція може вимкнути вхід таким чином, якого ви ніколи не очікували. Ви повинні переглянути всю кодову базу, щоб дізнатись, як конкретний ключ потрапив на карту чи чому він зник.
Для тих, хто має такий досвід, можливо, базою коду було: "Все займає 1 з N типів карт". Моє, "Все бере 1 з 1 типу карти". Якщо ви знаєте структуру 1-го типу, ви знаєте структуру всього. Звичайно, ця структура зазвичай росте з часом. Ось чому...
Є одне місце, де можна шукати посилання на реалізацію (тобто: схему). Ця реалізація посилань - це код, який гра використовує, тому вона не може застаріти.
Що стосується другого пункту, я не додаю / видаляю ключі до карти поза межами еталонної реалізації, я просто мутую те, що вже є. У мене також є великий набір автоматизованих тестів.
Якщо ця архітектура врешті-решт руйнується під власною вагою, я додам друге оновлення. Інакше припустимо, що все йде добре :)