编程中的弗兰肯斯坦——它是什么、原因与预防

作者: IT Sectr 发布日期: 2026-07-26 阅读时间: 10 分钟

编程中的弗兰肯斯坦是指由不同技术、风格和架构中互不兼容的部分拼凑而成的代码。根据ThoughtWorks Technology Radar(2024)的研究,28%的大型项目带有弗兰肯斯坦综合症的迹象——即在缺乏统一技术愿景时出现的架构折衷主义。类比玛丽·雪莱的小说,这样的代码能够运行,但其维护却会变成一场噩梦。

要点

  • 弗兰肯斯坦——一种反模式,系统由各种不兼容的组件拼凑而成
  • 主要原因:缺少架构师、项目合并、“无界限的创造力”
  • 问题——每个组件都需要了解自己的技术,而交互却不可预测
  • 重构弗兰肯斯坦需要对技术栈进行统一,并划清明确的边界
  • Architecture Decision Records 和 RFC 是预防的最佳工具

编程中的弗兰肯斯坦是什么

弗兰肯斯坦(Frankenstein code、Frankenstein pattern)是一种反模式,指软件系统由本不打算协同工作的部分拼装而成。就像弗兰肯斯坦的怪物一样,这样的代码可以运转,但它丑陋、不可预测,哪怕最微小的改动也会带来危险。

这个词来自文学:在玛丽·雪莱的小说《弗兰肯斯坦,或现代的普罗米修斯》(1818年)中,科学家用不同逝者的身体碎片创造了一个活物。在编程中,这种类比非常贴切——开发者从不同的框架、库、语言中取来片段,用“活线”将它们缝合在一起,得到的是能运行却骇人的结果。

弗兰肯斯坦与意大利面代码的区别在于问题的规模和性质。意大利面代码是单一技术栈内部的混乱结构。弗兰肯斯坦是架构层面的折衷主义:不同的技术、不兼容的范式、同一系统内部相互冲突的方法。

弗兰肯斯坦 vs 微服务

微服务架构允许不同的服务使用不同的技术,但前提是有清晰的边界和标准化的交互协议。弗兰肯斯坦则是没有边界的混乱混合:同一个控制器里既有 REST 又有 GraphQL,同一个模块里有两套 ORM,同一个实体既用 SQL 又用 NoSQL。

为什么会出现弗兰肯斯坦综合症

缺少技术负责人或架构师是根本原因。当项目中没有人为架构的完整性负责时,每个开发者都会选择“适合自己的”工具。有人喜欢 Spring,有人喜欢 Guice,还有人用自己写的 DI。结果就是架构大杂烩。

项目合并是第二个常见原因。两个团队各自独立开发自己的模块,使用不同的技术栈。当需要把这些模块合并到同一个应用时,它们只是用适配器和中间层“粘”在一起,就变成了弗兰肯斯坦。

企业并购是第三种情形。A 公司收购了 B 公司,想把它的产品整合进自己的体系。与其重写,不如通过 API、共享数据库和各种补丁拼接。一年后,系统就变成了无人理解的怪兽。

原因描述典型结果
没有架构师每个开发者选择自己的技术栈一个模块里有3个不同的 HTTP 客户端
项目合并两个产品拼成一个两套 ORM、两种日志记录方式
M&A并购公司及其产品不同架构和风格的混合体
试验无策略地引入新技术一个文件里同时有 Java 8 和 Java 21 的特性
政治决策从上而下强加技术而不考虑背景为简单脚本使用企业级框架

创造力因素

有经验的开发者想在生产环境中尝试新技术,往往成为弗兰肯斯坦的源头。他们没有把试验限制在隔离的模块中,而是把试验性代码引入系统的关键部分。

真实项目中的弗兰肯斯坦实例

经典例子是在一个应用中使用多套 ORM。部分模块用 Hibernate,部分用 MyBatis,还有部分直接写 JDBC 查询。事务变得难以管理,缓存变得不一致,新开发者也不知道新功能该用哪种方式。

