eis/eqpalg/algs/EXPBASE_REFACTOR_PLAN.md

16 KiB
Raw Permalink Blame History

ExpBase 设计分析与优化方案

2026-05-15


一、当前设计问题诊断

1.1 量化数据

指标 数值 健康标准
总代码行数(.h + .cpp 1956 < 500
exp_type_ 类型码分支 16 处 0应用多态消除
布尔状态标志 12 个 < 5
mon_proc() 方法行数 ~200 行 < 50
方法总数 40+ < 20
开关语句层级深度 3 层exp_type → feedback_mode → exp_type < 2

1.2 核心设计问题

问题一God Class上帝类

ExpBase 承担了至少 7 项不同职责

┌─────────────────────────────────────────────────────────┐
│                      ExpBase                            │
│  ┌──────────────┐ ┌──────────────┐ ┌────────────────┐  │
│  │ 变量管理      │ │ 表达式求值    │ │ 配置加载/校验   │  │
│  │ mm_vars/pv/  │ │ exp_act_     │ │ reload_config_*│  │
│  │ hold/s/mv2等  │ │ exp_feedback_│ │ init() 分支     │  │
│  └──────────────┘ │ exp_result_  │ └────────────────┘  │
│                   └──────────────┘                      │
│  ┌──────────────┐ ┌──────────────┐ ┌────────────────┐  │
│  │ 反馈状态机    │ │ 超限检测      │ │ 数据获取        │  │
│  │ act_start/   │ │ detect_up_   │ │ MEMORY vs IHDB │  │
│  │ done/timeout │ │ down()       │ │ refresh_*      │  │
│  │ /not_hold    │ │              │ │                │  │
│  └──────────────┘ └──────────────┘ └────────────────┘  │
│  ┌──────────────────────────────────────────────────┐   │
│  │ 统计学习 & 持久化                                  │   │
│  │ cron_proc() / task_base_proc() / DAA::STA        │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

问题二:类型码代替多态

exp_type_ 字段驱动了 16 处条件分支,这是典型的"switch-on-type-code"反模式。以 mon_proc() 为例,其控制流可以画成:

mon_proc()
├── Bound/BoundHoldTime? → 筛选+统计
├── feedback_mode_?
│   ├── act_start_done()? → return
│   ├── act_not_hold()? → reset, return
│   ├── act_done()?
│   │   ├── CondBound? → 上下限报警
│   │   └── else → 布尔报警
│   └── act_timeout()? → timeout报警
└── else (无反馈)
    ├── Bound? → 上下限报警
    ├── BoundHoldTime? → 持续时间+上下限报警
    └── else → 布尔报警

5 种算法类型共享一个方法,通过 exp_type_ + feedback_mode_ 两个标志组合出 5 条路径。新增一种算法类型就要修改这个核心方法。

问题三:子类继承不需要的复杂度

ExpBase (1956行)
├── ExpTimes         使用: 表达式求值,不使用: 反馈状态机/上下限/统计学习cron
├── ExpSample2D      使用: 表达式求值,不使用: 反馈状态机/上下限用自己的fit逻辑
├── ExpBound         使用: 表达式求值,不使用: 反馈状态机/上下限用自己的bound逻辑
├── Roller2          使用: 表达式求值,不使用: 反馈状态机/上下限/统计学习
└── Roller3          使用: 表达式求值,不使用: 反馈状态机用自己的median逻辑

每个子类都继承了 12 个布尔状态标志、40+ 个方法,但实际只用到其中一小部分。

问题四:状态标志过多且隐含耦合

12 个布尔标志之间存在隐含的状态转换关系,但没有显式的状态机:

feedback_mode_, keep_mode_, feedback_done_, act_started_, act_triggered_,
m_timemode, is_learning_, filter_flag_, is_fun_vars_need_reset_,
exp_is_wrong_, exp_wrong_is_alarmed_, is_running_

这些标志分散在各处被读写,难以追踪状态转换的全貌。


二、优化方案

2.1 总体策略:分层解耦 + 策略模式

核心思路:将 ExpBase 的 7 项职责拆分为独立的类,然后用策略模式消除类型码分支。

2.2 新架构

                        ┌─────────────────┐
                        │    AlgBase       │
                        │ (数据获取/报警)   │
                        └────────┬────────┘
                                 │
                        ┌────────┴────────┐
                        │   ExpBase       │  ← 薄抽象层,仅组合各组件
                        │ (模板方法)      │
                        └────────┬────────┘
                                 │
              ┌──────────────────┼──────────────────┐
              │                  │                  │
     ┌────────┴────────┐ ┌──────┴──────┐ ┌────────┴────────┐
     │ LogicAlg        │ │ BoundAlg    │ │ FeedbackAlg     │
     │ (Alg 1)         │ │ (Alg 2/5)   │ │ (Alg 3/4)       │
     └─────────────────┘ └─────────────┘ └─────────────────┘

组件层(组合到 ExpBase 中):
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ VarManager   │ │ BoundChecker │ │ FbStateMachine│ │ StatCollector│
│ 变量生命周期  │ │ 上下限检测    │ │ 反馈状态机    │ │ 统计学习      │
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘

2.3 组件详细设计

组件一VarManager — 变量管理器

职责:变量 map 的初始化、刷新、历史值移位、hold 变量刷新

// 约 150 行
class VarManager {
public:
    // 从共享内存刷新所有变量
    void refreshFromMemory(const std::vector<std::string>& tags);
    // 从 iHyperDB 行数据刷新
    void refreshFromIhdRow(int row, const Eigen::MatrixXd& data,
                           const std::vector<TimePoint>& times);
    // 首次填充(冷启动)
    void firstFill(const std::vector<std::string>& tags, int dataSource);
    // hold(n,T) 变量刷新
    void refreshHoldVars();

    // 变量访问
    double& operator[](const std::string& key);
    std::map<std::string, double>& vars();

    // 快照:记录动作开始时刻的变量状态
    void snapshot(size_t tagCount);
    // 更新动作期间的极值/累积变量
    void updateActionVars(size_t tagCount);

private:
    std::map<std::string, double> mm_vars_;
    std::vector<std::string> pv_keys_;     // pvN_0..pvN_5
    std::vector<std::string> tag_keys_;    // tagN, pN
    std::map<std::string, std::unique_ptr<HoldTime>> hold_times_;
    VarsCache var_cache_;
    size_t pv_num_ = 6;
};

收益

  • 消除 ExpBase 中 ~200 行的 refresh_exp_vars_mem() / refresh_exp_vars_ihd() / first_fill_mm_vars()
  • 变量命名规则tagN, pN, pvN_M, sN, mx_tagN 等)封装在一处
  • 可独立单元测试

