【发布时间】:2014-06-12 21:51:07
【问题描述】:
我编写了一个游戏服务器,并且必须检查来自用户的消息是否正确和有效。这意味着它们必须具有正确的语法、符合参数格式并且在语义上正确,即符合游戏规则。
我的目标是拥有一种表达性强、功能性强且不抛出异常的方法,从而尽可能提高可组合性。
我知道其他类似的问题,但它们要么引用我不喜欢的{:pre ..., :post ...},因为一旦抛出异常,只能处理字符串化信息,或者引用我不喜欢的一般异常处理,因为 Clojure 应该能够做到这种任务,或者他们指的是 Haskell 的单子风格,例如一个可能是 Monad à la (-> [err succ]) 我也不喜欢,因为 Clojure 应该能够在不需要 Monad 的情况下处理这种任务。
到目前为止,我使用 cond 作为前置条件检查器和错误代码,然后将其发送回发送请求的客户端,这是一种丑陋的方式:
(defn msg-handler [client {:keys [version step game args] :as msg}]
(cond
(nil? msg) :4001
(not (valid-message? msg)) :4002
(not (valid-version? version)) :5050
(not (valid-step? step)) :4003
(not (valid-game-id? game)) :4004
(not (valid-args? args)) :4007
:else (step-handler client step game args)))
和类似的......
(defn start-game [game-id client]
(let [games @*games*
game (get games game-id)
state (:state game)
players (:players game)]
(cond
(< (count players) 2) :4120
(= state :started) :4093
(= state :finished) :4100
:else ...)))
另一种方法是编写一个宏,类似于defn 和{:pre},但不是抛出一个AssertionError,而是抛出一个带有映射的ex-info,但同样:反对抛出异常。
【问题讨论】:
-
那么,最适合您的是什么?或者,为什么 cond 很难看?
-
是的,也许我应该解释一下这是什么“丑陋”:这是只返回一个实际结果的不好的可组合性,它几乎可以任意表示,一个带编号的关键字作为错误。我的调用堆栈看起来像这样:
(-> msg msg-handler step-handler step-N sub-function),它们都可以返回一个错误类型,但是它们都没有返回相同的成功类型,另一件事是,如果错误返回类型应该短路,我必须手动设计或者必须完成一些中间工作(撤消数据更改或并行通知其他客户端)。
标签: clojure