第二个例子是混合架构风格。REST API 的控制器里出现了 SOAP 服务调用、直接 SQL 查询、文件系统访问和 HTML 生成。这样的应用无法测试、无法扩展,也无法编写文档。

第三个例子是技术栈:后端用 Python,微服务用 Node.js,桌面客户端用 C#,Android 应用用 Java,而所有业务逻辑都被摊薄在它们之间,没有清晰的责任划分。

javascript
// 弗兰肯斯坦——混合的风格和技术
// callbacks、Promises 和 async/await 混用

// callbacks
db.query("SELECT * FROM users", function(err, rows) {
  if (err) handleError(err);
  // 回调里的 Promise
  fetch("/api/data").then(function(data) {
    // then 里的 async/await
    (async () => {
      const result = await processData(data);
      sendResponse(result);
    })();
  });
});

// 干净的代码——统一的 async/await 风格
async function getUserData(userId) {
  const user = await db.query("SELECT * FROM users WHERE id = ?", [userId]);
  const data = await fetch("/api/data/" + userId);
  return await processData(user, data);
}

数据层面的弗兰肯斯坦

同一个数据库同时被当作 SQL 关系型(带规范化)和 NoSQL 文档型(带 JSON 列)来使用。部分查询走 ORM,部分走存储过程,部分从代码里直接写 SQL。数据库 schema 没有文档,迁移相互冲突。

弗兰肯斯坦代码的后果

入职难度是第一个后果。新开发者必须掌握 5 门语言、3 个框架、2 种架构风格,才能理解系统是如何运行的。入职时间从几周拖到几个月。根据 LinkedIn(2023)的数据,技术折衷主义的项目流失新员工的概率高出 2 倍。

行为不可预测是第二个后果。对 Python 微服务的改动可能会意外弄坏 Java 模块,因为它们共享同一个数据库却没有明确的契约。调试这类问题需要同时掌握技术栈中的全部技术。

安全性是第三个后果。技术栈中的每项技术都需要自己的安全配置、自己的补丁、自己的监控。要在可接受的水平上维护 5–6 项异构技术几乎不可能,其中一项必然存在漏洞。

弗兰肯斯坦的技术债

SonarQube 可以衡量技术债,却无法衡量“架构债”——即组件之间的不兼容。这种债务并不体现在 lint 工具的警告里,而是体现在:不修改三个用不同技术写的模块,就无法添加一个新功能。

如何避免制造怪兽

第一也是最重要的一步:指定一位负责技术栈完整性的架构师或 tech lead。这个人对未经架构评审的新技术拥有一票否决权。不是民主,而是对关键技术做出负责任的最终决定。

第二步:推行 Architecture Decision Record(ADR)流程。任何重要的架构决策(选择数据库、框架、协议)都以简短文本记录下来:背景、考虑过的备选方案、最终决定、影响。ADR 保存在仓库中,向整个团队开放。

第三步:确立“一个任务——一种工具”的原则。HTTP 请求用同一个客户端,ORM 用同一个库,日志记录用同一个框架。例外只能通过有充分理由的 ADR 批准。如果项目里已经有 Axios,就不要再加 fetch;如果有 SLF4J,就不要用 System.out 输出。

  • 架构师拥有对新技术的一票否决权
  • Architecture Decision Records 记录每一个重要选择
  • 每个任务使用统一技术栈——一个 HTTP 客户端、一套 ORM
  • RFC 用于重大变更,由全团队讨论
  • 技术雷达用于跟踪可以引入的新技术

试验性技术的政策

试验是允许的,但要在隔离的环境中。划出一个模块或服务,可以在不影响系统其他部分的情况下用新技术重写。试验成功,就通过 ADR 把它标准化;失败,就无痛删除。

如何重构现有的弗兰肯斯坦

盘点是第一步。画出完整的技术栈地图:用了哪些框架、库、语言、协议,在哪些模块、为哪些任务使用。你会看到问题的规模:工具重复、技术冲突、无用的依赖。