组件二BoundChecker — 上下限检测器

职责:上下限比较、检测模式判断

// 约 60 行
class BoundChecker {
public:
    void setLimits(double down, double up);
    void setDetectMode(DetectMode mode);

    // 判断 value 是否超限
    bool isOutOfBounds(double value) const;
    // 获取报警消息(包含合理区间描述)
    std::string buildAlarmMsg(double value, const std::string& unit,
                              const std::string& errorStr) const;
    double limitDown() const;
    double limitUp() const;
    DetectMode detectMode() const;

private:
    double limit_down_ = -32768;
    double limit_up_ = -32768;
    DetectMode detect_mode_ = DetectMode::Default;
};

收益

  • detect_up_down() 逻辑从 ExpBase 抽离
  • Roller3 自己的 detect_up_down() 可以直接复用
  • 哨兵值 -32768/32767/32768 的处理集中管理

组件三FbStateMachine — 反馈状态机

职责:动作反馈的四阶段状态转换

// 约 120 行
enum class FbState { Idle, Started, Done, Timeout, NotHold };

class FbStateMachine {
public:
    // 配置
    void configure(bool keepMode, TimeDur timeout, bool timeMode);

    // 状态转换(每个周期调用)
    FbState update(bool actTriggered, bool feedbackCondition,
                   TimePoint now, VarManager& vars);

    // 查询
    bool isActive() const;
    TimePoint startTime() const;
    TimeDur elapsed() const;

    // 重置
    void reset();

private:
    bool keep_mode_ = false;
    TimeDur timeout_ = 10min;
    bool time_mode_ = false;
    bool started_ = false;
    TimePoint start_time_;
};

收益

  • act_start_done() / act_done() / act_timeout() / act_not_hold() 四合一
  • 状态转换显式化,可画状态图验证
  • 消除 4 个布尔标志:act_started_, act_triggered_, feedback_triggered_, feedback_done_
  • 可独立单元测试(模拟时间推进)

组件四StatCollector — 统计学习器

职责DAA::STA 统计学习的生命周期管理

// 约 80 行
class StatCollector {
public:
    void configure(const std::string& ruleId, const std::string& ruleName,
                   int distMode, bool isLearning);

    // mon 进程添加样本
    void addSample(double value);
    // cron 进程批量处理
    int processBatch(const std::vector<double>& values);
    // 从 DB2 加载置信区间
    bool reloadCiDist(double& limitDown, double& limitUp);

private:
    std::unique_ptr<DAA::STA> sta_ptr_;
    int dist_mode_ = 0;
    bool is_learning_ = true;
    std::string rule_id_;
};

收益

  • cron_proc() 约 60 行逻辑从 ExpBase 抽离
  • 统计相关的 DB2 操作封装

2.4 算法子类化设计

