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