从单文件到可扩展:我给 stock-sdk 做的一次架构升级
这篇文章不是一份“正式的技术方案文档”,而是站在工具作者的视角,记录我在给 stock-sdk 做一次比较大的架构升级时,真实的思考过程、取舍以及落地方案。
如果你也维护过一个“越写越大的 SDK / 工具库”,大概率会在下面的某些场景里看到自己的影子。
背景:当一个 SDK 开始“失控”
在很长一段时间里,stock-sdk 的代码结构其实非常简单:
- 一个
sdk.ts - 一些工具函数
- 一份类型定义
这样写在早期非常爽:
- 新功能直接往里加
- 一个文件就能看到所有逻辑
- 调试也很直观
但问题在于,它会一直长。
当 sdk.ts 的体量来到一千多行之后,一些信号开始变得明显:
- 找一个方法要不停地滚动
- 改一个功能,总担心影响到旁边完全不相关的逻辑
- 测试文件也跟着一起膨胀
- 心理上开始抗拒“再往这里加东西”
这不是某一行代码的问题,而是结构已经不再适合继续演进。
我给这次重构定下的几个底线
在真正动手之前,我先给自己划了几条“红线”,否则很容易越改越乱:
- 对外 API 必须完全兼容(这是 SDK,不是业务项目)
- 可以慢慢迁,但不能一次性推翻重来
- 结构要能撑得住未来继续加功能
- 不要为了“优雅”而牺牲可读性
这几条基本决定了后面的所有设计选择。
一个核心判断:问题不在逻辑,而在“职责混在一起”
把 sdk.ts 拆开看,会发现里面其实混着几类完全不同的事情:
- HTTP 请求和超时控制
- GBK 编码解码、响应解析
- 腾讯 / 东财等不同数据源的细节
- 行情、K 线、分时、资金流向
- 技术指标计算
这些代码本身并不复杂,复杂的是:
它们全都挤在同一个文件里。
所以这次升级的核心目标也很明确:
把“不同职责的代码”拆到不同层级里,而不是单纯拆文件。
新架构的整体思路(先说人话版)
我最终采用的是一个非常传统、但足够稳的分层思路:
- Core 层:和“股票”无关的基础设施
- Provider 层:对接不同数据源的适配器
- Indicators 层:纯计算逻辑
- SDK 层:一个薄薄的门面
- Types 层:纯类型声明
换句话说:
SDK 只负责“我能做什么”,
Provider 负责“数据从哪来”,
Core 负责“怎么请求、怎么解析”。
为什么我要引入 Provider 层
这是这次重构里最关键的一步。
以前的代码里,腾讯、东方财富的数据处理逻辑是交织在一起的:
- 一个方法里判断市场
- 根据条件拼不同 URL
- 再写一堆 if/else 解析返回值
短期看没问题,长期看非常痛苦。
于是我干脆换了一个思路:
每一个数据源,都只干一件事:把“它自己的接口”适配成 SDK 需要的结构。
比如:
providers/tencent/quote.ts只负责腾讯的行情providers/eastmoney/aShareKline.ts只负责东财的 A 股 K 线
它们的共同点只有一个:
1 | (client: RequestClient, params...) => Promise<T> |
这样一来:
- 新增数据源 = 新增一个目录
- 不需要去碰原有实现
- 测试也可以直接按数据源拆开
Core 层:把“工程脏活”集中处理
Core 层只做一件事:
处理那些“业务不关心,但每个功能都会用到”的细节。
比如:
- 请求超时
- abort 控制
- GBK 解码
- 响应解析
- 通用工具函数
这样做有两个非常直接的好处:
- Provider 的代码会变得非常“干净”
- SDK 层几乎不再关心底层实现细节
RequestClient 基本成了整个 SDK 的“地基”。
SDK 层:刻意做“薄”
重构后的 sdk.ts,我给自己定了一个硬指标:
不超过 200 行。
它只做三件事:
- 创建并持有
RequestClient - 组合 Provider 方法
- 做极少量的参数整理
所有真正的逻辑,都被推到了更合适的地方。
结果也很直观:
- 原来 1000+ 行的文件
- 现在稳定在 150 行左右
阅读体验和心理负担完全不是一个量级。
分层架构图
1 | ┌─────────────────────────────────────────────────────────────────────────┐ |
| 模式 | 应用场景 | 说明 |
|---|---|---|
| 门面模式 (Facade) | StockSDK 类 |
统一对外接口,隐藏内部复杂性 |
| 适配器模式 (Adapter) | providers/ |
不同数据源适配为统一接口 |
| 策略模式 (Strategy) | 指标计算、市场识别 | 可替换的算法实现 |
| 工厂模式 (Factory) | 请求客户端创建 | 统一创建请求实例 |
| 依赖注入 (DI) | Provider 函数 | 注入 RequestClient |
渐进式迁移,而不是“一把梭”
这次升级我刻意没有一次性重写,而是拆成了几个阶段:
- 先抽 Core(风险最低)
- 再拆 Types
- 然后一个一个迁 Provider
- 最后才动测试结构
每一步都有一个明确标准:
- 测试必须全绿
- 对外 API 不变
这样哪怕中途停下来,项目也是健康的。
一个数字对比,最能说明问题
| 项目 | 重构前 | 重构后 |
|---|---|---|
sdk.ts 行数 |
1198 | ~150 |
| 最大单文件 | 1198 | <300 |
| 模块数量 | 少 | 15+ |
| 新增数据源成本 | 高 | 低 |
总代码量几乎没变,但可维护性完全不是一个级别。
写在最后
这次架构升级并没有引入什么“很新”的东西:
- 没有复杂框架
- 没有花哨模式
- 大多数设计都很传统
但它解决了一个非常现实的问题:
这个 SDK 还能不能被我和别人继续放心地往下写。
如果你也在维护一个“慢慢变大的工具项目”,我的经验只有一句话:
当你开始抗拒改代码的时候,问题往往已经不是业务复杂,而是结构该升级了。
希望这次 stock-sdk 的重构思路,能对你有所参考。
Happy Coding!
