依赖管理
程序产生的 bug 中,有相当一部分来自于依赖管理错误。
获取依赖之难
在程序中,一个依赖可以被理解成当前的程序内存所存在的一个变量,亦或者是磁盘上存在的某个文件数据,亦或者是远程服务器上保存的一个数据。不论如何,当我们的程序在某个函数中希望获取某个信息来进行计算的时候,就不得不
blog补充状态管理 从业务代码中抽离生命周期,让容器托管创建和销毁,补充声明式思维和声明式状态
单例模式与全局变量,单例终结。 如何将全局变量重构为局部变量
依赖管理,在软件设计中添加
依赖显式化
程序的美德,自己尽全力解决依赖,提高健壮性
高内聚低耦合,solid,DRY,单元测试,代码即文档, 函数式,分布式系统
程序复杂的一个重要原因是外部依赖的不确定性
软件开发,不怕修改全局变量,怕的是你整个项目到处都在修改全局变量
问题的本质
软件失败的根因,往往不是逻辑错误,而是对环境与依赖的假设过强。系统在启动或运行时对某个外部依赖做"强定位",一旦定位失败,系统整体拒绝运行。这是一个过度确定性建模问题:依赖被建模为必然存在,定位方式是确定性路径或单点 key,失败策略是 fail-fast 加全局崩溃。
依赖的定位方式
不同系统采用不同的依赖定位策略,从简单到复杂形成演化轴:Map Lookup → Filtered Collection → Pattern Query。
Java IoC 容器使用简单的 Map 结构(BeanName → Bean),这是"直接定位"而非"查询"。这种设计是有意为之的,因为 IoC 是组件装配系统,不是组件查询系统,确定性比表达能力更重要。
相比之下,文件系统使用 glob 模式(src/**/*.c)匹配路径集合,CSS 使用选择器在 DOM 树上进行结构模式匹配,响应式配置中心则支持查询式的订阅规则。这些系统都引入了一层间接性,允许多解和运行期决策。
健壮性的解决方向
解耦身份与定位
文件路径同时承担了身份、位置和加载策略三种角色,这三者被错误地压缩成一个字符串。更健壮的方式是用逻辑名代替物理路径,例如用 config:main 而非 /etc/myapp/config.yaml,然后通过环境变量、搜索路径列表、默认值和用户覆盖等策略解析。PATH、LD_LIBRARY_PATH、DNS 和 pkg-config 等系统早已验证了这种模式的有效性。
从必需依赖到能力检测
不问"你在哪里",而问"你能做什么"。反模式是必须存在 /usr/lib/libssl.so,改为检测是否支持 TLS、AES-GCM 等能力。这是接口驱动而非实现驱动,定位结果是一个候选集而非单点绑定。
弱化失败语义
失败不等于退出,而是降级、禁用功能、警告并继续执行。工程上要点是功能模块可独立失效,核心路径最小化,启动成功优先于完美状态。
声明式依赖解析
从硬编码转向依赖声明,系统行为变为收集候选、按策略匹配、产出一个绑定结果。这本质上是把依赖解析做成一个查询问题,允许可选依赖、多候选路径和运行期决策。
响应式与依赖追踪
信号与依赖自动传播
信号是带有通知能力的变量,本质是封装了 getter/setter 的可追踪值。当信号变化时,所有依赖它的函数会自动重新运行。这种方式将依赖追踪自动化,副作用函数无需手动管理,适合轻量级的状态管理和 UI 层。
跨进程响应式
在分布式环境中,状态隔离、跨网络通知和一致性延迟成为核心挑战。基本模式包括发布/订阅(Pub/Sub)、分布式状态同步(CRDT/OT)和响应式 API 层。微服务之间的响应式通信通过事件总线或流式接口实现,类似前端信号机制,但用跨进程通信代替内存调用。
响应式配置中心
配置中心作为信号源,每个配置项都是可追踪的信号。服务订阅自己关心的配置信号,当信号变化时自动触发更新逻辑。配置中心主动推送而非服务轮询,实现实时性、解耦和集中管理。
ADP (无环依赖原则): 组件依赖图中不应出现环路。如果 A 依赖 B,B 依赖 C,C 绝对不能依赖 A。
SDP (稳定依赖原则): 依赖应该指向更稳定的方向。不稳定的组件(易变的)应该依赖稳定的组件(不易变的)。
SAP (稳定抽象原则): 一个组件越稳定,它就应该越抽象。