# 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 变量刷新 ```cpp // 约 150 行 class VarManager { public: // 从共享内存刷新所有变量 void refreshFromMemory(const std::vector& tags); // 从 iHyperDB 行数据刷新 void refreshFromIhdRow(int row, const Eigen::MatrixXd& data, const std::vector& times); // 首次填充(冷启动) void firstFill(const std::vector& tags, int dataSource); // hold(n,T) 变量刷新 void refreshHoldVars(); // 变量访问 double& operator[](const std::string& key); std::map& vars(); // 快照:记录动作开始时刻的变量状态 void snapshot(size_t tagCount); // 更新动作期间的极值/累积变量 void updateActionVars(size_t tagCount); private: std::map mm_vars_; std::vector pv_keys_; // pvN_0..pvN_5 std::vector tag_keys_; // tagN, pN std::map> 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 — 上下限检测器 **职责**:上下限比较、检测模式判断 ```cpp // 约 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 — 反馈状态机 **职责**:动作反馈的四阶段状态转换 ```cpp // 约 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 统计学习的生命周期管理 ```cpp // 约 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& values); // 从 DB2 加载置信区间 bool reloadCiDist(double& limitDown, double& limitUp); private: std::unique_ptr sta_ptr_; int dist_mode_ = 0; bool is_learning_ = true; std::string rule_id_; }; ``` **收益**: - `cron_proc()` 约 60 行逻辑从 ExpBase 抽离 - 统计相关的 DB2 操作封装 ### 2.4 算法子类化设计 ExpBase 变为只包含公共逻辑的薄层,具体算法行为通过子类多态实现: ```cpp // ExpBase 精简后(~300 行) class ExpBase : public AlgBase { public: AlarmInfo exec_mon() override; // 模板方法,调用 virtual doMonProc() std::vector 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 exp_act_; std::unique_ptr exp_feedback_; std::unique_ptr 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 天 4. **提取 FbStateMachine** - `act_start_done/act_done/act_timeout/act_not_hold` 四合一 - 消除 4 个布尔标志 5. **提取 StatCollector** - `cron_proc()` 统计逻辑移入 6. **运行验证** #### 阶段三:子类化消除类型码 时间估算:2-3 天 7. **创建 ExpBase 子类**(LogicAlg, BoundAlg, FeedbackAlg, BoundHoldAlg) 8. **migrate `build_algorithm.cpp`** 使用新子类 9. **清理 ExpBase**:删除不再需要的分支逻辑 10. **全量回归测试** --- ## 四、收益评估 | 指标 | 优化前 | 优化后 | |------|--------|--------| | 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 模式对历史数据回测,对比优化前后的报警输出一致性