1. 从“上下文模式”说起:一个被低估的工程概念
第一次看到“context-mode”这个词,很多人会下意识地把它当成某个框架里的配置项,或者某个库的开关参数。但如果你在工程一线待过几年,尤其是在做系统设计、状态管理、AI应用编排或者复杂前端交互的时候,你会发现“上下文模式”其实是一个贯穿始终的设计哲学。它不是一个具体的API,也不是某个语言独有的语法糖,而是一种组织代码、管理状态、协调模块之间关系的思维方式。
我最早接触这个概念是在做多轮对话系统的时候。当时团队里有个争论:用户的一次输入到底应该被看作一个独立的请求,还是应该被放进整个会话的历史里理解?这个问题的本质就是上下文模式的选择。选错了,系统要么变得极其臃肿,每次请求都带着全部历史;要么变得极其健忘,用户说“帮我改一下刚才那个”,系统一脸茫然。后来我们把这个思路抽象出来,发现它不只适用于对话系统,在任何需要跨调用、跨模块、跨生命周期传递信息的场景里,上下文模式都在暗中决定系统的可维护性和扩展性。
这篇文章想做的事情很直接:把“context-mode”这个听起来有点抽象的词拆开,讲清楚它到底在解决什么问题,有哪些常见的实现模式,每种模式的适用场景和代价是什么,以及在实际项目里怎么选、怎么落地、怎么避坑。不管你是做后端服务、前端状态管理、AI应用编排,还是做工作流引擎,只要你的系统里有“多个步骤需要共享信息”这件事,上下文模式就跟你有关。我会尽量用工程语言而不是学术语言来讲,配上可以直接参考的代码结构和参数选择逻辑,让不同基础的读者都能拿走能用的东西。
2. 上下文模式到底在解决什么问题
2.1 从一次“参数爆炸”说起
先看一个很具体的场景。假设你在做一个订单处理流程,步骤包括:校验用户身份、检查库存、计算价格、生成订单、发起支付、通知物流。最原始的做法是每个步骤写一个函数,然后把所有需要的数据通过参数传进去。写着写着你就会发现,函数签名变成了这样:
def create_order(user_id, sku_id, quantity, coupon_code, address_id, payment_method, inventory_snapshot, price_snapshot, trace_id, retry_count, ...): ...这还只是一个步骤。等到第六个步骤的时候,参数列表已经长到没人愿意看了。更麻烦的是,每加一个字段,所有中间函数的签名都要改一遍。这就是典型的“参数爆炸”,它的根源在于:流程中的多个步骤需要共享一组不断变化的数据,但你没有为这组数据找一个统一的载体。
上下文模式的核心思路就是:把这组跨步骤共享的数据封装成一个上下文对象,让它在流程中流转,每个步骤按需读取和写入,而不是通过参数列表层层传递。这个思路听起来简单,但真正落地的时候,围绕“上下文对象怎么设计、生命周期怎么管理、并发怎么处理、异常怎么回滚”会产生一系列工程决策,这些决策的总和就是所谓的“上下文模式”。
2.2 上下文模式的三个核心维度
我在实际项目里总结下来,判断一个上下文模式是否合适,主要看三个维度。
第一个维度是生命周期。上下文是从一次请求开始到结束就销毁,还是跨请求存在,还是跨用户会话存在?这直接决定了上下文的存储位置和清理策略。请求级的上下文可以放在内存里,会话级的上下文通常需要外部存储,而全局级的上下文则要非常谨慎,因为它很容易变成隐式的全局状态。
第二个维度是可见性。上下文里的数据是对所有步骤可见,还是只对特定步骤可见?全可见的好处是灵活,坏处是容易产生隐式依赖,某个步骤偷偷改了上下文里的字段,下游步骤的行为就变了,排查起来非常痛苦。所以很多成熟的做法会对上下文做分区,比如分成“输入区”“中间状态区”“输出区”,或者按模块划分命名空间。
第三个维度是可变性。上下文是只读的,还是允许步骤往里写数据?只读上下文最安全,但表达能力有限;可变上下文灵活,但需要配套的变更追踪和并发控制。在并发场景下,可变上下文如果处理不好,会出现数据竞争,这类bug往往在压力测试时才暴露出来。
这三个维度组合起来,就形成了不同的上下文模式。下面我会逐一拆解几种常见的模式,以及它们各自适合的场景。
2.3 为什么现在“context-mode”又热起来了
这个概念其实不新,但最近几年它重新被频繁讨论,主要跟两个趋势有关。一是AI应用编排的兴起,大模型调用往往需要把对话历史、工具调用结果、系统提示词、用户画像等信息组织成一个上下文,这个上下文的组织方式直接影响到模型输出的质量和成本。二是微服务和事件驱动架构的普及,一个业务动作往往跨越多个服务,每个服务都需要知道“当前这笔业务进行到哪了”,这就需要一个跨服务的上下文传递机制。
换句话说,上下文模式从“代码组织技巧”升级成了“系统架构的一部分”。以前它可能只是你项目里的一个工具类,现在它可能是决定系统能否水平扩展、能否可观测、能否快速排障的关键设计。这也是为什么我觉得值得花时间把它讲透。
3. 几种常见的上下文模式及其适用场景
3.1 请求级上下文:最轻量也最常用
请求级上下文是我在绝大多数项目里首选的模式。它的生命周期严格绑定一次请求或一次任务执行,请求开始的时候创建,请求结束的时候销毁。实现上通常有两种方式:显式传递和隐式绑定。
显式传递就是把上下文对象作为参数,在函数调用链里一路传下去。这种方式最直观,也最容易测试,缺点是签名会变长,但比起参数爆炸已经好很多了。隐式绑定则是利用语言特性,比如Python的contextvars、Java的ThreadLocal、Go的context.Context,把上下文绑定到当前执行线程或协程上,函数不需要显式接收就能拿到。隐式绑定的好处是代码干净,坏处是隐式依赖不容易发现,测试的时候需要额外设置。
我个人的经验是:核心业务逻辑用显式传递,框架层和横切关注点(比如日志、埋点、鉴权)用隐式绑定。这样既保证了业务代码的可测试性,又避免了在每个函数里手动传trace_id这种无聊的事情。
请求级上下文的一个关键设计点是清理。如果上下文里持有数据库连接、文件句柄或者大对象,请求结束后必须确保释放。我见过一个项目因为上下文里缓存了一个大列表,在高并发下内存直接爆掉。所以上下文里放什么、不放什么,需要有明确的规范。
3.2 会话级上下文:跨请求的状态管理
会话级上下文比请求级复杂一个数量级,因为它要跨多次请求存在。典型场景包括用户登录态、购物车、多轮对话历史。这种上下文通常不能只放在内存里,因为服务可能重启、可能多实例部署,所以需要外部存储,比如Redis、数据库或者专门的会话服务。
设计会话级上下文时,最需要想清楚的是一致性边界。用户在浏览器上点了两下,两个请求同时到达,都去修改购物车,怎么保证不冲突?常见做法是加版本号或者用乐观锁,更新的时候检查版本是否变化。另一个问题是过期策略,会话不能永久存在,否则存储会无限增长,但过期时间设太短又会影响用户体验。我的经验是,根据业务场景设置滑动过期,比如用户每次操作都刷新过期时间,同时设置一个绝对过期上限。
还有一个容易被忽略的点是序列化成本。会话上下文如果很大,每次请求都要从存储里读出来反序列化,请求结束再序列化写回去,这个开销在高频场景下非常可观。所以会话上下文要尽量精简,只放真正需要跨请求共享的数据,能放客户端的就放客户端。
3.3 流程级上下文:长事务和编排场景的利器
流程级上下文介于请求级和会话级之间,它的生命周期绑定一个业务流程,这个流程可能包含多个步骤、多个服务调用,甚至可能跨越较长时间。典型场景包括订单履约、审批流、数据管道。
这种上下文的核心挑战是状态持久化和恢复。流程执行到一半,服务重启了,怎么从断点继续?这就需要把上下文持久化到数据库或者专门的状态存储里,每个步骤执行完后保存一次。恢复的时候根据当前步骤和上下文重新执行。这里有个设计选择:是保存整个上下文快照,还是只保存变更日志?快照简单但存储成本高,变更日志节省空间但恢复逻辑复杂。我一般建议中小规模用快照,大规模用变更日志加定期快照。
流程级上下文还需要考虑补偿机制。如果第三步失败了,前两步已经产生的副作用怎么回滚?这就是Saga模式要解决的问题。上下文里需要记录每个步骤的执行状态和补偿操作,失败时按逆序触发补偿。这块内容展开会很长,但核心思想是:上下文不只是数据载体,还是流程状态的权威记录。
3.4 全局上下文:强大但危险
全局上下文是指在整个应用生命周期内存在、对所有请求可见的上下文。它通常用来存放配置、连接池、缓存客户端这类基础设施对象。全局上下文的问题是它很容易被滥用,变成隐式的全局变量,导致模块之间产生难以追踪的耦合。
我的建议是:全局上下文只放真正全局且不可变的东西。配置可以放,但配置一旦加载就不应该再改;连接池可以放,但连接池本身要线程安全。任何跟具体请求或用户相关的数据,都不应该放进全局上下文。如果发现自己想在全局上下文里放“当前用户”这种字段,那说明架构设计出了问题,应该退回到请求级或会话级上下文。
4. 上下文模式的核心实现细节
4.1 上下文对象的结构设计
上下文对象的结构设计直接影响到后续的可维护性。我见过两种极端:一种是所有字段平铺在一个大对象里,另一种是过度嵌套,访问一个字段要写五层点号。这两种都不好。
比较合理的做法是按领域或职责分区。比如一个订单流程的上下文可以分成几个区:
class OrderContext: def __init__(self): self.request = RequestInfo() # 请求元信息:trace_id、用户ID、时间戳 self.input = OrderInput() # 原始输入:商品、数量、优惠券 self.state = OrderState() # 中间状态:库存快照、价格计算结果 self.output = OrderOutput() # 最终输出:订单号、支付链接 self.meta = ExecutionMeta() # 执行元信息:重试次数、耗时、错误这样分区的好处是:每个步骤只需要关注自己相关的区,依赖关系清晰;序列化的时候可以按区选择是否持久化,比如request和meta通常不需要持久化,state和output需要。另外,分区也方便做访问控制,比如只允许特定步骤写output区。
字段命名上,我建议用明确的业务语义,避免用data、info、temp这种模糊词。一个字段叫inventory_snapshot比叫data1要好一万倍,半年后回来看代码的人会感谢你。
4.2 上下文的创建与传递
上下文的创建时机很关键。请求级上下文通常在请求入口创建,比如Web框架的中间件里。创建的时候要初始化必要的字段,但不要做耗时操作,否则会增加请求延迟。我见过在上下文构造函数里查数据库的写法,这是大忌,因为上下文创建应该尽可能轻。
传递方式上,如果语言支持,优先用语言原生的上下文机制。Go的context.Context是标准做法,Python的contextvars在异步场景下很好用,Java的ThreadLocal要注意线程池场景下的清理问题。如果不支持或者不想用隐式机制,就显式传递,虽然啰嗦但可控。
这里有个实操技巧:在上下文里放一个父子关系的标记。比如一个请求触发了异步任务,异步任务应该继承父上下文的部分字段,但有自己的生命周期。这时候可以创建一个子上下文,复制父上下文的关键字段,同时标记自己的来源。这样在排查问题时可以顺着上下文链找到完整的调用路径。
4.3 上下文的并发安全
并发场景下上下文的安全性是必须考虑的。如果上下文只在单个线程或协程内使用,那不需要额外同步。但如果多个并发任务共享同一个上下文,就需要加锁或者用不可变数据结构。
我的经验是:尽量让上下文单线程独占,不要跨线程共享可变上下文。如果确实需要跨线程传递数据,传递不可变的快照,而不是可变对象本身。比如主线程计算出价格快照,把快照传给工作线程,工作线程只读不写。这样既避免了锁的开销,也避免了数据竞争。
如果非要在上下文里做并发写,那就要明确哪些字段是并发写的,对这些字段用原子操作或者细粒度锁。不要用一把大锁锁住整个上下文,那样并发度会急剧下降。更好的做法是把并发写的部分拆出去,用消息队列或者channel来协调。
4.4 上下文的序列化与持久化
当上下文需要跨进程或跨时间传递时,序列化就不可避免。选择序列化格式时,要考虑几个因素:性能、可读性、兼容性。JSON可读性好但性能一般,Protobuf性能好但需要定义schema,MessagePack介于两者之间。
我一般建议:内部服务之间通信用Protobuf或MessagePack,需要人工排查的日志或调试信息用JSON。序列化的时候要注意版本兼容,新增字段要有默认值,删除字段要保留占位,避免老版本反序列化新数据时出错。
持久化上下文时,要区分热数据和冷数据。热数据是流程执行过程中频繁读写的,放在Redis或内存数据库里;冷数据是流程结束后归档的,放在对象存储或普通数据库里。这样既保证了执行性能,又控制了存储成本。
5. 实操:从零搭建一个上下文管理模块
5.1 需求梳理与方案选型
假设我们要为一个订单处理系统搭建上下文管理模块。需求是:支持请求级上下文,跨多个处理步骤共享数据;支持异步任务继承上下文;支持上下文快照用于问题排查;上下文要能序列化持久化,用于流程恢复。
方案选型上,语言假设用Python,因为它的contextvars在异步场景下表现很好。存储用Redis做热数据,PostgreSQL做归档。序列化用JSON加gzip压缩,因为订单数据量不大,可读性优先。
为什么不选ThreadLocal?因为系统里有异步任务,ThreadLocal在协程切换时会丢失。为什么不选全局字典加请求ID索引?因为那样需要手动管理生命周期,容易泄漏。contextvars是Python官方推荐的异步上下文方案,跟asyncio配合得很好。
5.2 核心代码结构
先定义上下文的数据结构。我用dataclass来做,因为它的字段清晰,序列化方便。
from dataclasses import dataclass, field, asdict from typing import Optional, Dict, Any import time import uuid @dataclass class RequestInfo: trace_id: str = field(default_factory=lambda: uuid.uuid4().hex) user_id: Optional[str] = None created_at: float = field(default_factory=time.time) @dataclass class OrderInput: sku_id: str = "" quantity: int = 0 coupon_code: Optional[str] = None @dataclass class OrderState: inventory_ok: bool = False price_cents: int = 0 step_history: list = field(default_factory=list) @dataclass class OrderContext: request: RequestInfo = field(default_factory=RequestInfo) input: OrderInput = field(default_factory=OrderInput) state: OrderState = field(default_factory=OrderState) meta: Dict[str, Any] = field(default_factory=dict) def to_dict(self): return asdict(self) @classmethod def from_dict(cls, data): ctx = cls() ctx.request = RequestInfo(**data.get("request", {})) ctx.input = OrderInput(**data.get("input", {})) ctx.state = OrderState(**data.get("state", {})) ctx.meta = data.get("meta", {}) return ctx然后是上下文的绑定和获取。用contextvars来实现:
import contextvars _current_context: contextvars.ContextVar[OrderContext] = contextvars.ContextVar("order_context") def set_context(ctx: OrderContext): return _current_context.set(ctx) def get_context() -> OrderContext: ctx = _current_context.get(None) if ctx is None: raise RuntimeError("No context bound to current execution") return ctx def reset_context(token): _current_context.reset(token)在请求入口处创建并绑定上下文:
def handle_request(raw_request): ctx = OrderContext() ctx.request.user_id = raw_request.get("user_id") ctx.input.sku_id = raw_request["sku_id"] ctx.input.quantity = raw_request["quantity"] token = set_context(ctx) try: process_order() return {"order_id": ctx.meta.get("order_id")} finally: reset_context(token)5.3 异步任务继承上下文
异步任务需要继承父上下文的部分字段,但不能直接共享可变对象。做法是创建一个子上下文,复制关键字段:
import asyncio async def async_notify(ctx_snapshot: dict): child_ctx = OrderContext.from_dict(ctx_snapshot) child_ctx.meta["is_async"] = True token = set_context(child_ctx) try: await send_notification(child_ctx) finally: reset_context(token) def trigger_async_notify(): parent = get_context() snapshot = parent.to_dict() asyncio.create_task(async_notify(snapshot))这里的关键是传快照而不是传对象引用。如果直接传parent对象,异步任务修改了字段,主流程也会受影响,这是很隐蔽的bug。
5.4 上下文快照与持久化
快照用于问题排查和流程恢复。实现上就是把上下文序列化成JSON,压缩后存起来:
import json import gzip import redis r = redis.Redis() def save_snapshot(ctx: OrderContext, ttl_seconds=3600): key = f"ctx:{ctx.request.trace_id}" data = json.dumps(ctx.to_dict()).encode("utf-8") compressed = gzip.compress(data) r.setex(key, ttl_seconds, compressed) def load_snapshot(trace_id: str) -> OrderContext: key = f"ctx:{trace_id}" compressed = r.get(key) if not compressed: raise KeyError(f"No snapshot for {trace_id}") data = gzip.decompress(compressed).decode("utf-8") return OrderContext.from_dict(json.loads(data))参数选择上,TTL设一小时是因为订单流程通常几分钟内完成,一小时足够覆盖异常重试的场景。压缩用gzip是因为JSON文本压缩率很高,通常能压到原来的20%到30%,节省Redis内存。
5.5 实操现场记录:一次内存泄漏的排查
上线后跑了一周,监控发现服务内存缓慢增长,每天涨大概200MB,重启后恢复。用memory profiler抓了一下,发现OrderContext对象数量异常多。排查后发现两个问题。
第一个问题是异步任务里创建了子上下文,但异常路径下没有reset。代码里用了try/finally,但有个分支在finally之前就return了,导致reset没执行。修复方法是把reset放在最外层的finally里,确保任何路径都会执行。
第二个问题是快照保存后,上下文对象还被某个全局列表引用着,导致无法回收。那个全局列表本来是用来做调试的,上线时忘了删。修复方法是去掉全局引用,调试信息通过日志输出而不是内存持有。
这两个问题的教训是:上下文的生命周期管理必须严格,创建和销毁要成对出现;上下文对象不要被长期引用,否则contextvars的清理机制也救不了你。
6. 常见问题与排查技巧实录
6.1 上下文丢失:为什么get_context拿不到值
这是最常见的问题,表现是运行时报“No context bound”。原因通常有三种:一是在新线程里执行,contextvars不会自动跨线程传递;二是在异步任务里没有显式复制上下文;三是reset调用时机不对,提前把上下文清掉了。
排查方法:在get_context里打印当前线程ID和协程ID,对比创建上下文时的信息。如果是跨线程问题,用copy_context()手动复制;如果是异步问题,确保在create_task之前捕获上下文快照。
6.2 上下文污染:数据串了怎么办
表现是A请求的数据出现在了B请求的上下文里。这通常是因为上下文对象被复用了,或者用了全局可变对象。排查方法是给每个上下文打上trace_id,在读写上下文的地方都打印trace_id,看哪个环节的trace_id对不上。
预防措施:上下文对象每次请求都新建,不要缓存复用;上下文里的可变字段用default_factory创建,不要用类属性默认值。Python里有个经典坑:dataclass的list字段如果直接写field: list = [],所有实例会共享同一个列表,必须用field(default_factory=list)。
6.3 序列化失败:哪些字段不能序列化
上下文里如果放了数据库连接、文件句柄、lambda函数这类对象,序列化时会报错。排查方法是先尝试json.dumps,看报错信息定位到具体字段。预防措施是在上下文设计时就明确哪些字段需要序列化,把这些字段单独放在一个区里,序列化时只序列化这个区。
如果确实需要序列化复杂对象,可以实现自定义的to_dict和from_dict方法,把复杂对象转换成可序列化的形式。比如数据库连接可以序列化成连接字符串,用的时候再重建。
6.4 性能问题:上下文太大导致变慢
表现是请求延迟随上下文大小线性增长。排查方法是统计上下文序列化后的字节数,以及序列化和反序列化的耗时。如果超过1毫秒,就要考虑优化。
优化方向:一是精简上下文,只放必要字段;二是延迟加载,不常用的字段用到时再查;三是换更高效的序列化格式,比如从JSON换成MessagePack;四是分片存储,把大字段单独存,上下文里只放引用。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| get_context报错 | 上下文未绑定或已重置 | 打印线程和协程ID | 检查绑定和重置的配对 |
| 数据串请求 | 上下文对象被复用 | 打印trace_id对比 | 每次请求新建上下文 |
| 序列化报错 | 上下文含不可序列化对象 | 逐步json.dumps定位 | 分离可序列化字段 |
| 内存持续增长 | 上下文被长期引用 | memory profiler | 移除全局引用,确保reset |
| 延迟随上下文增大 | 上下文过大 | 统计序列化字节数和耗时 | 精简字段,换序列化格式 |
| 异步任务拿不到上下文 | contextvars不跨协程 | 检查create_task时机 | 传快照而非对象引用 |
6.6 几个我踩过的坑
第一个坑是在上下文里放ORM对象。ORM对象通常持有数据库会话,序列化会失败,而且生命周期管理很麻烦。后来改成只放ORM对象的ID和必要字段,用的时候重新查。
第二个坑是用上下文做缓存。有人把上下文当成请求级缓存,什么数据都往里塞,结果上下文越来越大,序列化越来越慢。上下文应该只放流程状态,缓存是另一个独立的问题,用专门的缓存层解决。
第三个坑是忘记清理敏感数据。上下文里如果放了用户密码、token这类敏感信息,快照持久化后会留在存储里。后来我们在序列化前加了一个脱敏步骤,把敏感字段替换成占位符。
7. 上下文模式的扩展与演进
7.1 从单机到分布式
单机上下文用contextvars就够了,但分布式场景下,上下文需要跨服务传递。常见做法是把上下文的关键字段放在请求头里,比如trace_id、user_id、租户ID,服务收到请求后重建上下文。完整的上下文数据放在共享存储里,请求头里只放引用ID。
这种做法的挑战是上下文的一致性。服务A修改了上下文,服务B怎么知道?如果每个服务都从共享存储读写,会有并发问题。解决方案是用版本号加乐观锁,或者把上下文变更做成事件,通过消息队列广播。
7.2 上下文与可观测性
上下文是可观测性的天然载体。trace_id、span_id、用户ID、租户ID这些字段放在上下文里,日志、指标、链路追踪都能自动带上,排查问题时可以一键串联所有相关日志。我现在的做法是:上下文创建时自动生成trace_id,所有日志输出都从上下文取trace_id,这样任何一条日志都能找到完整的调用链。
7.3 上下文与AI应用编排
在AI应用里,上下文模式有了新的含义。一次对话的上下文包括系统提示词、历史消息、工具调用结果、用户画像等,这些信息的组织方式直接影响模型输出。常见的做法是把上下文分成“静态部分”和“动态部分”,静态部分(系统提示词、工具定义)可以缓存,动态部分(历史消息、当前输入)每次组装。这样既能利用模型的缓存机制降低成本,又能保证上下文的完整性。
上下文窗口的管理也是个问题。历史消息太长会超出模型限制,需要做摘要或截断。我的经验是:保留最近N轮完整对话,更早的对话做摘要,摘要里保留关键实体和意图。这样既控制了长度,又不丢失重要信息。
7.4 上下文模式的未来方向
一个明显的趋势是上下文的自动化管理。现在很多上下文管理还是手动的,哪些字段放进去、什么时候清理、怎么序列化,都需要开发者决策。未来可能会有更智能的上下文管理框架,根据访问模式自动优化存储和序列化策略。
另一个方向是上下文的跨语言标准化。现在每种语言都有自己的上下文机制,Go的context、Python的contextvars、Java的ThreadLocal,语义和用法都不一样。如果有一个跨语言的上下文标准,分布式系统的上下文传递会简单很多。不过这需要社区共识,短期内不太可能实现。
8. 一些个人经验与建议
做上下文管理这些年,我最大的体会是:上下文的设计要克制。很多人一开始会把所有能想到的字段都塞进上下文,觉得这样方便。但上下文越大,序列化越慢,并发问题越多,排查越困难。好的上下文设计应该是精简的,只放真正需要跨步骤共享的状态,其他数据通过参数或查询获取。
第二个体会是:上下文的生命周期要明确。创建和销毁必须成对,异常路径也要覆盖。我现在的习惯是在上下文创建的地方就写好清理逻辑,用try/finally包起来,确保任何情况下都能释放。
第三个体会是:上下文要可观测。trace_id是必须的,最好再加上创建时间、当前步骤、执行耗时这些字段。出问题的时候,这些信息能帮你快速定位。我甚至会在上下文里放一个step_history列表,记录每个步骤的执行时间和结果,排查性能问题时非常有用。
最后一个建议是:不要过度设计。如果你的系统只有几个步骤,参数传递就够了,不需要引入上下文。上下文模式的价值在于管理复杂度,如果复杂度本身不高,引入上下文反而增加负担。判断标准很简单:当你发现参数列表超过五个,或者多个步骤需要共享同一组数据时,再考虑上下文模式。