Frankenstein trong lập trình — định nghĩa, nguyên nhân và phòng ngừa

Tác giả: IT Sectr Đã đăng: 2026-07-26 Thời gian đọc: 10 phút

Frankenstein trong lập trình là mã được tập hợp từ các phần không tương thích của các công nghệ, phong cách và kiến trúc khác nhau. Theo nghiên cứu ThoughtWorks Technology Radar (2024), 28% các dự án lớn có dấu hiệu của hội chứng Frankenstein — chủ nghĩa chiết trung kiến trúc phát sinh do thiếu tầm nhìn kỹ thuật thống nhất. Tương tự như tiểu thuyết của Mary Shelley, mã như vậy có thể hoạt động, nhưng việc bảo trì nó trở thành một cơn ác mộng.

Những điểm chính

  • Frankenstein là một phản mẫu trong đó hệ thống được tập hợp từ các thành phần không đồng nhất, kém tương thích
  • Nguyên nhân chính: thiếu kiến trúc sư, sáp nhập dự án, “sáng tạo không giới hạn”
  • Vấn đề — mỗi thành phần yêu cầu kiến thức về công nghệ riêng và tương tác không thể đoán trước
  • Tái cấu trúc Frankenstein đòi hỏi hợp nhất ngăn xếp công nghệ và thiết lập ranh giới rõ ràng
  • Architecture Decision Records và RFC là những công cụ phòng ngừa tốt nhất

Frankenstein trong lập trình là gì

Frankenstein (mã Frankenstein, mẫu Frankenstein) là một phản mẫu trong đó một hệ thống phần mềm được tập hợp từ các phần không được thiết kế để hoạt động cùng nhau. Giống như quái vật của Frankenstein, mã như vậy có thể hoạt động, nhưng nó xấu xí, không thể đoán trước và nguy hiểm khi có thay đổi nhỏ nhất.

Thuật ngữ này đến từ văn học: trong tiểu thuyết của Mary Shelley “Frankenstein, hay Prometheus Hiện đại” (1818), một nhà khoa học đã tạo ra một sinh vật sống từ các mảnh cơ thể của những người chết khác nhau. Trong lập trình, sự tương tự chính xác — các nhà phát triển lấy các mảnh của các framework, thư viện, ngôn ngữ khác nhau và dán chúng lại “sống”, thu được kết quả hoạt động nhưng quái dị.

Sự khác biệt giữa Frankenstein và mã spaghetti nằm ở quy mô và bản chất. Mã spaghetti là một cấu trúc rối rắm trong một ngăn xếp công nghệ duy nhất. Frankenstein là chủ nghĩa chiết trung ở cấp độ kiến trúc: các công nghệ khác nhau, các mô hình không tương thích, các cách tiếp cận xung đột trong cùng một hệ thống.

Frankenstein vs Microservices

Kiến trúc microservice cho phép sử dụng các công nghệ khác nhau cho các dịch vụ khác nhau, nhưng với điều kiện ranh giới rõ ràng và các giao thức tương tác được tiêu chuẩn hóa. Frankenstein là sự pha trộn hỗn loạn không có ranh giới: REST và GraphQL trong cùng một bộ điều khiển, hai ORM trong cùng một mô-đun, SQL và NoSQL cho cùng một thực thể.

Tại sao hội chứng Frankenstein xuất hiện

Sự thiếu vắng lãnh đạo kỹ thuật hoặc kiến trúc sư là nguyên nhân gốc rễ. Khi không có người chịu trách nhiệm về tính toàn vẹn kiến trúc, mỗi nhà phát triển chọn công cụ “cho riêng mình”. Một người thích Spring, người khác thích Guice, người thứ ba sử dụng DI tùy chỉnh. Kết quả là một mớ hỗn độn kiến trúc.

Sáp nhập dự án là nguyên nhân phổ biến thứ hai. Hai nhóm đã phát triển các mô-đun của họ một cách độc lập, sử dụng các ngăn xếp khác nhau. Khi các mô-đun cần được kết hợp thành một ứng dụng duy nhất, chúng chỉ đơn giản là được “dán” với nhau bằng các bộ điều hợp và lớp trung gian. Kết quả là Frankenstein.

Mua lại doanh nghiệp là kịch bản thứ ba. Công ty A mua công ty B và muốn tích hợp sản phẩm của nó vào sản phẩm của mình. Thay vì viết lại — dán thông qua API, cơ sở dữ liệu dùng chung và các bản vá. Sau một năm, hệ thống trở thành một con quái vật mà không ai hiểu.

