Legacy trong phát triển ứng dụng — khái niệm, rủi ro và chiến lược

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

Legacy — không chỉ là mã cũ. Nó là một hệ thống đang hoạt động, mang lại tiền cho doanh nghiệp nhưng làm chậm sự phát triển. Trong phát triển di động, legacy có thể được viết bằng Objective-C, sử dụng các thư viện lỗi thời hoặc các mẫu kiến trúc cũ. Theo báo cáo của CAST Software (2024), tuổi trung bình của một dòng mã trong các dự án enterprise vượt quá 14 năm. Chiến lược làm việc với legacy quyết định liệu nó trở thành rào cản hay vẫn là tài sản có thể quản lý được.

Các điểm chính

  • Legacy — mã chạy trong sản xuất nhưng sử dụng công nghệ hoặc cách tiếp cận lỗi thời
  • Bảo trì legacy đòi hỏi hiểu các quyết định lịch sử và tái cấu trúc cẩn thận
  • Chiến lược di chuyển — thay thế mô-đun dần dần mà không dừng sản phẩm qua Strangler Fig
  • Kiểm thử legacy — các characterization tests ghi lại hành vi hiện tại trước khi tái cấu trúc
  • Tuổi của mã tự nó không phải là vấn đề — vấn đề là thiếu kiểm thử và tầm nhìn kiến trúc

Legacy trong phát triển ứng dụng là gì

Legacy — mã hoặc hệ thống tiếp tục chạy trong sản xuất nhưng không còn đáp ứng các tiêu chuẩn chất lượng hiện đại. Legacy có thể được viết bằng ngôn ngữ lỗi thời (ví dụ, Objective-C thay vì Swift), sử dụng các thư viện không được hỗ trợ hoặc các mẫu kiến trúc từ lâu được coi là phản mẫu.

Đặc điểm chính của legacy là thiếu kiểm thử. Theo định nghĩa của Michael Feathers (2004), mã legacy là mã không có kiểm thử. Nếu không thể thay đổi hành vi một cách an toàn, hệ thống ở trạng thái legacy bất kể tuổi đời. Mã mới không có kiểm thử đơn vị là legacy từ ngày đầu tiên.

Legacy không nhất thiết là xấu. Một hệ thống được thiết kế tốt bằng Java 8 có thể đáng tin cậy và dễ hiểu hơn mã hỗn độn trong Kotlin với coroutines. Tuổi của mã không phải là chỉ số chất lượng — điều quan trọng là hệ thống có thể được thay đổi và mở rộng dễ dàng như thế nào.

Tại sao mã legacy là bình thường

Mọi hệ thống thành công đều trở thành legacy theo thời gian. Đây là một quá trình tự nhiên: công nghệ phát triển nhanh hơn mã có thể được viết lại. Một ứng dụng được viết 5 năm trước bằng Swift 2 là legacy ngày nay, mặc dù nó hiện đại tại thời điểm tạo ra.

Giá trị kinh doanh của legacy thường bị đánh giá thấp. Hệ thống hoạt động ổn định, xử lý giao dịch, lưu trữ dữ liệu — viết lại mang rủi ro. Theo Standish Group (2024), 35% dự án viết lại toàn bộ kết thúc thất bại. Về mặt kinh tế, không phải loại bỏ legacy mà là học cách làm việc với nó.

Các chiến lược tốt nhất là di chuyển dần dần, đóng gói mã cũ đằng sau các giao diện mới và kiểm thử tự động. Legacy chỉ trở thành vấn đề khi nó không còn có thể thay đổi với chi phí dự đoán được.

Dấu hiệu chính của hệ thống legacy

Thiếu kiểm thử tự động — chỉ số chính. Nếu sau khi thay đổi một dòng duy nhất, nhà phát triển không thể chạy kiểm thử và xác nhận không có gì hỏng — bạn đang đối mặt với legacy. Dấu hiệu bổ sung: quy trình triển khai mất hàng giờ và yêu cầu các bước thủ công.

