第四十五章 模块设计模式

篇别:第十三篇 最佳实践

本章学习目标

  • 运用 低耦合、高内聚 原则划分 模块边界depends
  • 通过 AbstractModel、Mixin、工具方法 复用逻辑,避免复制粘贴
  • 正确使用 UserErrorValidationError异常链,区分 用户可见错误与程序缺陷
  • 建立 结构化日志 习惯,服务 排障与审计
  • 处理 多公司、多货币数据隔离与金额一致性

导读:模式是「可维护性的复利」

Odoo 模块常在 数年 内持续迭代。过早把销售/库存/会计绑死在一个自定义模块里,后续 分包、复用、开源 都会痛。本章把 最常见解耦、复用、错误、日志、多公司/货币 固定成 可教可练 的条目;与 第十二章(继承)第七章(安全)第四十六章(高级主题) 交叉。


45.1 模块解耦

45.1.1 知识要点

  • depends 最小化:仅依赖 真正调用的模块可选扩展try/except ImportError_inherit 条件(谨慎)。
  • 扩展优于复制_inherit 标准模型勿 fork 整份 sale 进私有模块。
  • 接口层:跨模块调用 稳定方法名 _prepare_*_get_*_values避免 深钻对方 private 方法
  • 数据耦合XML ID 引用 上游模块注明版本升级前读 changelog

45.1.2 案例

:复制 sale.order.action_confirm 200 行到自定义模块。

_inheritsuper 前后插入 5 行 钩子调用 _library_validate_order()

45.1.3 截图占位

图 45-1 模块依赖图(示意)

45.1.4 本节练习

  1. 简答「依赖过多」三个负面后果
  2. 判断:为避免依赖,应用 eval 动态 import 调其他模块。( )

参考答案提示:2. 错;难测、难审计、升级炸裂


45.2 代码复用

45.2.1 知识要点

  • AbstractModel_name = 'my.abstract.mixin' _abstract = True共用的 compute、check、格式化
  • Python 工具纯函数 def format_isbn(code):tools/无 ORM 依赖 易测。
  • Domain 工厂@api.model def _domain_open_loans(self): 返回 list列表/报表/规则 共用。
  • 反模式在 XML 里堆巨型 domain 复制 十次改一处漏九处

45.2.2 案例

class LibraryDiscountMixin(models.AbstractModel):
    _name = "library.discount.mixin"
    _description = ...

    def _apply_discount(self, lines, percent):
        for line in lines:
            line.discount = max(line.discount, percent)

45.2.3 本节练习

  1. 实操:把 重复 domain 抽为 @api.model 方法两处调用
  2. 简答Mixin_inherits 委托继承 选型一句?

参考答案提示:2. 行为复用 用 Mixin;「是一条记录的组成部分」 用委托继承。


45.3 错误处理

45.3.1 知识要点

  • UserError用户可理解可恢复填错字段、业务规则)。
  • ValidationError数据约束Python + SQL)。
  • AccessError权限UserError 冒充
  • 程序错误KeyError记录日志500(或 Odoo 标准错误页), 吞掉。
  • raise ... from e:保留 异常链 便于 排障

45.3.2 案例

import requests
from odoo.exceptions import UserError

try:
    external_api.call()
except requests.Timeout as e:
    raise UserError(_("同步超时,请稍后重试")) from e

45.3.3 本节练习

  1. 判断所有异常 都应转成 UserError。( )
  2. 简答:何时应 _logger.exceptionre-raise

参考答案提示:1. 错。2. 未预期缺陷需要栈追踪 时。


45.4 日志记录

45.4.1 知识要点

  • _logger = logging.getLogger(__name__)模块级 logger
  • 结构化 extra{'integration': 'dhl', 'picking_id': picking.id},便于 ELK 检索
  • 级别INFO 业务流程WARNING 可恢复异常ERROR 失败需人看
  • 隐私 记录 完整卡号、密码GDPR日志保留策略

45.4.2 案例

_logger.warning("DHL rate limit, backing off", extra={"partner_ref": partner.ref})

45.4.3 截图占位

图 45-4 结构化日志在 ELK 中的字段

45.4.4 本节练习

  1. 实操:为 集成失败 记录 extra={'partner_ref': ...} 一行日志。
  2. 简答生产 为何慎用 DEBUG 全量开

参考答案提示:2. 体积、PII、I/O、性能


45.5 多公司架构(新增)

45.5.1 知识要点

  • company_id / company_ids记录归属规则 ('company_id', 'in', company_ids + [False]) 常见。
  • 共享主数据company_id 表示 全局规则显式允许须测试 切换公司 菜单。
  • sudo + with_company极危险知悉后果内部封装 使用,并文档化
  • 报表默认公司上下文用户所选公司 易混显式 with_company

45.5.2 案例

错误sudo().create({...}) 未设 company_id新记录落在错误公司

45.5.3 截图占位

图 45-5 用户可访问公司列表与当前公司

45.5.4 本节练习

  1. 简答sudo + with_company 联用 风险
  2. 实操切换公司列表 domain 是否仍 正确过滤任选一模型 验证)。

参考答案提示:1. 绕过规则 + 写错公司 + 审计缺口


45.6 多货币处理(新增)

45.6.1 知识要点

  • Monetarycurrency_fieldcompany_id.currency_id 区分 单据币别 / 公司本位币
  • 汇率res.currency.rate 按日期历史单据 应用 当时汇率 而非 今日
  • 报表统一公司币折算 简单 sum 外币金额
  • 舍入decimal.precision会计规则 对齐;浮点 直接 == 比较

45.6.2 案例

报表行amount_foreign * rate_at(date)amount_companyrate 来自 _convertcurrency._convert(以版本 API 为准)。

45.6.3 截图占位

图 45-6 货币与汇率表单

45.6.4 本节练习

  1. 实操:用 Monetary 展示 外币单据公司币折算参考 account 模块 字段设计)。
  2. 简答「用今天汇率重算去年报表」 何时 错误

参考答案提示:2. 历史业绩分析时点汇率仅「当前估值」 可用现价。


本章综合练习

  1. 设计插件式定价规则策略模式草图上下文、策略接口、注册表)。
  2. 文档模块 README 必备章节不少于 6 项依赖、配置、已知问题 等)。
  3. 综合同一 mixin销售与采购 同时继承时 如何避免方法名冲突
  4. 实操:列出你维护模块中 一处 可抽成 mixin重复代码草拟 _name

本章对应白皮书目录:第四十五章 模块设计模式。