Nguyên nhânMô tảKết quả điển hình
Không có kiến trúc sưMỗi nhà phát triển chọn ngăn xếp riêng3 HTTP client khác nhau trong một mô-đun
Sáp nhập dự ánHai sản phẩm dán thành mộtHai ORM, hai cách ghi log
M&AMua lại công ty với sản phẩm của nóLai ghép các kiến trúc và phong cách khác nhau
Thử nghiệmGiới thiệu công nghệ mới không có chiến lượcTính năng Java 8 + Java 21 trong một tệp
Quyết định chính trịÁp đặt công nghệ từ trên xuống không có ngữ cảnhFramework doanh nghiệp cho một script đơn giản

Yếu tố “sáng tạo”

Các nhà phát triển giàu kinh nghiệm muốn thử công nghệ mới trong sản xuất thường trở thành nguồn gốc của Frankenstein. Thay vì giới hạn thử nghiệm trong một mô-đun cách ly, họ đưa mã thử nghiệm vào các phần quan trọng của hệ thống.

Ví dụ về Frankenstein trong các dự án thực tế

Một ví dụ kinh điển là sử dụng nhiều ORM trong một ứng dụng. Một số mô-đun sử dụng Hibernate, một số sử dụng MyBatis, và một số sử dụng truy vấn JDBC trực tiếp. Các giao dịch trở nên không thể quản lý, bộ nhớ đệm không nhất quán, và nhà phát triển mới không biết nên chọn cách tiếp cận nào cho tính năng mới.

Ví dụ thứ hai là sự pha trộn các phong cách kiến trúc. Trong một bộ điều khiển REST API, bạn tìm thấy các cuộc gọi dịch vụ SOAP, truy vấn SQL trực tiếp, truy cập hệ thống tệp và tạo HTML. Một ứng dụng như vậy không thể kiểm tra, mở rộng hoặc tài liệu hóa.

Ví dụ thứ ba là một ngăn xếp công nghệ nơi Python được sử dụng cho backend, Node.js cho microservice, C# cho client máy tính để bàn và Java cho ứng dụng Android, trong khi tất cả logic kinh doanh bị phân tán giữa chúng mà không có sự phân chia trách nhiệm rõ ràng.

javascript
// Frankenstein — pha trộn phong cách và công nghệ
// callbacks, Promises và async/await kết hợp

// callbacks
db.query("SELECT * FROM users", function(err, rows) {
  if (err) handleError(err);
  // Promise bên trong callback
  fetch("/api/data").then(function(data) {
    // async/await bên trong then
    (async () => {
      const result = await processData(data);
      sendResponse(result);
    })();
  });
});

// mã sạch — phong cách async/await thống nhất
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);
}

Frankenstein ở cấp độ dữ liệu

Một cơ sở dữ liệu được sử dụng đồng thời như SQL quan hệ (với chuẩn hóa) và NoSQL hướng tài liệu (với các cột JSON). Một số truy vấn đi qua ORM, một số qua thủ tục lưu trữ, một số qua SQL trực tiếp từ mã. Lược đồ DB không được tài liệu hóa, di chuyển xung đột.

Hậu quả của mã Frankenstein

Sự phức tạp của việc onboarding là hậu quả đầu tiên. Một nhà phát triển mới phải biết 5 ngôn ngữ, 3 framework, 2 phong cách kiến trúc để hiểu cách hệ thống hoạt động. Onboarding kéo dài từ vài tuần đến vài tháng. Theo LinkedIn (2023), các dự án có chủ nghĩa chiết trung công nghệ mất nhân viên mới thường xuyên gấp 2 lần.

Sự không thể đoán trước của hành vi là hậu quả thứ hai. Một thay đổi trong microservice Python có thể bất ngờ phá vỡ mô-đun Java vì chúng chia sẻ cơ sở dữ liệu mà không có hợp đồng rõ ràng. Gỡ lỗi các vấn đề như vậy đòi hỏi kiến thức đồng thời về tất cả các công nghệ trong ngăn xếp.

Bảo mật là hậu quả thứ ba. Mỗi công nghệ trong ngăn xếp yêu cầu cấu hình bảo mật riêng, bản vá riêng, giám sát riêng. Duy trì bảo mật ở mức chấp nhận được cho 5-6 công nghệ không đồng nhất là thực tế không thể. Một trong số chúng chắc chắn sẽ dễ bị tổn thương.

Nợ kỹ thuật của Frankenstein

SonarQube có thể đo nợ kỹ thuật, nhưng không thể đo “nợ kiến trúc” — sự không tương thích của các thành phần. Khoản nợ này không xuất hiện trong cảnh báo của linter, mà ở sự bất khả thi của việc thêm tính năng mới mà không sửa đổi ba mô-đun khác nhau được viết bằng các công nghệ khác nhau.