Tài liệu không khớp với mã — một dấu hiệu khác. Sơ đồ kiến trúc đã lỗi thời, các chú thích mô tả hành vi đã thay đổi. Thời gian làm quen cho nhà phát triển mới vượt quá một tháng — dấu hiệu của độ phức tạp cao và khả năng bảo trì thấp.

Các dấu hiệu bổ sung: kiến trúc nguyên khối không có ranh giới rõ ràng, kiểm thử thủ công là phương pháp xác nhận chính, đường ống CI dài (hơn 30 phút), sử dụng thư viện không có phiên bản mới nhất và không thể cập nhật phụ thuộc mà không làm hỏng các mô-đun liên quan.

Hiện tượng mã dễ vỡ — thay đổi một chỗ làm hỏng ba chỗ khác. Đây là hậu quả của sự kết nối chặt chẽ, khi các mô-đun biết quá nhiều về nhau. Kết nối càng cao, hệ thống càng nhanh chóng chuyển sang hạng mục legacy.

Rủi ro khi làm việc với mã lỗi thời

Giảm tốc độ — rủi ro chính. Thêm một tính năng đơn giản cần hàng giờ nghiên cứu mã và hàng ngày kiểm thử. Theo Stripe (2024), các nhà phát triển dành 33% thời gian để khắc phục nợ kỹ thuật, liên quan trực tiếp đến sự hiện diện của các mô-đun legacy trong dự án.

Rò rỉ chuyên môn — tác giả của mã gốc rời công ty và tài liệu không đầy đủ. Các nhà phát triển mới sợ chạm vào các mô-đun không quen thuộc, dẫn đến hiệu ứng mã đông lạnh: mô-đun không phát triển nhưng tiếp tục hoạt động. Bus factor của các hệ thống này cực kỳ thấp.

Bảo mật — các thư viện lỗi thời chứa các lỗ hổng đã biết. Sử dụng OpenSSL 1.0.2 hoặc các phiên bản cũ của Jackson trong các dự án Java là con đường trực tiếp dẫn đến các sự cố bảo mật có thể khiến doanh nghiệp mất uy tín và khách hàng.

Mất động lực đội nhóm — làm việc với legacy mà không có chiến lược cải thiện làm giảm sự hài lòng của nhà phát triển. Đội nhóm không còn tự hào về sản phẩm, tỷ lệ nhân viên nghỉ việc tăng, làm chậm sự phát triển hệ thống hơn nữa.

Chiến lược tái cấu trúc legacy

Characterization tests — bước đầu tiên trước bất kỳ thay đổi nào đối với mã legacy. Chạy mã với dữ liệu đầu vào đã biết và ghi lại đầu ra dự kiến. Các kiểm thử này ghi lại hành vi hiện tại như một đặc tả. Golden master testing là một biến thể nơi đầu ra được so sánh với tệp tham chiếu.

Phân tích Seam — tìm các điểm có thể phá vỡ kết nối mà không thay đổi hành vi. Michael Feathers xác định một số loại seam: preprocessor seam, object seam, link seam. Object seam là phổ biến nhất: thay thế đối tượng thực bằng stub kiểm thử qua giao diện.

Sprout method và Sprout class — các kỹ thuật thêm mã mới bên cạnh mã cũ, không phải bên trong nó. Thay vì sửa đổi phương thức hiện có, hãy tạo phương thức mới với logic mong muốn và gọi nó từ phương thức cũ. Điều này giảm thiểu rủi ro làm hỏng mã đang hoạt động.

Ví dụ: thêm logging vào legacy

groovy
class LegacyPaymentProcessor {
    def process(payment) {
        // 200 dòng mã legacy không nên chạm vào
        logPayment(payment) // phương thức sprout
    }
    def logPayment(payment) {
        // mã mới được thêm vào bên cạnh legacy
    }
}

Di chuyển sang nền tảng công nghệ hiện đại

Mẫu Strangler Fig — cách tiếp cận được khuyến nghị cho di chuyển legacy. Một mô-đun mới được tạo song song, lưu lượng truy cập dần dần được chuyển từ cũ sang mới. Mô-đun cũ tự nhiên chết khi không còn nhận được yêu cầu. Mẫu này giảm thiểu rủi ro và cho phép khôi phục nếu có sự cố.

