我编写了一个游戏服务器,必须检查从用户收到的消息是否正确和有效。这意味着它们必须具有正确的语法,遵守参数格式并且在语义上是正确的,即遵守游戏规则。

我的目标是要有一种表达性,功能性的方式,而不会抛出异常,从而尽可能地提高可组合性。

我知道其他类似的问题,但是它们要么引用我不喜欢的{:pre ..., :post ...},因为一旦抛出异常,就只能处理字符串信息,或者引用我通常不喜欢的异常处理,因为Clojure应该能够执行此类任务,或者他们用例如Haskell的单子(monad)风格我可能也不喜欢Monadà(-> [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而不是抛出带有 map 的ex-info,但是要再次声明:与异常抛出相反。

最佳答案

您的问题的核心似乎在您的评论中:

Clojure 1.5+具有some->线程宏以摆脱nil?检查样板。我们只需要轻轻地调整代码,以用nil?检查代替我们选择的检查即可。

(defmacro pred->
  "When predicate is not satisfied, threads expression into the first form
  (via ->), and when that result does not satisfy the predicate, through the
  next, etc. If an expression satisfies the predicate, that expression is
  returned without evaluating additional forms."
  [expr pred & forms]
  (let [g (gensym)
        pstep (fn [step] `(if (~pred ~g) ~g (-> ~g ~step)))]
    `(let [~g ~expr
           ~@(interleave (repeat g) (map pstep forms))]
       ~g)))
请注意,使用此定义,(pred-> expr nil? form1 form2 ...)(some-> expr form1 form2...)。但是现在我们可以使用其他谓词。

例子
(defn foo [x] (if (even? x) :error-even (inc x)))
(defn bar [x] (if (zero? (mod x 3)) :error-multiple-of-three (inc x)))

(pred-> 1 keyword? foo) ;=> 2
(pred-> 1 keyword? foo foo) ;=> :error-even
(pred-> 1 keyword? foo foo foo) ;=> :error-even

(pred-> 1 keyword? foo bar foo bar) ;=> 5
(pred-> 1 keyword? foo bar foo bar foo bar foo bar) ;=> :error-multiple-of-three

您的用例
灵活的选择是为验证错误做一个包装
(deftype ValidationError [msg])
然后,您可以像在(->ValidationError 4002)中一样包装错误代码/消息,并将线程更改为
(pred-> msg #(instance? ValidationError %)
  msg-handler step-handler step-N sub-function)

10-06 13:29
查看更多