Cách tránh tạo ra quái vật

Bước đầu tiên và chính — bổ nhiệm một kiến trúc sư hoặc lãnh đạo kỹ thuật chịu trách nhiệm về tính toàn vẹn của ngăn xếp công nghệ. Người này có quyền phủ quyết đối với việc giới thiệu công nghệ mới mà không có đánh giá kiến trúc. Không phải dân chủ, mà là quyết định cá nhân có trách nhiệm về các công nghệ chính.

Bước thứ hai — triển khai quy trình Architecture Decision Record (ADR). Bất kỳ quyết định kiến trúc quan trọng nào (lựa chọn DB, framework, giao thức) đều được tài liệu hóa dưới dạng văn bản ngắn: bối cảnh, các lựa chọn thay thế đã xem xét, quyết định được đưa ra, hậu quả. ADR được lưu trữ trong kho lưu trữ và có sẵn cho toàn bộ nhóm.

Bước thứ ba — thiết lập nguyên tắc “một nhiệm vụ — một công cụ”. Cho các yêu cầu HTTP — một client. Cho ORM — một thư viện. Cho ghi log — một framework. Các ngoại lệ chỉ được phép thông qua ADR với sự biện minh. Nếu dự án đã có Axios — đừng thêm fetch, nếu có SLF4J — đừng viết qua System.out.

  • Kiến trúc sư với quyền phủ quyết đối với công nghệ mới
  • Architecture Decision Records cho mỗi lựa chọn quan trọng
  • Ngăn xếp thống nhất cho mỗi nhiệm vụ — một HTTP client, một ORM
  • RFC cho các thay đổi lớn với thảo luận toàn nhóm
  • Radar công nghệ để theo dõi những gì có thể áp dụng

Chính sách công nghệ thử nghiệm

Các thử nghiệm được phép, nhưng trong môi trường cách ly. Phân bổ một mô-đun hoặc dịch vụ có thể được viết lại bằng công nghệ mới mà không ảnh hưởng đến phần còn lại của hệ thống. Nếu thử nghiệm thành công — tiêu chuẩn hóa nó qua ADR. Nếu không — loại bỏ nó mà không có hậu quả.

Cách tái cấu trúc Frankenstein hiện có

Kiểm kê — bước đầu tiên. Tạo một bản đồ hoàn chỉnh của ngăn xếp công nghệ: framework, thư viện, ngôn ngữ, giao thức nào được sử dụng, trong mô-đun nào và cho nhiệm vụ nào. Bạn sẽ thấy quy mô của vấn đề: trùng lặp công cụ, công nghệ xung đột, phụ thuộc không sử dụng.

Tiêu chuẩn hóa — bước thứ hai. Chọn một công cụ cho mỗi nhiệm vụ. Ví dụ: chỉ Hibernate cho ORM, chỉ SLF4J + Logback cho ghi log, chỉ REST cho API. Tài liệu hóa tiêu chuẩn trong ADR. Bắt đầu thay thế từ các mô-đun nơi chủ nghĩa chiết trung gây ra nhiều vấn đề nhất.

Chiến lược Parallel Run — bước thứ ba. Công cụ cũ và mới hoạt động song song cho đến khi công cụ mới chứng minh được độ tin cậy của nó. Ví dụ, HTTP client cũ và mới hoạt động đồng thời, nhưng cái mới chỉ xử lý một phần yêu cầu. Sau thời gian ổn định, cái cũ bị loại bỏ.

java
// Frankenstein — ba cách tiếp cận HTTP trong một dự án
// Mô-đun A: OkHttp
OkHttpClient client = new OkHttpClient().newCall(request);

// Mô-đun B: RestTemplate (Spring)
restTemplate.getForObject(url, String.class);

// Mô-đun C: java.net.HttpURLConnection
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();

// cách tiếp cận thống nhất: RestTemplate cho đồng bộ, WebClient cho phản ứng
@Autowired
private RestTemplate restTemplate;

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

Vai trò của lãnh đạo kỹ thuật trong phòng ngừa

Lãnh đạo kỹ thuật là công cụ chính để chống lại Frankenstein. Không phải quản lý, không phải kiến trúc sư trong tháp ngà, mà là một nhà phát triển thực hành viết mã, xem xét PR và đưa ra quyết định kiến trúc. Không có người như vậy, dự án chắc chắn sẽ trượt vào chủ nghĩa chiết trung công nghệ.

