在我制作的这款游戏中,我观看了Stuart Sierra的演讲“ 数据中的思考 ”,并将其中的一种想法作为设计原则。不同之处在于他在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
优点如下:
- 易于测试
- 轻松单独查看这些功能
- 只需注释一个步骤即可轻松注释掉其中一行,并查看结果是什么
- 每个子进程可以向该状态添加更多信息。如果子流程1需要与子流程3进行通信,则就像添加键/值一样简单。
- 没有样板可以从状态中提取所需的数据,以便可以将其保存回去。只需传递整个状态,然后让子进程分配所需的数据即可。
现在,回到我的情况:我上了本课并将其应用于我的游戏。就是说,几乎所有我的高级函数都使用并返回一个gameState
对象。该对象包含游戏的所有数据。EG:badGuys列表,菜单列表,地面战利品等。这是我的更新功能的示例:
update(gameState)
...
gameState = handleUnitCollision(gameState)
...
gameState = handleLoot(gameState)
...
我在这里要问的是,我是否创造了某种可憎的东西,使一个仅在函数式编程语言中可行的想法扭曲了? JavaScript不是惯用的功能(尽管可以用这种方式编写),并且编写不可变的数据结构确实具有挑战性。我所关心的一件事是,他假定每个那些子过程是纯粹的。为什么需要做出这个假设?很少有我的函数是纯函数(通过这种方式,我是说它们经常修改gameState
。除此之外,我没有任何其他复杂的副作用)。如果您没有不变的数据,这些想法会崩溃吗?
我担心有一天我会醒来,意识到整个设计是虚假的,我真的只是在实施Big Mud Of Mud反模式。
老实说,我已经在编写此代码好几个月了,这很棒。我觉得我正在获得他所声称的所有优势。我的代码对我来说很容易推理。但是我是一个单人团队,所以我有知识的诅咒。
更新资料
我已经用这种模式编码了6个月以上。通常到这个时候,我会忘记自己所做的事情,那就是“我以一种干净的方式写的吗?” 发挥作用。如果没有,我真的会很努力。到目前为止,我一点都没有挣扎。
我了解如何需要另一组眼睛来验证其可维护性。我只能说我首先关心可维护性。无论在哪里工作,我始终是最干净的代码传播者。
我想直接回答那些对这种编码方式有不良经历的人。那时我还不知道,但是我认为我们实际上是在谈论两种不同的代码编写方式。我完成此操作的方式似乎比其他人所经历的更加结构化。当某人对“一切都是地图”有不好的个人经验时,他们会说这很难维护,因为:
- 您永远不会知道该功能所需的地图结构
- 任何函数都可以以您意想不到的方式改变输入。您必须遍历整个代码库,以查找特定键如何进入映射或为何消失。
对于那些有这种经验的人,也许代码库是:“一切都需要N种地图中的一种。” 我的想法是,“一切都需要1种地图中的1种”。如果您知道那一种类型的结构,那么您就知道一切的结构。当然,这种结构通常会随着时间而增长。这就是为什么...
在一个地方可以找到参考实现(即模式)。此参考实现是游戏使用的代码,因此它不会过时。
关于第二点,我没有在参考实现之外向地图添加/删除键,我只是对已经存在的内容进行了突变。我也有一大套自动化测试。
如果该体系结构最终因其自身的重量而崩溃,我将添加第二个更新。否则,假设一切顺利:)