# 统一表达式引擎设计:消除双 FunVars 和 is_exp_alg_ 补丁 > 针对 ExpBase::fun_vars_ 与 ExpModule::fun_vars_ 双重存在、is_exp_alg_ 补丁标志的历史遗留问题的根治方案 --- ## 一、现状问题精准画像 ### 1.1 重复代码对照 ``` ExpBase::refresh_exp_vars_mem() ExpModule::refresh_exp_vars_mem() ───────────────────────────────── ───────────────────────────────── 更新 tagN ← 共享内存 更新 tagN ← 共享内存 更新 pN ← 旧 tagN 更新 pN ← 旧 tagN 更新 pvN_0..pvN_5 历史移位 更新 pvN_0..pvN_5 历史移位 更新 "now" ← epoch ms 更新 "now" ← epoch ms 调用 fun_vars_.refresh_fun_vars(false) (在 update() 中调用 fun_vars_.refresh_fun_vars(false)) 调用 refresh_hold_var() (无 hold_var 处理) 设置 query_time_range_ (无) ``` 两段代码做**几乎相同的事**,操作**同一个 mm_vars**,属于典型的 Cargo Cult 编程。 ``` ExpBase::first_fill_mm_vars() ExpModule::init() ───────────────────────────────── ────────────────── 初始化所有 tagN, pN, pvN_0..pvN_5 初始化所有 tagN, pN, pvN_0..pvN_5 初始化 stime, now, etime, time 初始化 "now" 初始化 sN, mx_tagN, mi_tagN (无) 初始化 mv2_tagN, up_tagN, dw_tagN (无) ``` 也是重复。 ### 1.2 双 FunVars 的冲突风险 ``` ExpBase::fun_vars_ ExpModule::fun_vars_ ───────────────────── ───────────────────── 管理表达式: 管理表达式: exp_act_ (带状态函数替换) pre_result (带状态函数替换) exp_feedback_ (带状态函数替换) exp_result_ (带状态函数替换) pre_result (通过 fun_vars_init 共享) 内部index: 独立计数 内部index: 独立计数 创建的 mm_vars 变量: fun0,fun1,... 创建的 mm_vars 变量: fun0,fun1,... ``` **冲突场景**: - ExpModule::add_exp("pre_result", "KeepC(tag1,1)") → 创建 mm_vars["fun0"] - ExpBase::fun_vars_init() → fun_vars_.add_exp_str("KeepC(tag1,1)") → 也创建 mm_vars["fun0"]! 两个 FunVars 实例的 `index` 都从 0 开始,会向 mm_vars 写入同名的 `fun0` 变量。当 `is_exp_alg_=true` 时 ExpModule 跳过刷新,所以两个 `fun0` 不会在同一周期冲突。但如果某天代码修改导致两个 FunVars 都活跃,就会产生难以排查的 bug。 ### 1.3 `is_exp_alg_` 传递链的脆弱性 ``` AlgBase::is_exp_alg_ = false (默认) │ │ 引用传递 ▼ ExpModule::is_exp_alg_& ← 持有 AlgBase 成员的引用 │ │ ExpBase 构造时 ▼ AlgBase::is_exp_alg_ = true │ │ ExpModule 通过引用看到变化 ▼ ExpModule::is_exp_alg_ == true → 跳过变量刷新 ``` 这条引用链的问题: - ExpModule 的生命周期依赖 AlgBase(引用不能悬挂) - `is_exp_alg_` 被当作"表达式算法"的标记,实际上它是"是否需要 ExpModule 管理变量刷新"的补丁 - 新算法开发者需要理解这个隐式约定才能正确设置 --- ## 二、统一方案:ExpressionEngine ### 2.1 核心思路 **让 ExpModule 吃掉 ExpBase 的表达式管理职责**,成为唯一的表达式引擎。 ``` BEFORE (现状): AFTER (统一后): AlgBase AlgBase ├── exp_mpdule_ptr_ (ExpModule) └── expr_engine_ (ExpressionEngine) │ ├── PRR 表达式 ├── 所有命名表达式 │ ├── 自己的 FunVars │ ├── "pre_result" (PRR) │ └── 变量刷新 (当 is_exp_alg_=false) │ ├── "act" (前提表达式) │ │ ├── "feedback" (反馈表达式) ExpBase (is_exp_alg_=true) │ ├── "result" (结果表达式) ├── exp_act_ (独立的 Expression) │ ├── "sample_X", "sample_Y" ├── exp_feedback_ (独立的 Expression) │ ├── "dataX" ├── exp_result_ (独立的 Expression) │ └── "X1".."X9" (Roller2) ├── 自己的 FunVars ├── 唯一的 FunVars ├── 变量刷新 ├── 唯一的变量刷新 └── 自己的表达式配置加载 └── 统一的配置加载 ``` ### 2.2 ExpressionEngine 完整设计 ```cpp // expression_engine.h // 统一表达式引擎 —— 替代 ExpModule + ExpBase 的表达式管理 class ExpressionEngine { public: // ========== 构造 ========== ExpressionEngine(std::map& mm_vars, std::vector& m_tags); // ========== 表达式注册 ========== // 注册一个命名表达式。返回 0 成功,-1 失败。 // exp_name: "act", "feedback", "result", "sample_X", "sample_Y", // "dataX", "X1"..."X9", "pre_result" 等 // raw_exp_str: 原始表达式字符串(含 KeepC(tag1,1) 等) // use_fun_vars: 是否启用状态函数替换(默认 true) int registerExpression(const std::string& exp_name, const std::string& raw_exp_str, bool use_fun_vars = true); // ========== 每周期数据刷新 ========== // 从共享内存刷新变量 + fun_vars void refreshFromMemory(); // 从 IHDB 行数据刷新变量 + fun_vars void refreshFromIhdRow(int row, const Eigen::MatrixXd& data, const std::vector& times); // 首次填充(冷启动) void firstFill(int data_source); // ========== 表达式求值 ========== double evaluate(const std::string& exp_name); bool evaluateBool(const std::string& exp_name); // ========== FunVars 控制 ========== // 每周期调用一次(通常在 mon_proc 开头) void autoResetFunVars(); // 标记下周期需要重置 void markFunVarsNeedReset(); // 立即强制重置(报警后调用) void forceResetFunVars(); // ========== hold(n,T) 变量 ========== void refreshHoldVars(); // ========== 查询 ========== bool hasExpression(const std::string& exp_name) const; std::string getExpressionStr(const std::string& exp_name) const; std::map& vars() { return mm_vars_; } private: std::map& mm_vars_; std::vector& m_tags_; // 唯一的 FunVars 实例 StatExp::FunVars fun_vars_; bool fun_vars_need_reset_ = false; // 表达式注册表 std::map exp_raw_strs_; // 原始表达式字符串 std::map exp_processed_; // FunVars 处理后的字符串 std::map> exp_ptrs_; // 编译后的表达式 // hold 变量管理 std::map> hold_times_; // 变量缓存(pv 键名管理) VarsCache var_cache_; static constexpr size_t PV_NUM = 6; // 内部方法 void doRefreshVarsMemory(); void doRefreshVarsIhd(int row, const Eigen::MatrixXd& data, const std::vector& times); int initHoldExpStr(const std::string& exp_str); }; ``` ### 2.3 实现要点 ```cpp int ExpressionEngine::registerExpression(const std::string& exp_name, const std::string& raw_exp_str, bool use_fun_vars) { if (exp_ptrs_.count(exp_name)) return 0; // 已注册 exp_raw_strs_[exp_name] = raw_exp_str; std::string processed = raw_exp_str; if (use_fun_vars) { auto [ok, result] = fun_vars_.add_exp_str(raw_exp_str, &mm_vars_); if (!ok) return -1; processed = result; } exp_processed_[exp_name] = processed; exp_ptrs_[exp_name] = std::make_unique(processed, &mm_vars_); return 0; } void ExpressionEngine::refreshFromMemory() { // 1. 刷新 tag/pv 变量(统一的,只做一次) for (size_t i = 0; i < m_tags_.size(); i++) { double current = SingletonTemplate::GetInstance()[m_tags_[i]]; auto idx = std::to_string(i + 1); mm_vars_[var_cache_.p_keys[i]] = mm_vars_[var_cache_.tag_keys[i]]; mm_vars_[var_cache_.tag_keys[i]] = current; for (size_t j = PV_NUM - 1; j > 0; j--) { mm_vars_[var_cache_.pv_keys[i][j]] = mm_vars_[var_cache_.pv_keys[i][j - 1]]; } mm_vars_[var_cache_.pv_keys[i][0]] = current; } auto now = system_clock::now(); mm_vars_["now"] = duration_cast(now.time_since_epoch()).count(); // 2. 刷新 fun_vars(只做一次,处理所有表达式的状态函数) fun_vars_.refresh_fun_vars(false, &mm_vars_); // 3. 刷新 hold 变量 refreshHoldVars(); } void ExpressionEngine::autoResetFunVars() { if (fun_vars_need_reset_) { fun_vars_.refresh_fun_vars(true, &mm_vars_); fun_vars_need_reset_ = false; } } ``` ### 2.4 各算法使用 ExpressionEngine 的方式 ``` // ========== AlgBase ========== class AlgBase { protected: std::unique_ptr expr_engine_; // 替代 exp_mpdule_ptr_ // 删除: is_exp_alg_ }; // AlgBase::init() 中: expr_engine_ = std::make_unique(mm_vars, m_tags); if (prr_ == 1) { expr_engine_->registerExpression("pre_result", pre_exp_str); } // AlgBase::exec_mon_call() 中: if (alarm.alarmed) { expr_engine_->forceResetFunVars(); // 替代 exp_mpdule_ptr_->fun_reset() } // AlgBase::get_prr() 中: expr_engine_->refreshFromMemory(); // 统一刷新(不再有 is_exp_alg_ 判断) bool prr_result = expr_engine_->evaluateBool("pre_result"); // ========== ExpBase (expression algorithms) ========== // init() 中: expr_engine_->registerExpression("act", exp_str); if (feedback_mode_) { expr_engine_->registerExpression("feedback", fb_exp_str); } expr_engine_->registerExpression("result", result_exp_str); expr_engine_->firstFill(data_source_); // exec_mon() 中: expr_engine_->refreshFromMemory(); // 统一刷新 // ... 然后 mon_proc() 使用 expr_engine_->evaluate("act") 等 // ========== TrendSlope2 (non-expression algorithm) ========== // 只需要 PRR: // 在 AlgBase::get_prr() 中 expr_engine_->evaluateBool("pre_result") // 自身的数据获取不受影响 // ========== Roller2 (multi-expression) ========== // init() 中: expr_engine_->registerExpression("pre_exp", pre_str); expr_engine_->registerExpression("X1", x1_str); expr_engine_->registerExpression("X2", x2_str); // ... ``` --- ## 三、消除项清单 统一后,以下内容可以**删除**: ### 从 ExpBase 删除 | 删除项 | 行数 | 说明 | |--------|------|------| | `ExpBase::fun_vars_` | 1 行声明 | 合并到 ExpressionEngine | | `ExpBase::refresh_exp_vars_mem()` | ~25 行 | 合并到 ExpressionEngine::refreshFromMemory() | | `ExpBase::refresh_exp_vars_ihd()` | ~45 行 | 合并到 ExpressionEngine::refreshFromIhdRow() | | `ExpBase::first_fill_mm_vars()` | ~110 行 | 合并到 ExpressionEngine::firstFill() | | `ExpBase::auto_fun_vars_reset()` | ~15 行 | 合并到 ExpressionEngine::autoResetFunVars() | | `ExpBase::fun_vars_init()` | ~6 行 | 不再需要(注册时自动处理) | | `ExpBase::refresh_hold_var()` | ~10 行 | 合并到 ExpressionEngine | | `ExpBase::init_hold_exp_str()` | ~30 行 | 合并到 ExpressionEngine | | `ExpBase::exp_messy_code_check()` | ~8 行 | 合并到 ExpressionEngine | | `exp_act_`, `exp_feedback_`, `exp_result_` | 3 个成员 | 改为通过 expr_engine_->evaluate("act") 等 | ### 从 ExpModule 删除 | 删除项 | 说明 | |--------|------| | **整个 ExpModule 类** | 被 ExpressionEngine 完全替代 | ### 从 AlgBase 删除 | 删除项 | 说明 | |--------|------| | `is_exp_alg_` | 补丁标志不再需要 | | `exp_mpdule_ptr_` | 替换为 `expr_engine_` | ### 总计消除 - **重复的变量刷新代码**:~180 行(ExpBase+ExpModule 各一套) - **重复的 FunVars 实例**:1 个 - **补丁标志 `is_exp_alg_`**:1 个布尔 + 引用传递链 - **ExpModule 整个类**:~140 行(被 ExpressionEngine 取代) - **ExpBase 可精简**:~260 行 --- ## 四、实施路径 ### 阶段一:创建 ExpressionEngine(1 天) 1. 新建 `eqpalg/utility/expression_engine.h/.cpp` 2. 将 ExpBase 的变量刷新逻辑(`refresh_exp_vars_mem/ihd`、`first_fill_mm_vars`)迁入 3. 将 ExpBase 的 FunVars 管理(`auto_fun_vars_reset`、`refresh_hold_var`、`init_hold_exp_str`)迁入 4. 将 ExpModule 的表达式注册机制(`add_exp`、`update`)迁入并重命名为 `registerExpression` / `evaluate` 5. **此阶段不修改 AlgBase/ExpBase,仅创建新类并编写独立单元测试** ### 阶段二:替换 ExpModule(1 天) 6. 将 AlgBase 中的 `exp_mpdule_ptr_` 替换为 `expr_engine_` 7. 适配 `AlgBase::init()` 中的 PRR 注册 8. 适配 `AlgBase::get_prr()` 和 `AlgBase::exec_mon_call()` 中的 fun_reset 调用 9. 适配所有非 ExpBase 算法(TrendSlope2/3、Roller、FaultCode、GlitchDetection) 10. **删除 ExpModule 类** 11. **删除 `is_exp_alg_` 标志** 12. 运行非 ExpBase 算法的回归测试 ### 阶段三:替换 ExpBase 的表达式管理(1-2 天) 13. 将 ExpBase 中的 `exp_act_`/`exp_feedback_`/`exp_result_` 改为通过 `expr_engine_->evaluate()` 访问 14. 删除 ExpBase 的 `fun_vars_`、变量刷新方法、`auto_fun_vars_reset` 等 15. 适配 ExpBase 的所有子类(ExpBound、ExpTimes、ExpSample2D、Roller2、Roller3) 16. 运行全量回归测试 ### 阶段四:清理(0.5 天) 17. 删除 ExpBase 中不再需要的成员和方法 18. 删除 `exp_base.cpp` 中 ~260 行已迁移代码 19. 更新 build_algorithm.cpp 如需要 --- ## 五、收益评估 | 指标 | 优化前 | 优化后 | |------|--------|--------| | FunVars 实例数 | 2 | 1 | | 变量刷新代码份数 | 2(ExpBase + ExpModule) | 1(ExpressionEngine) | | `is_exp_alg_` 补丁 | 存在 | 消除 | | 表达式管理入口 | 3 处(ExpBase独立3成员 + ExpModule map) | 1 处(ExpressionEngine 统一 map) | | 新算法接入方式 | 需了解 is_exp_alg_ 约定 | 直接 registerExpression | | 状态函数冲突风险 | 存在(同名 funN) | 消除 | | ExpModule 类 | 存在 | 删除 |