RFC (Request for Comments) là một quy trình vay mượn từ cộng đồng mã nguồn mở. Trước khi giới thiệu bất kỳ công nghệ quan trọng nào, tác giả viết RFC: vấn đề, giải pháp đề xuất, các lựa chọn thay thế, kế hoạch triển khai. Nhóm thảo luận, bỏ phiếu, chấp nhận hoặc từ chối. RFC tạo ra sự minh bạch và ngăn chặn các quyết định kiến trúc “im lặng”.

Radar công nghệ (ThoughtWorks Technology Radar) là một công cụ phân loại: Adopt, Trial, Assess, Hold. Nhóm thường xuyên xem xét radar và cập nhật trạng thái. Điều này giúp phân biệt “xu hướng” với “hữu ích” và tránh đưa công nghệ chưa được kiểm chứng vào mã quan trọng.

Nguyên tắc nhất quán

Phẩm chất quan trọng nhất của kiến trúc là sự nhất quán. Ngay cả một công cụ không tốt lắm được sử dụng trong toàn bộ dự án cũng tốt hơn công cụ tốt nhất chỉ được sử dụng trong một mô-đun. Sự nhất quán giảm tải nhận thức, đơn giản hóa việc onboarding và làm cho mã có thể dự đoán được.

Câu hỏi thường gặp

Frankenstein khác với việc sử dụng polyglot persistence như thế nào?

Polyglot persistence là việc sử dụng có ý thức các cơ sở dữ liệu khác nhau cho các nhiệm vụ khác nhau (PostgreSQL cho giao dịch, Redis cho bộ nhớ đệm, Elasticsearch cho tìm kiếm). Frankenstein là sự pha trộn hỗn loạn không có chiến lược. Sự khác biệt nằm ở sự hiện diện của một quyết định kiến trúc: polyglot là một kế hoạch, Frankenstein là sự vắng mặt của nó.

Kiến trúc microservice có thể biến thành Frankenstein không?

, và đây là một vấn đề phổ biến. Khi mỗi microservice sử dụng ngôn ngữ riêng, DB riêng, giao thức riêng và cách tiếp cận triển khai riêng mà không có tiêu chuẩn tập trung — bạn có một Frankenstein phân tán. Đối với microservice, các tiêu chuẩn chung rất quan trọng: giao thức thống nhất (REST/gRPC), định dạng log chung, khả năng quan sát tập trung.

Làm thế nào để thuyết phục nhóm không sử dụng công nghệ mới?

Đừng cấm — hãy hướng dẫn. Đề nghị tác giả viết RFC: mô tả tại sao giải pháp hiện tại không phù hợp, những lựa chọn thay thế nào đã được xem xét, việc di chuyển sẽ được thực hiện như thế nào. Thường trong quá trình viết RFC, chính nhà phát triển nhận ra rằng công nghệ mới không cần thiết. Nếu RFC thuyết phục — hãy triển khai, nhưng với kế hoạch và giới hạn.

Làm thế nào để đối phó với Frankenstein trong dự án kế thừa?

Đầu tiên kiểm kê, sau đó tiêu chuẩn hóa. Đừng cố viết lại mọi thứ cùng một lúc. Chọn một lớp (ví dụ: HTTP client hoặc ghi log), chọn một công cụ duy nhất, viết ADR và di chuyển dần dần. Mô hình Strangler Fig — thay thế các thành phần cũ bằng các thành phần mới từng cái một mà không dừng ứng dụng.

Bao nhiêu công nghệ là tối ưu cho một dự án?

Càng ít càng tốt. Lý tưởng — một ngôn ngữ, một framework, một DB, một phương pháp ghi log. Thực tế — 2-3 ngôn ngữ (với phân chia rõ ràng), 1-2 DB, 1-2 framework. Mỗi công nghệ bổ sung làm tăng tải nhận thức của nhóm và chi phí bảo trì.

Tóm tắt

  • Frankenstein là một phản mẫu trong đó hệ thống được tập hợp từ các thành phần không đồng nhất không tương thích
  • Nguyên nhân chính: thiếu kiến trúc sư, sáp nhập dự án, thử nghiệm không kiểm soát
  • Hậu quả — onboarding phức tạp, hành vi không thể đoán trước, vấn đề bảo mật
  • ADR và RFC là các quy trình chính để ngăn chặn chủ nghĩa chiết trung kiến trúc
  • Nguyên tắc “một công cụ cho mỗi nhiệm vụ” là nền tảng của phòng ngừa
  • Tái cấu trúc bắt đầu bằng kiểm kê và tiêu chuẩn hóa ngăn xếp công nghệ
  • Sự nhất quán của kiến trúc quan trọng hơn “công cụ tốt nhất” cho một nhiệm vụ phụ

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm