Я смотрел выступление Стюарта Сьерры « Мышление в данных » и использовал одну из идей в качестве принципа дизайна в этой игре, которую я делаю. Разница в том, что он работает в 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
Вот преимущества:
- Легко проверить
- Легко смотреть на эти функции в отдельности
- Легко закомментировать одну строку этого и посмотреть, каков будет результат, удалив один шаг
- Каждый подпроцесс может добавить больше информации о состоянии. Если для подпроцесса нужно что-то сообщить подпроцессу 3, это так же просто, как добавить ключ / значение.
- Нет шаблона для извлечения необходимых данных из состояния, чтобы вы могли сохранить их обратно. Просто передайте все состояние и позвольте подпроцессу назначить то, что ему нужно.
Теперь вернемся к моей ситуации: я взял этот урок и применил его к своей игре. То есть почти все мои высокоуровневые функции принимают и возвращают gameState
объект. Этот объект содержит все данные игры. Например: список badGuys, список меню, добыча на земле и т. Д. Вот пример моей функции обновления:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
Здесь я хочу спросить, создал ли я какую-то мерзость, извращающую идею, которая практична только для функционального языка программирования? JavaScript не является идиоматически функциональным (хотя он может быть написан таким образом), и действительно сложно написать неизменяемые структуры данных. Меня беспокоит то, что он предполагает, что каждый из этих подпроцессов чист. Почему необходимо сделать это предположение? Редко когда какие-либо из моих функций чистые (я имею в виду, что они часто модифицируют gameState
. У меня нет никаких других сложных побочных эффектов, кроме этого). Разве эти идеи разваливаются, если у вас нет неизменных данных?
Я волнуюсь, что однажды я проснусь и пойму, что весь этот дизайн - обман, и я действительно только что внедрил антишаблон Big Ball Of Mud .
Честно говоря, я работал над этим кодом несколько месяцев, и это было здорово. Я чувствую, что получаю все преимущества, на которые он претендовал. Мой код очень прост для меня, чтобы рассуждать о. Но я команда из одного человека, поэтому у меня есть проклятие знаний.
Обновить
Я кодировал 6+ месяцев с этим шаблоном. Обычно к этому времени я забываю, что я сделал, и именно здесь "я написал это чисто?" вступает в игру. Если бы не я, я бы действительно боролся. Пока я не борюсь вообще.
Я понимаю, как другой набор глаз был бы необходим для проверки его ремонтопригодности. Все, что я могу сказать, это прежде всего забота о ремонтопригодности. Я всегда самый громкий евангелист за чистый код, где бы я ни работал.
Я хочу прямо ответить на те, которые уже имеют плохой личный опыт с этим способом кодирования. Тогда я этого не знал, но думаю, что мы действительно говорим о двух разных способах написания кода. То, как я это сделал, выглядит более структурированным, чем то, что пережили другие. Когда кто-то имеет плохой личный опыт работы с «Все это карта», они говорят о том, как трудно поддерживать, потому что:
- Вы никогда не знаете структуру карты, которая требуется для функции
- Любая функция может изменить входные данные способами, которые вы никогда не ожидаете. Вы должны просмотреть всю кодовую базу, чтобы узнать, как тот или иной ключ попал в карту или почему он исчез.
Для тех, у кого такой опыт, возможно, кодовая база гласила: «Все требует 1 из N типов карт». Моя, «Все занимает 1 из 1 типа карты». Если вы знаете структуру этого 1 типа, вы знаете структуру всего. Конечно, эта структура обычно растет со временем. Вот почему...
Есть одно место для поиска эталонной реализации (т.е. схема). Эта эталонная реализация является кодом, используемым в игре, поэтому она не может устареть.
Что касается второго пункта, я не добавляю / удаляю ключи к карте вне эталонной реализации, я просто изменяю то, что уже есть. У меня также есть большой набор автоматизированных тестов.
Если эта архитектура рухнет под собственным весом, я добавлю второе обновление. В противном случае предположим, что все идет хорошо :)