eis/eqpalg/algs/EXPBASE_REFACTOR_PLAN.md

416 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 模式对历史数据回测对比优化前后的报警输出一致性