Branch by Abstraction — kỹ thuật tạo lớp trừu tượng trên cả cài đặt cũ và mới. Mã client chuyển sang lớp trừu tượng và cài đặt cũ dần dần được thay thế. Ví dụ: thay thế lớp mạng từ AFNetworking sang Alamofire qua giao thức NetworkService thống nhất.

Di chuyển theo giai đoạn — chia quá trình chuyển đổi thành các bước nhỏ: đóng gói mô-đun cũ → viết kiểm thử → tạo mô-đun mới → chạy song song → xóa mô-đun cũ. Mỗi bước kết thúc với trạng thái hệ thống ổn định, cho phép triển khai bất cứ lúc nào.

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

Có cần viết lại hoàn toàn legacy không?

Viết lại hoàn toàn là lựa chọn rủi ro nhất. Chỉ 25% dự án Big Rewrite thành công đúng hạn. Tốt hơn là áp dụng mẫu Strangler Fig: thay thế mô-đun dần dần mà không dừng sản phẩm. Mỗi lần lặp mang lại giá trị kinh doanh và rủi ro được phân bổ theo thời gian.

Làm thế nào để bắt đầu tái cấu trúc legacy mà không có kiểm thử?

Bắt đầu với characterization tests: chạy mô-đun với dữ liệu đã biết, ghi lại kết quả. Golden master testing là cách đơn giản để ghi lại hành vi. Thêm kiểm thử mỗi khi bạn chạm vào một dòng mã. Sau 6 tháng bạn sẽ có một khung bảo vệ chống lại hồi quy.

Khi nào thì tốt hơn không nên chạm vào legacy?

Nếu hệ thống ổn định, không yêu cầu thay đổi thường xuyên và không ảnh hưởng đến tốc độ phát triển của các mô-đun khác — hãy để nó yên. Nếu không hỏng, đừng sửa là cách tiếp cận hợp lý cho các mô-đun legacy cô lập với tần suất thay đổi thấp. Chỉ chạm vào mã khi cần thực hiện các thay đổi kinh doanh.

Làm thế nào để cập nhật phụ thuộc trong dự án legacy?

Sử dụng phiên bản ngữ nghĩa và cập nhật theo từng bước: patch → minor → major. Viết kiểm thử tương thích cho mỗi thư viện. Dependabot hoặc Renovate tự động hóa việc tạo PR cập nhật. Nếu thư viện không còn được hỗ trợ, hãy lên kế hoạch thay thế nó qua một lớp trừu tượng.

Legacy khác nợ kỹ thuật như thế nào?

Nợ kỹ thuật là một phép ẩn dụ để ước tính chi phí của các cải tiến bị hoãn. Legacy là hệ thống hoặc mã cụ thể đã trở nên lỗi thời. Nợ kỹ thuật có thể tích lũy trong một tháng, legacy cần thời gian. Không phải mọi nợ kỹ thuật đều trở thành legacy, nhưng mọi legacy đều chứa nợ kỹ thuật.

Tổng kết

  • Legacy — mã không có kiểm thử, bất kể tuổi đời. Mã mới không có coverage là legacy từ ngày đầu tiên
  • Tuổi của mã — không phải vấn đề. Vấn đề là kết nối cao, thiếu kiểm thử và tài liệu
  • Characterization tests — bước đầu tiên trước bất kỳ thay đổi nào đối với mô-đun legacy để ghi lại hành vi
  • Mẫu Strangler Fig — chiến lược di chuyển an toàn với thay thế mô-đun dần dần
  • Sprout method — kỹ thuật thêm mã mới bên cạnh mã cũ mà không rủi ro hỏng
  • 35% viết lại toàn bộ thất bại — di chuyển dần dần đáng tin cậy hơn Big Rewrite
  • Legacy cô lập với tần suất thay đổi thấp tốt nhất là không chạm vào

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