Osmotic Pressure
"In biology, osmotic pressure drives water from low-concentration regions to high-concentration regions through a semipermeable membrane. In software, complexity follows the same law — it flows toward where it's already concentrated, until the membrane tears."
What It Does
Every system has boundaries: between frontend and backend, between services, between modules, between your code and the framework. At each boundary, complexity must live somewhere. The question is: where does it accumulate, and when does the accumulation become unsustainable?
Osmotic Pressure maps the complexity gradient across every boundary in your system. It finds the places where one side has absorbed a disproportionate share of the system's complexity — making it fragile, hard to test, and expensive to change — while the other side coasts on artificial simplicity that exists only because the hard work was pushed across the membrane.
The Biology of Complexity
| Biological Concept | Software Equivalent |
|---|
| Cell | Module, service, or component |
| Cell Membrane |
API, interface, or boundary |
|
Solute | Complexity (logic, state, error handling, edge cases) |
|
Osmotic Pressure | The force driving complexity to accumulate on one side |
|
Lysis (cell bursting) | Module becomes unmaintainable, must be rewritten |
|
Crenation (cell shriveling) | Module becomes trivially thin, serves no purpose |
|
Isotonic | Complexity balanced appropriately across boundary |
The Five Pressure Patterns
1. The God Module (Hypertonic)
One module has absorbed the complexity of its entire neighborhood. Everything is "simple" because everything delegates to it.
CODEBLOCK0
Symptom: Every bug ticket, every new feature, every refactor touches the same module. Other modules are "clean" because they're empty of responsibility.
Pressure indicator: Module size × dependency count × bug frequency. When this exceeds neighbors by > 3x, pressure is critical.
2. The Distributed Monolith (Isotonic Failure)
Complexity is "evenly distributed" — but only because every module reimplements the same logic. The membrane between them allows no sharing, so each cell independently evolved the same complexity.
CODEBLOCK1
Symptom: Total system complexity is 3-5x what it should be. Fixing a bug means fixing it in N places. "We have microservices" but each service is a mini-monolith.
Pressure indicator: Code similarity > 60% across modules that should be independent.
3. The Thin Facade (Hypotonic)
A module's public interface is beautifully simple. Its internals are a nightmare. The complexity was pushed inward rather than addressed — a clean API hiding a disaster.
CODEBLOCK2
Symptom: The module is easy to use and impossible to maintain. New developers can call it on day one. No developer can modify it after a year.
Pressure indicator: Public surface area vs. internal cyclomatic complexity. Ratios > 1:10 indicate excessive inward pressure.
4. The Complexity Cascade (Pressure Chain)
Complexity flows downhill through a chain of modules. Each layer pushes its hard problems to the next, until the bottom module bears the accumulated weight of every decision above it.
CODEBLOCK3
Symptom: The bottom of the stack is fragile and critical. Changing the database schema requires understanding the entire application. The database is doing business logic.
Pressure indicator: Complexity increases monotonically down the call stack. The ratio of bottom-layer complexity to top-layer complexity exceeds 5:1.
5. The Boundary Void (Zero Pressure)
Two modules interact but have no defined boundary. Complexity flows freely in both directions. There's no membrane at all — just a zone of chaos where one module bleeds into the other.
CODEBLOCK4
Symptom: Nobody knows where A ends and B begins. Pull requests touch both "modules." Tests can't be scoped to one side.
Pressure indicator: Circular dependencies + shared mutable state + zero interface definition.
Measurement Framework
Complexity Metrics Per Module
| Metric | What It Measures |
|---|
| Lines of Logic | Executable lines, excluding declarations and imports |
| Cyclomatic Complexity |
Branching paths through the module |
|
State Surface | Number of mutable state variables managed |
|
Error Handling Density | Ratio of error/edge-case code to happy-path code |
|
Dependency Weight | Number and depth of dependencies consumed |
|
Export Surface | Number of things exposed to consumers |
|
Change Frequency | How often this module changes (git analysis) |
|
Bug Density | Bugs-per-line (from commit message analysis) |
Pressure Calculation
CODEBLOCK5
Output Format
CODEBLOCK6
When to Invoke
- - During architecture design (set healthy pressure budgets before building)
- After rapid feature development (complexity accumulates under speed pressure)
- When one module seems to attract all the bugs
- When a "simple" module is impossible to test
- Before a rewrite (understand where the pressure is before redistributing)
Why It Matters
Complexity is conserved. You can't eliminate it — only distribute it. The question isn't "how do we reduce complexity?" but "where should complexity live, and is it living there?"
Systems don't fail because they're complex. They fail because the complexity is concentrated where it shouldn't be — behind thin facades, inside god modules, at the bottom of cascading chains. Osmotic Pressure makes the invisible distribution visible.
Zero external dependencies. Zero API calls. Pure complexity analysis.
渗透压
在生物学中,渗透压驱动水分子通过半透膜从低浓度区域流向高浓度区域。在软件中,复杂性遵循同样的法则——它总是流向已经集中的地方,直到膜破裂。
它的作用
每个系统都有边界:前端与后端之间、服务之间、模块之间、你的代码与框架之间。在每个边界上,复杂性必须存在于某处。问题是:它在哪里积累,以及这种积累何时变得不可持续?
渗透压绘制了系统中每个边界上的复杂性梯度。它找出了一方吸收了系统不成比例复杂性的地方——使其变得脆弱、难以测试、修改成本高昂——而另一方则享受着人为的简单性,这种简单性之所以存在,只是因为困难的工作被推到了膜的另一边。
复杂性的生物学原理
API、接口或边界 |
|
溶质 | 复杂性(逻辑、状态、错误处理、边界情况) |
|
渗透压 | 驱动复杂性在某一侧积累的力 |
|
裂解(细胞破裂) | 模块变得不可维护,必须重写 |
|
皱缩(细胞萎缩) | 模块变得过于单薄,毫无意义 |
|
等渗 | 复杂性在边界两侧适当平衡 |
五种压力模式
1. 上帝模块(高渗)
一个模块吸收了其整个邻域的复杂性。其他一切都很简单,因为一切都委托给它。
┌──────────────────┐ ┌──────┐
│ API 网关 │────▶│ 认证 │ 简单
│ 1,847 行 │ └──────┘
│ 43 个函数 │ ┌──────┐
│ 12 个依赖 │────▶│ 日志 │ 简单
│ 89% 的缺陷 │ └──────┘
│ │ ┌──────┐
│ 所有复杂性 │────▶│ 数据库│ 简单
└──────────────────┘ └──────┘
▲
渗透压在这里
症状: 每个缺陷单、每个新功能、每次重构都触及同一个模块。其他模块很干净,因为它们没有任何职责。
压力指标: 模块大小 × 依赖数量 × 缺陷频率。当此值超过邻居的 3 倍时,压力达到临界。
2. 分布式单体(等渗失效)
复杂性均匀分布——但这只是因为每个模块都重新实现了相同的逻辑。它们之间的膜不允许共享,因此每个细胞独立演化出了相同的复杂性。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 服务 A │ │ 服务 B │ │ 服务 C │
│ 验证 │ │ 验证 │ │ 验证 │
│ 格式化 │ │ 格式化 │ │ 格式化 │
│ 重试 │ │ 重试 │ │ 重试 │
│ 缓存 │ │ 缓存 │ │ 缓存 │
└──────────┘ └──────────┘ └──────────┘
相同复杂性 相同复杂性 相同复杂性
症状: 系统总复杂性是应有的 3-5 倍。修复一个缺陷意味着在 N 个地方修复。我们有微服务,但每个服务都是一个迷你单体。
压力指标: 本应独立的模块间代码相似度 > 60%。
3. 薄门面(低渗)
模块的公共接口非常简洁。其内部却是一场噩梦。复杂性被向内推压而非解决——一个干净的 API 隐藏着一场灾难。
公共接口: 实现:
┌─────────────────┐ ┌─────────────────────────────┐
│ createUser() │──────▶│ 17 个条件分支 │
│ getUser() │ │ 4 个重试循环 │
│ deleteUser() │ │ 8 个隐式状态转换 │
│ │ │ 3 个未记录的副作用 │
│ 干净、简单。 │ │ 2 个竞态条件 │
│ 3 个函数。 │ │ 1 个祈祷 │
└─────────────────┘ └─────────────────────────────┘
症状: 模块易于使用却难以维护。新开发者第一天就能调用它。一年后没有开发者能修改它。
压力指标: 公共表面积与内部圈复杂度的比值。比值 > 1:10 表明内部压力过大。
4. 复杂性级联(压力链)
复杂性通过模块链向下流动。每一层都将其难题推给下一层,直到最底层的模块承受了其上方所有决策的累积重量。
前端 → 让 API 处理这个
API 层 → 让服务处理这个
服务 → 让数据库处理这个
数据库 → [复杂的存储过程、触发器、视图、物化聚合,做着本应是应用逻辑的工作]
症状: 栈底脆弱且关键。更改数据库模式需要理解整个应用程序。数据库正在执行业务逻辑。
压力指标: 复杂性沿调用栈单调递增。底层复杂性对顶层复杂性的比值超过 5:1。
5. 边界真空(零压力)
两个模块交互但没有定义的边界。复杂性在双向自由流动。根本没有膜——只是一个混乱的区域,一个模块渗入另一个模块。
┌─────────────────────────────────────┐
│ 模块 A ... ??? ... 模块 B │
│ A 导入 B,B 导入 A │
│ 共享状态,共享类型 │
│ 没有清晰的接口 │
│ 没有清晰的归属 │
│ 熵:最大 │
└─────────────────────────────────────┘
症状: 没人知道 A 在哪里结束,B 从哪里开始。拉取请求同时触及两个模块。测试无法限定在某一侧。
压力指标: 循环依赖 + 共享可变状态 + 零接口定义。
测量框架
每个模块的复杂性指标
| 指标 | 测量内容 |
|---|
| 逻辑行数 | 可执行行数,排除声明和导入 |
| 圈复杂度 |
通过模块的分支路径数 |
|
状态表面积 | 管理的可变状态变量数量 |
|
错误处理密度 | 错误/边界情况代码与正常路径代码的比率 |
|
依赖权重 | 消耗的依赖数量及深度 |
|
导出表面积 | 暴露给消费者的内容数量 |
|
变更频率 | 该模块变更的频率(git 分析) |
|
缺陷密度 | 每行缺陷数(来自提交消息分析) |
压力计算
对于模块 A 和模块 B 之间的每个边界:
复杂性(A) = A 所有指标的加权和
复杂性(B) = B 所有指标的加权和
压力 = |复杂性(A) - 复杂性(B)| / max(复杂性(A), 复杂性(B))
0.0 - 0.2 → 等渗(健康平衡)
0.2 - 0.4 → 轻度压力(监控)
0.4 - 0.6 → 中度压力(建议重新平衡)
0.6 - 0.8 → 高压(需要重新平衡)
0.8 - 1.0 → 临界(即将裂解)
输出格式
╔══════════════════════════════════════════════════════════════╗
║ 渗透压分析 ║
║ 系统平衡评分:0.58(压力状态) ║
╠══════════════════════════════════════════════════════════════╣
║ ║
║ 压力图: ║
║ ║
║ 前端 ──[0.31]──▶ API ──[0.72]──▶ 服务 ──[0.44]──▶ 数据库 ║
║ (低) (高) (中等) ║
║ ║
║ 关键失衡: ║
║ ┌──────────────────────────────────────────────┐ ║
║ │ API 层 → 服务层 │ ║
║ │ 压力:0.72(高——接近裂解) │ ║
║ │ 模式:上帝模块(服务层) │ ║
║ │ │ ║
║ │ API 层: 142 逻辑行,圈复杂度 8,3 个状态变量 │ ║
║ │ 服务层: 2,847 逻辑行,圈复杂度 67