eis/eqpalg/algs/EXPBASE_REFACTOR_PLAN.md

416 lines
16 KiB
Markdown
Raw Permalink Normal View 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 变量刷新
```cpp
// 约 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 — 上下限检测器
**职责**:上下限比较、检测模式判断
```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<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 变为只包含公共逻辑的薄层,具体算法行为通过子类多态实现:
```cpp
// 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 天
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 模式对历史数据回测,对比优化前后的报警输出一致性