1. 从"context-mode"这个命名说起:它到底在解决什么问题
第一次看到context-mode这个词,我的直觉是:这大概率不是一个具体的框架或库的名字,而是一种设计模式或者运行模式的命名。在软件工程里,凡是带-mode后缀的东西,通常都意味着"同一套系统在不同场景下切换行为策略"。比如strict-mode、dark-mode、safe-mode,它们本质上都是在描述"系统在特定条件下应该怎么表现"。
context-mode也是同样的逻辑。它描述的是:一个系统如何根据当前上下文(context)来决定自己的行为模式。这个上下文可以是用户身份、请求来源、运行环境、数据状态、时间窗口,甚至是多个条件的组合。核心思想是——不再用一套固定的逻辑应对所有情况,而是让系统"看情况办事"。
为什么这个概念最近被反复提起?因为现在的软件系统越来越复杂。一个后端服务可能同时要服务 Web 端、移动端、内部管理后台、第三方 API 调用方;一个前端组件可能同时要适配桌面浏览器、平板、手机、甚至嵌入式 WebView。如果所有场景都走同一套逻辑,代码会变成一堆if-else的泥潭,维护成本极高。context-mode的思路就是把这些分支逻辑抽象出来,用"上下文"作为路由键,让每种模式独立演进。
这篇文章适合谁看?如果你正在做以下任何一件事,那这篇内容对你会很有用:正在设计一个需要适配多种场景的系统架构;被if-else嵌套折磨得痛不欲生;想了解如何用模式化的思路管理复杂分支逻辑;或者你只是单纯看到context-mode这个词,想搞清楚它到底指什么。我会从设计思路、核心实现、实操步骤、常见坑四个维度展开,尽量把每个决策背后的"为什么"讲清楚。
需要提前说明的是,context-mode并不是某个官方标准术语,不同团队、不同项目里它的具体含义可能有差异。下面我讲的是基于常见工程实践总结出来的一套通用方法论,你可以根据自己的技术栈和业务场景做调整。
2. 核心设计思路拆解:为什么用"上下文"来驱动模式切换
2.1 传统分支逻辑的三大痛点
在引入context-mode之前,大多数系统的分支逻辑是这么写的:在业务代码里直接判断条件,然后执行不同逻辑。比如一个订单处理函数:
def process_order(order, user): if user.is_vip and order.amount > 1000: # VIP大额订单逻辑 apply_vip_discount(order) send_priority_notification(order) elif user.is_vip: # VIP普通订单逻辑 apply_vip_discount(order) elif order.amount > 1000: # 普通用户大额订单逻辑 require_manual_review(order) else: # 普通逻辑 process_normally(order)这段代码能跑,但问题很明显。第一,条件判断散落在各处。订单处理里有,通知模块里有,折扣计算里可能还有一套。改一个规则要翻好几个文件。第二,组合爆炸。如果再加一个维度,比如"是否是新用户",分支数量直接翻倍。第三,测试困难。你要为每种组合写测试用例,覆盖率上去了,但维护成本也上去了。
我见过一个真实项目,光是用户等级和订单类型的组合判断就写了 200 多行if-else,后来加了一个"促销期"维度,直接失控。这就是没有做模式抽象的代价。
2.2 context-mode 的核心抽象:把"条件"变成"模式"
context-mode的思路是:不再在业务逻辑里判断条件,而是先把上下文归一化,然后根据上下文选择对应的模式处理器。用一张图来理解就是:
请求 → 上下文提取 → 上下文归一化 → 模式路由 → 模式处理器 → 结果关键变化在于:业务逻辑不再关心"当前是什么情况",它只关心"我被哪个模式处理器调用了"。每个模式处理器只负责一种场景,逻辑内聚,互不干扰。
还是用订单的例子,改造后大概是这样:
class OrderContext: def __init__(self, user, order): self.is_vip = user.is_vip self.is_new_user = user.is_new self.amount_level = self._classify_amount(order.amount) self.is_promo_period = self._check_promo() def _classify_amount(self, amount): if amount > 1000: return "large" elif amount > 100: return "medium" return "small" def mode_key(self): return f"{'vip' if self.is_vip else 'normal'}_" \ f"{self.amount_level}_" \ f"{'promo' if self.is_promo_period else 'regular'}"然后模式路由根据mode_key找到对应的处理器。这样做的直接好处是:新增一个维度,只需要在OrderContext里加一个字段,在路由表里注册新的模式,业务代码完全不用动。
2.3 为什么不用策略模式或状态机
有人可能会问:这不就是策略模式吗?或者用状态机也能实现类似效果。确实有重叠,但context-mode和它们有本质区别。
策略模式关注的是"算法可替换",它通常是一对一的关系——一个策略对应一种算法。而context-mode关注的是"场景可组合",上下文是多维度的,模式是这些维度的组合结果。状态机关注的是"状态流转",它强调状态之间的迁移关系,而context-mode不关心迁移,只关心"当前上下文对应哪个模式"。
打个比方:策略模式像是换工具,锤子换扳手;状态机像是红绿灯,红灯变绿灯;context-mode像是换工作模式,同一个人在不同场景下扮演不同角色。三者可以结合使用,但解决的问题层次不同。
2.4 适用场景与不适用场景
context-mode不是银弹,它有明确的适用范围。适合的场景包括:多端适配(Web/移动/小程序)、多租户系统(不同客户不同逻辑)、灰度发布(不同用户走不同版本)、权限分级(不同角色不同行为)、地域化差异(不同地区不同规则)。
不适合的场景也很明确:如果分支维度只有一个,且未来不太可能扩展,那直接写if-else更简单;如果模式之间需要频繁共享状态和互相调用,那强行拆分反而增加复杂度;如果上下文提取本身就很耗时,那每次请求都做一遍归一化可能得不偿失。
我的经验是:当分支维度达到 2 个以上,且组合数量超过 4 种时,就值得考虑 context-mode。低于这个阈值,过度设计反而有害。
3. 核心细节解析:上下文提取、归一化与模式路由
3.1 上下文提取:从哪里拿数据,拿哪些数据
上下文提取是context-mode的第一步,也是最容易被低估的一步。很多人以为上下文就是"请求参数",其实远不止。一个完整的上下文通常包含四类信息:
- 身份上下文:用户 ID、角色、等级、租户 ID、设备类型
- 环境上下文:请求来源、IP 归属地、客户端版本、操作系统、网络类型
- 业务上下文:订单金额、商品类目、交易时间、历史行为
- 运行时上下文:当前时间、系统负载、功能开关状态
提取这些数据时,有几个原则要遵守。第一,只提取模式路由需要的数据,不要把所有能拿到的数据都塞进上下文,否则上下文对象会变成"上帝对象"。第二,提取逻辑要集中,不要散落在各个中间件里,否则排查问题时很难定位。第三,提取失败要有兜底,比如设备类型识别不出来时,默认走"通用模式",而不是直接报错。
我一般会写一个ContextExtractor类,把提取逻辑封装起来:
class ContextExtractor: def extract(self, request): ctx = {} ctx["user_role"] = self._get_user_role(request) ctx["device_type"] = self._detect_device(request) ctx["client_version"] = self._parse_version(request) ctx["tenant_id"] = self._get_tenant(request) ctx["feature_flags"] = self._load_flags(ctx["tenant_id"]) return ctx def _detect_device(self, request): ua = request.headers.get("User-Agent", "") if "Mobile" in ua: return "mobile" elif "Tablet" in ua: return "tablet" return "desktop"注意:设备识别不要只依赖 User-Agent,它很容易被伪造或篡改。生产环境建议结合客户端主动上报的字段做双重校验。
3.2 上下文归一化:把"脏数据"变成"干净的模式键"
提取出来的上下文往往是"脏"的——设备类型可能是iPhone、iOS、Apple,客户端版本可能是1.2.3、v1.2.3、1.2.3-beta。如果直接拿这些值去路由,模式表会爆炸。所以需要归一化。
归一化的核心是定义标准值域。比如设备类型只允许mobile、tablet、desktop、unknown四个值;客户端版本只保留主版本号;用户角色映射到枚举值。归一化之后,再生成模式键。
class ContextNormalizer: DEVICE_MAP = { "iphone": "mobile", "android": "mobile", "ipad": "tablet", "tablet": "tablet", "windows": "desktop", "mac": "desktop", } def normalize(self, raw_ctx): normalized = {} device = raw_ctx.get("device_type", "").lower() normalized["device"] = self.DEVICE_MAP.get(device, "unknown") normalized["role"] = self._normalize_role(raw_ctx.get("user_role")) normalized["version"] = self._major_version(raw_ctx.get("client_version")) return normalized def _major_version(self, version): if not version: return "0" return version.lstrip("v").split(".")[0]归一化还有一个隐藏价值:它让模式键变得可预测。你可以提前枚举出所有可能的模式键,然后为每个键注册处理器。如果某个键没有对应的处理器,说明要么归一化逻辑有遗漏,要么出现了预期外的场景,这时候可以打日志告警,而不是静默走默认逻辑。
3.3 模式路由:匹配策略与优先级设计
模式路由的核心是"给定模式键,找到处理器"。最简单的做法是用字典直接映射:
MODE_HANDLERS = { "mobile_vip_v2": MobileVipHandlerV2, "mobile_normal_v2": MobileNormalHandlerV2, "desktop_vip_v2": DesktopVipHandlerV2, # ... }但实际场景中,模式键的组合可能非常多,不可能为每个组合都写一个处理器。这时候需要引入优先级和继承机制。我的做法是:定义一组匹配规则,按优先级从高到低排列,第一个匹配成功的规则生效。
ROUTING_RULES = [ # (匹配条件, 处理器) (lambda ctx: ctx["device"] == "mobile" and ctx["role"] == "vip", MobileVipHandler), (lambda ctx: ctx["device"] == "mobile", MobileDefaultHandler), (lambda ctx: ctx["role"] == "vip", VipDefaultHandler), (lambda ctx: True, DefaultHandler), # 兜底 ]这种设计的精髓在于兜底规则必须存在。我踩过一次坑:某次上线新增了一个设备类型foldable,但路由表里没有对应规则,结果所有折叠屏用户都收到了 500 错误。后来加了兜底规则,即使匹配不到具体模式,也能走默认逻辑,最多是体验差一点,不会直接崩。
提示:路由规则的数量建议控制在 20 条以内。超过这个数量,说明模式划分太细,应该考虑合并或者引入二级路由。
3.4 模式处理器的设计原则
模式处理器是真正干活的单元。设计时有几个原则:单一职责,一个处理器只处理一种模式;无状态,处理器本身不保存状态,所有状态通过上下文传入;可组合,多个处理器可以共享一些工具函数,但不要互相调用。
我见过有人把处理器写成继承体系,BaseHandler下面挂十几个子类,子类之间还互相调用。这种设计看起来很"面向对象",实际上很难维护。更好的做法是组合优于继承:把公共逻辑抽成独立的工具函数或服务,处理器按需调用。
4. 实操过程:从零搭建一个 context-mode 系统
4.1 环境准备与技术选型
下面我用 Python 做一个完整的演示。选 Python 是因为它写起来直观,逻辑清晰,换成 Java、Go、Node.js 思路完全一样。环境要求很简单:Python 3.8+,不需要额外依赖,标准库足够。
项目结构建议这样组织:
context_mode/ ├── context/ │ ├── extractor.py # 上下文提取 │ ├── normalizer.py # 上下文归一化 │ └── models.py # 上下文数据模型 ├── routing/ │ ├── rules.py # 路由规则定义 │ └── router.py # 路由器实现 ├── handlers/ │ ├── base.py # 处理器基类 │ ├── mobile.py # 移动端处理器 │ ├── desktop.py # 桌面端处理器 │ └── default.py # 默认处理器 └── main.py # 入口这个结构的好处是职责边界清晰。上下文相关的东西都在context/下,路由逻辑在routing/下,业务处理器在handlers/下。新人接手时,看一眼目录就知道系统怎么组织的。
4.2 定义上下文数据模型
先定义上下文的数据结构。我用dataclass来做,因为它自带__init__和__repr__,调试时很方便:
from dataclasses import dataclass, field from typing import Dict, Optional @dataclass class RequestContext: user_id: str user_role: str = "normal" device_type: str = "unknown" client_version: str = "0" tenant_id: str = "default" feature_flags: Dict[str, bool] = field(default_factory=dict) extra: Dict[str, str] = field(default_factory=dict) def mode_key(self) -> str: return f"{self.device_type}:{self.user_role}:{self.client_version}"mode_key的格式可以根据需要调整。我习惯用冒号分隔,因为可读性好,日志里一眼能看出各个维度。如果维度很多,也可以考虑用哈希,但哈希的缺点是排查问题时看不出原始值,所以我一般不用。
4.3 实现上下文提取器
提取器的职责是从原始请求中抽取上下文。这里我用一个模拟的请求对象来演示:
class ContextExtractor: ROLE_MAP = { "admin": "admin", "vip": "vip", "premium": "vip", "normal": "normal", } def extract(self, request) -> RequestContext: return RequestContext( user_id=request.get("user_id", "anonymous"), user_role=self._normalize_role(request.get("role")), device_type=self._detect_device(request.get("user_agent", "")), client_version=self._parse_version(request.get("version", "0")), tenant_id=request.get("tenant_id", "default"), feature_flags=request.get("flags", {}), ) def _normalize_role(self, role: Optional[str]) -> str: if not role: return "normal" return self.ROLE_MAP.get(role.lower(), "normal") def _detect_device(self, ua: str) -> str: ua_lower = ua.lower() if "mobile" in ua_lower or "iphone" in ua_lower or "android" in ua_lower: return "mobile" if "ipad" in ua_lower or "tablet" in ua_lower: return "tablet" return "desktop" def _parse_version(self, version: str) -> str: if not version: return "0" return version.lstrip("v").split(".")[0]这里有个细节值得说:_normalize_role里我把premium也映射到了vip。这是业务上的归一化——虽然用户系统里区分了vip和premium,但在模式路由层面,它们的行为是一样的,所以合并处理。这种"业务语义归一化"是 context-mode 的精髓之一,它让模式数量大幅减少。
4.4 实现路由器
路由器负责根据上下文找到处理器。我采用"规则列表 + 优先级"的方式:
class ContextRouter: def __init__(self): self.rules = [] def register(self, condition, handler, priority=0): self.rules.append((priority, condition, handler)) self.rules.sort(key=lambda x: -x[0]) def route(self, ctx: RequestContext): for priority, condition, handler in self.rules: if condition(ctx): return handler raise RuntimeError(f"No handler matched for context: {ctx.mode_key()}")注册规则时,优先级高的先匹配。比如"移动端 VIP 用户"的规则优先级设为 100,"移动端用户"设为 50,"VIP 用户"设为 30,"所有用户"设为 0。这样组合越具体的规则优先级越高,符合直觉。
router = ContextRouter() router.register(lambda c: c.device_type == "mobile" and c.user_role == "vip", MobileVipHandler(), priority=100) router.register(lambda c: c.device_type == "mobile", MobileDefaultHandler(), priority=50) router.register(lambda c: c.user_role == "vip", VipDefaultHandler(), priority=30) router.register(lambda c: True, DefaultHandler(), priority=0)4.5 编写模式处理器
处理器是真正执行业务逻辑的地方。我定义一个基类,约定接口:
class BaseHandler: def handle(self, ctx: RequestContext, payload: dict) -> dict: raise NotImplementedError def name(self) -> str: return self.__class__.__name__然后实现具体的处理器:
class MobileVipHandler(BaseHandler): def handle(self, ctx, payload): return { "mode": "mobile_vip", "layout": "mobile_compact", "discount": 0.85, "priority_support": True, "message": f"欢迎 VIP 用户 {ctx.user_id}", } class MobileDefaultHandler(BaseHandler): def handle(self, ctx, payload): return { "mode": "mobile_default", "layout": "mobile_compact", "discount": 1.0, "priority_support": False, "message": f"欢迎用户 {ctx.user_id}", } class VipDefaultHandler(BaseHandler): def handle(self, ctx, payload): return { "mode": "vip_default", "layout": "desktop_full", "discount": 0.9, "priority_support": True, "message": f"欢迎 VIP 用户 {ctx.user_id}", } class DefaultHandler(BaseHandler): def handle(self, ctx, payload): return { "mode": "default", "layout": "desktop_full", "discount": 1.0, "priority_support": False, "message": f"欢迎用户 {ctx.user_id}", }注意每个处理器返回的结构是一致的,都包含mode、layout、discount、priority_support、message这几个字段。这是契约——不管走哪个模式,调用方拿到的数据结构是一样的,只是值不同。这样调用方不需要关心当前是什么模式,直接按统一结构处理即可。
4.6 串联完整流程
最后把所有组件串起来:
class ContextModeEngine: def __init__(self): self.extractor = ContextExtractor() self.router = ContextRouter() def execute(self, request, payload): ctx = self.extractor.extract(request) handler = self.router.route(ctx) result = handler.handle(ctx, payload) result["_handler"] = handler.name() result["_context"] = ctx.mode_key() return result跑一个测试:
engine = ContextModeEngine() engine.router.register(lambda c: c.device_type == "mobile" and c.user_role == "vip", MobileVipHandler(), priority=100) engine.router.register(lambda c: c.device_type == "mobile", MobileDefaultHandler(), priority=50) engine.router.register(lambda c: c.user_role == "vip", VipDefaultHandler(), priority=30) engine.router.register(lambda c: True, DefaultHandler(), priority=0) # 场景1:移动端VIP result = engine.execute( {"user_id": "u001", "role": "vip", "user_agent": "iPhone", "version": "v2.1.0"}, {} ) print(result) # {'mode': 'mobile_vip', 'layout': 'mobile_compact', 'discount': 0.85, ...} # 场景2:桌面端普通用户 result = engine.execute( {"user_id": "u002", "role": "normal", "user_agent": "Windows", "version": "v1.0.0"}, {} ) print(result) # {'mode': 'default', 'layout': 'desktop_full', 'discount': 1.0, ...}到这里,一个最小可用的 context-mode 系统就跑通了。代码量不大,但结构清晰,扩展性强。新增一个模式,只需要写一个处理器,注册一条规则,其他代码完全不用动。
5. 常见问题与排查技巧实录
5.1 模式匹配不到,系统直接报错
这是最常见的线上问题。原因通常是上下文归一化后产生了预期外的值,而路由表里没有对应的兜底规则。排查思路:先看日志里打印的mode_key,确认是哪个维度出了问题;然后检查归一化逻辑,看是否有遗漏的映射;最后确认兜底规则是否存在且优先级最低。
我的做法是在路由失败时打印完整的上下文对象,而不是只打印mode_key。因为mode_key是归一化后的结果,看不出原始值。有一次线上报错,mode_key显示unknown:normal:0,但看不出为什么是unknown,后来打印原始User-Agent才发现是一个新出的浏览器标识没被识别。
5.2 模式处理器之间逻辑重复
刚开始用 context-mode 时,很容易写出大量重复代码。比如MobileVipHandler和MobileDefaultHandler里都有"设置移动端布局"的逻辑。这时候应该把公共逻辑抽出来,而不是让处理器互相继承。
我的做法是按维度拆分工具函数:设备相关的逻辑放device_utils,角色相关的放role_utils,然后处理器按需调用。这样既避免了重复,又保持了处理器的独立性。
5.3 上下文提取性能瓶颈
如果每次请求都要查数据库、调远程接口来提取上下文,性能会很差。优化思路有三个:缓存,把不常变的上下文(如用户角色)缓存起来;懒加载,只在路由需要时才提取对应维度;异步预取,在请求进入业务逻辑前就并行提取多个维度。
我一般会用请求级别的缓存,同一个请求内多次提取同一维度只查一次。跨请求的缓存要谨慎,因为用户角色可能变更,缓存过期时间不宜过长。
5.4 模式数量膨胀
用了一段时间后,模式数量可能从最初的 4 个膨胀到 40 个。这时候要反思:是不是模式划分太细了?很多模式其实可以合并。比如mobile_vip_v1和mobile_vip_v2,如果 v1 和 v2 的行为差异很小,完全可以合并成一个模式,在处理器内部再判断版本。
我的经验是:当模式数量超过 15 个时,就应该做一次合并审查。合并的原则是"行为差异小于 20% 的模式可以合并"。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 路由匹配失败 | 缺少兜底规则 | 检查路由表最后一条规则 | 添加lambda c: True兜底 |
| 模式行为不符合预期 | 归一化映射错误 | 打印原始上下文对比 | 修正映射表 |
| 性能下降 | 上下文提取耗时 | 加埋点统计各维度耗时 | 引入缓存或懒加载 |
| 模式数量过多 | 划分粒度过细 | 统计各模式调用频率 | 合并低频相似模式 |
| 处理器逻辑重复 | 缺少公共工具 | 代码审查 | 抽取工具函数 |
| 新增维度后大量报错 | 未做兼容处理 | 检查新维度的默认值 | 新维度设默认值并加兜底 |
5.6 几个我踩过的坑
第一个坑:归一化时把大小写搞混了。VIP和vip被当成两个不同的角色,导致路由匹配失败。后来统一在归一化时转小写,问题解决。
第二个坑:路由规则顺序写错。把"所有用户"的兜底规则放在了最前面,结果所有请求都走了默认处理器,VIP 逻辑完全没生效。这个 bug 很隐蔽,因为系统不报错,只是行为不对。后来我强制要求兜底规则必须显式标记priority=0,且注册时做校验。
第三个坑:上下文对象被意外修改。有个处理器在handle方法里修改了ctx的字段,导致后续逻辑拿到的是被污染的数据。后来我在RequestContext上加了一个约定:处理器只能读,不能写。需要传递数据时,通过payload参数传递。
提示:如果你的系统对稳定性要求很高,建议在路由层加一个"模式命中率"监控。正常情况下,兜底规则的命中率应该很低(比如低于 5%)。如果兜底命中率突然升高,说明归一化或路由规则出了问题,可以提前告警。
6. 进阶玩法:让 context-mode 更智能
6.1 动态规则加载
硬编码的路由规则有个问题:改规则要重新部署。如果规则经常变,可以考虑把规则配置化,从配置中心或数据库加载。规则用表达式描述,比如device == 'mobile' and role == 'vip',然后用eval或专门的表达式引擎解析。
这样做的好处是运营人员也能改规则,不用等开发排期。但要注意安全,表达式引擎要限制可访问的变量和函数,避免注入风险。
6.2 模式灰度与 A/B 测试
context-mode 天然适合做灰度。比如你想让 10% 的移动端用户走新版本,可以在上下文里加一个bucket字段,根据用户 ID 哈希取模得到 0-99 的值,然后路由规则里判断bucket < 10走新模式。
def assign_bucket(user_id, total=100): return hash(user_id) % total # 路由规则 router.register( lambda c: c.device_type == "mobile" and c.extra.get("bucket", 100) < 10, MobileNewHandler(), priority=90 )这种灰度方式比在业务代码里写if random() < 0.1要可控得多,因为灰度逻辑集中在路由层,业务代码完全无感知。
6.3 模式执行链路追踪
当模式数量多了之后,排查问题需要知道"这个请求走了哪条规则、命中了哪个处理器、耗时多少"。我的做法是在路由器里加埋点:
import time def route(self, ctx): for priority, condition, handler in self.rules: start = time.time() matched = condition(ctx) elapsed = time.time() - start if matched: log.info(f"matched rule priority={priority} handler={handler.name()} " f"eval_time={elapsed*1000:.2f}ms ctx={ctx.mode_key()}") return handler raise RuntimeError("no handler matched")这样每次请求都能看到规则匹配的详细过程,排查问题时非常有用。如果某条规则的eval_time特别长,说明条件表达式太复杂,需要优化。
6.4 与配置中心的结合
生产环境里,我建议把路由规则和模式开关放到配置中心。这样可以在不重启服务的情况下:临时关闭某个模式、调整规则优先级、新增灰度规则。配置变更时要有版本记录和回滚机制,避免误操作导致线上故障。
配置的结构大概是这样:
{ "rules": [ {"priority": 100, "condition": "device=='mobile' and role=='vip'", "handler": "MobileVipHandler"}, {"priority": 50, "condition": "device=='mobile'", "handler": "MobileDefaultHandler"}, {"priority": 0, "condition": "true", "handler": "DefaultHandler"} ], "disabled_handlers": [], "version": "20240115-001" }每次配置变更都生成一个新版本号,出问题时可以快速回滚到上一个版本。这个机制在灰度发布时特别有用。
7. 一些个人体会
context-mode这套东西,我用了大概两年多,最大的感受是:它把"变化"这件事从业务代码里赶了出去。以前每次加需求,都要在业务逻辑里加if;现在加需求,只需要加一个处理器、注册一条规则。业务代码越来越稳定,变化都集中在路由层和处理器层。
但它也不是没有代价。最明显的是代码量增加了。一个简单的if-else可能 5 行搞定,用 context-mode 要写 50 行。所以我的建议是:不要为了用而用。如果分支逻辑很简单,直接写if-else完全没问题。只有当分支维度多、组合复杂、变化频繁时,context-mode 的价值才体现出来。
另外一点体会是:上下文的设计比路由的设计更重要。上下文提取和归一化做得好,路由规则就会很简洁;上下文做得不好,路由规则就会变成另一堆if-else。我见过有人把路由规则写得极其复杂,仔细一看,是因为上下文里塞了太多原始数据,没有做归一化。所以如果你打算用 context-mode,先把上下文设计好,后面的事情会顺很多。
最后分享一个小技巧:给每个模式处理器写一个"自描述"方法,返回这个模式的适用场景、行为特征、负责人等信息。这样新人接手时,不用翻代码就能知道每个模式是干什么的。这个习惯帮我省了很多沟通成本。