January 2, 2021
By: Kevin

怎么避免在代码里搞飞机✈

假如说一个页面按钮点击的时候需要做如下5个检查, 每个检查会报告不同的错误msg.

(defn- valid1? [m]
  (->> m ...))

(defn- valid2? [m]
  (->> m ...))

(defn- valid3? [m]
  (->> m ...))

(defn- valid4? [m]
  (->> m ...))

(defn- valid5? [m]
  (->> m ...))

我们最常见的写法: 逐层嵌套的if语句, 代码的形状是一个典型的大于号 >. 貌似一架飞机.

其实任何语言多次if/else嵌套之后都是飞机状态. 类似代码, 我们不少人写过吧...

[:> Button {:on-click #(if (valid1? m)
                         (if (valid2? m)
                           (if (valid3? m)
                             (if (valid4? m)
                               (if (valid5? m)
                                 (ok-action!);;;;; ====> 我是✈️头
                                 (error "5没有通过"))
                               (error "4没有通过"))
                             (error "3没有通过"))
                           (error "2没有通过"))
                         (error "1没有通过")
                         )} "ok"]

就是得写很多的分支, 会有点烦.

灵活组织数据结构 + 使用some函数, 以下的写法更加简洁很多.

(defn- all-checks [m]
  (some (fn [[f msg]]
          (when-not (f m) msg))
        [[valid1?   "检查1没有通过"]
         [valid2?   "检查2没有通过"]
         [valid3?   "检查3没有通过"]
         [valid4?   "检查4没有通过"]
         [valid5?   "检查5没有通过"]]))

[:> Button {:on-click #(if-let [msg (all-checks m)]
                         (show-error msg)
                         (ok-action!))} "ok"]

此外, 如果对OOP的设计模式熟悉, 你会发现这是一个典型的责任链模式.

some函数, 完成了这个模式对应的全部功能

下面是这个模式的Java实现:

先定一个抽象的Validator:

public abstract class Validator {
  protected Validator nextValidator;

  abstract void process(String message);

  public void setNextValidator(Validator nextValidator) {
    this.nextValidator = nextValidator;
  }
}

然后定义具体的Validator

class Validator1 extends Validator {
    boolean valid (Message msg) {
        ....
    }
    @Override
    void process(Message msg) {
        if (valid msg) {
            if (nextValidator != null) nextValidator.process(message);
        } else {
            return "检查1没有通过"
                }
    }
}
class ProfanityValidator extends Validator {
    boolean valid (Message msg) {
        ....
    }
    @Override
    void process(Message msg) {
        if (valid message) {
        if (nextValidator != null) nextValidator.process(message);
        } else {
            return "检查2没有通过";
        }
    }
}
class RejectValidator extends Validator {
    boolean valid (Message msg) {
        ....
    }
    @Override
    void process(Message msg) {
        if (valid message) {
            if (nextValidator != null) nextValidator.process(message);
        } else {
          "检查3没有通过"
              }
    }
}
// 后面两个省略了....

最后应用这些具体的检查过滤

Validator validator1 = new Validator1();
Validator validator2 = new Validator2();
Validator validator3 = new Validator2();
Validator validator4 = new Validator2();
Validator validator5 = new Validator2();


validator1.setNextValidator(validator2);
validator2.setNextValidator(validator3);
validator3.setNextValidator(validator4);
validator4.setNextValidator(validator5);

Message message = ...
validator1.process(message);

比较了三种方式

  1. 纯粹的if/else 会搞飞机
  2. 使用clojure的some函数实现责任链模式
  3. 原始的责任链模式

同样是链式处理的场景, FP比OOP要简单很多.

Tags: clojure