ExpBase 变为只包含公共逻辑的薄层,具体算法行为通过子类多态实现:

// ExpBase 精简后(~300 行)
class ExpBase : public AlgBase {
public:
    AlarmInfo exec_mon() override;   // 模板方法,调用 virtual doMonProc()
    std::vector<AlarmInfo> exec_task(mix_cc::time_range_t) override;
    mix_cc::json exec_cron() override;

protected:
    // 钩子方法 — 子类重写
    virtual AlarmInfo doMonProc() = 0;            // 纯虚,每个子类实现自己的逻辑
    virtual void doInitExtend() {}                // 子类扩展初始化
    virtual bool doFilterCheck() const { return true; }

    // 共享组件
    VarManager vars_;
    BoundChecker bound_;
    FbStateMachine fb_fsm_;
    StatCollector stat_;

    // 共享表达式
    std::unique_ptr<Expression> exp_act_;
    std::unique_ptr<Expression> exp_feedback_;
    std::unique_ptr<Expression> exp_result_;
};

// 各子类实现(每个 60~150 行)
class LogicAlg : public ExpBase {        // Alg 1
    AlarmInfo doMonProc() override {
        if (exp_act_->evaluate()) {
            return buildAlarm(...);
        }
        return {};
    }
};

class BoundAlg : public ExpBase {        // Alg 2, 5
    AlarmInfo doMonProc() override {
        double val = exp_act_->evaluate();
        if (!doFilterCheck()) return {};
        if (is_learning_) stat_.addSample(val);
        if (bound_.isOutOfBounds(val)) {
            return buildAlarm(bound_.buildAlarmMsg(val, ...));
        }
        return {};
    }
};

class BoundHoldAlg : public BoundAlg {   // Alg 5
    AlarmInfo doMonProc() override { /* 加上持续时间逻辑 */ }
};

class FeedbackAlg : public ExpBase {     // Alg 3, 4
    AlarmInfo doMonProc() override {
        double val = exp_act_->evaluate();
        auto state = fb_fsm_.update(val, exp_feedback_->evaluate(),
                                     now_time_, vars_);
        switch (state) {
        case FbState::Done: return evaluateResult();
        case FbState::Timeout: return timeoutAlarm();
        default: return {};
        }
    }
};

2.5 现有子类的影响

子类 当前继承 优化后 说明
ExpTimes ExpBase ExpBase组合 StatCollector, VarManager 丢弃不需要的 FbStateMachine, BoundChecker
ExpSample2D ExpBase ExpBase 使用自己的 fit/pear 逻辑
ExpBound ExpBase ExpBase组合 BoundChecker 复用 BoundChecker
Roller2 ExpBase ExpBase组合 VarManager 丢弃反馈和统计组件
Roller3 ExpBase ExpBase组合 BoundChecker, VarManager 复用 BoundChecker

三、实施路径

分三个阶段,每阶段可独立交付

阶段一:提取组件(不改外部行为)

时间估算2-3 天

  1. 提取 VarManager(最大收益)
    • mm_vars_ 及相关方法移入 VarManager
    • ExpBase 通过 vars_ 成员访问
    • 不改变任何 public 接口
  2. 提取 BoundChecker
    • detect_up_down() 移入 BoundChecker
    • Roller3 的 detect_up_down() 改为复用
  3. 运行现有测试验证

阶段二:提取状态机 + 统计学习器

时间估算1-2 天

  1. 提取 FbStateMachine
    • act_start_done/act_done/act_timeout/act_not_hold 四合一
    • 消除 4 个布尔标志
  2. 提取 StatCollector
    • cron_proc() 统计逻辑移入
  3. 运行验证

阶段三:子类化消除类型码

时间估算2-3 天

  1. 创建 ExpBase 子类LogicAlg, BoundAlg, FeedbackAlg, BoundHoldAlg
  2. migrate build_algorithm.cpp 使用新子类
  3. 清理 ExpBase:删除不再需要的分支逻辑
  4. 全量回归测试

四、收益评估

指标 优化前 优化后
ExpBase 总行数 1956 ~300
每个具体算法类行数 N/A 60~150
exp_type_ 分支 16 处 0
布尔状态标志 12 ~4
mon_proc() 行数 200 ~20模板方法
可单元测试的组件 1整体 6+VarManager, BoundChecker, FbStateMachine, StatCollector, 各算法子类)
新增算法类型的改动 修改 ExpBase 多处分支 新增一个 ~100行子类

五、风险评估

  • 风险低:阶段一和阶段二仅做提取,不改变外部行为,配合回归测试风险可控
  • 风险中:阶段三涉及算法 ID 映射变更,需确保所有 18 个 Alg ID 的映射关系正确
  • 缓解措施:每个阶段完成后执行 task 模式对历史数据回测,对比优化前后的报警输出一致性