16 KiB
16 KiB
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 天
- 提取 VarManager(最大收益)
- 将
mm_vars_及相关方法移入 VarManager - ExpBase 通过
vars_成员访问 - 不改变任何 public 接口
- 将
- 提取 BoundChecker
detect_up_down()移入 BoundChecker- Roller3 的
detect_up_down()改为复用
- 运行现有测试验证
阶段二:提取状态机 + 统计学习器
时间估算:1-2 天
- 提取 FbStateMachine
act_start_done/act_done/act_timeout/act_not_hold四合一- 消除 4 个布尔标志
- 提取 StatCollector
cron_proc()统计逻辑移入
- 运行验证
阶段三:子类化消除类型码
时间估算:2-3 天
- 创建 ExpBase 子类(LogicAlg, BoundAlg, FeedbackAlg, BoundHoldAlg)
- migrate
build_algorithm.cpp使用新子类 - 清理 ExpBase:删除不再需要的分支逻辑
- 全量回归测试
四、收益评估
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 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 模式对历史数据回测,对比优化前后的报警输出一致性