标准化是第二步。为每类任务选一种工具。例如:ORM 只用 Hibernate,日志只用 SLF4J + Logback,API 只用 REST。把标准写进 ADR。从折衷主义问题最严重的模块开始替换。

Parallel Run(并行运行)策略是第三步。旧工具和新工具并行工作,直到新工具证明自己的可靠性。例如,旧 HTTP 客户端和新客户端同时运行,但新的只处理一部分请求。经过稳定期后删除旧客户端。

java
// 弗兰肯斯坦——一个项目里三种 HTTP 方式
// 模块 A:OkHttp
OkHttpClient client = new OkHttpClient().newCall(request);

// 模块 B:RestTemplate (Spring)
restTemplate.getForObject(url, String.class);

// 模块 C:java.net.HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();

// 统一方案:同步用 RestTemplate,响应式用 WebClient
@Autowired
private RestTemplate restTemplate;

public String callApi(String url) {
    return restTemplate.getForObject(url, String.class);
}

技术负责人在预防中的作用

技术负责人是对抗弗兰肯斯坦的主要工具。不是经理,不是象牙塔里的架构师,而是真正写代码、评审 PR、做架构决策的一线开发者。没有这样的人,项目不可避免地滑向技术折衷主义。

RFC(Request for Comments)是借鉴自开源社区的过程。在引入任何重要技术之前,作者先写 RFC:问题、提议的解决方案、备选方案、实施计划。团队讨论、投票、通过或否决。RFC 带来透明度,避免“悄悄”做出架构决策。

技术雷达(ThoughtWorks 的 Technology Radar)是技术分类工具:Adopt、Trial、Assess、Hold。团队定期复查雷达并更新状态。这有助于区分“时髦”与“有用”,避免把未经检验的技术引入关键代码。

一致性原则

架构最重要的品质是一致性(consistency)。即便不是最好的工具,只要在整个项目中使用,也比只在单个模块中使用的最佳工具更好。一致性降低认知负担,简化入职,让代码可预测。

常见问题

弗兰肯斯坦与使用 polyglot persistence 有什么不同?

Polyglot persistence 是刻意地为不同任务使用不同的数据库(PostgreSQL 处理事务、Redis 做缓存、Elasticsearch 做搜索)。弗兰肯斯坦是没有策略的混乱混合。区别在于是否存在架构决策:polyglot 是计划,弗兰肯斯坦是计划的缺失。

微服务架构会变成弗兰肯斯坦吗?

,而且这是常见问题。当每个微服务都使用自己的语言、自己的数据库、自己的协议、自己的部署方式,又没有统一标准时,就出现了分布式弗兰肯斯坦。微服务需要共同的标准:统一的协议(REST/gRPC)、统一的日志格式、集中式的可观测性(observability)。

如何说服团队不要使用新技术?

不要禁止,而要引导。请作者写 RFC:说明为什么现有方案不合适、考虑了哪些备选方案、如何迁移。往往在写 RFC 的过程中,开发者自己就会明白新技术并不必要。如果 RFC 有说服力,就采纳,但要带着计划和限制。

如何在遗留项目中对抗弗兰肯斯坦?

盘点,再标准化。不要试图一次性重写所有东西。划出一层(例如 HTTP 客户端或日志),选择统一工具,写 ADR,然后逐步迁移。Strangler Fig 方法——在不停止应用运行的前提下,把旧组件逐个替换为新组件。

一个项目用多少技术最合适?

越少越好。理想情况是一门语言、一个框架、一个数据库、一种日志方式。现实情况是 2–3 门语言(要有清晰划分)、1–2 个数据库、1–2 个框架。每多一项技术都会增加团队的认知负担和维护成本。

总结

  • 弗兰肯斯坦——系统由各种不兼容组件拼成的反模式
  • 主要原因:缺少架构师、项目合并、失控的试验
  • 后果——入职困难、行为不可预测、安全问题
  • ADR 和 RFC 是防止架构折衷主义的关键流程
  • “一个任务一种工具”原则是预防的基础
  • 重构从盘点和技术栈标准化开始
  • 架构的一致性比单个子任务的“最佳工具”更重要

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读