Tái cấu trúc mã (Refactoring) và mẫu thiết kế (Design Pattern)
1. Tổng quan
A. Định nghĩa
Tái cấu trúc mã (Refactoring) là hoạt động cải thiện cấu trúc bên trong trong khi vẫn giữ nguyên hành vi bên ngoài (chức năng) của phần mềm để dễ hiểu và dễ sửa đổi hơn, còn mẫu thiết kế (Design Pattern) là cấu trúc giải pháp đã được kiểm chứng và có thể tái sử dụng (khuôn mẫu thiết kế) cho các bài toán thiết kế thường lặp lại.
Chìa khóa để hiểu quan hệ giữa hai kỹ thuật là 'tái cấu trúc là quá trình (process), mẫu thiết kế là đích đến (target)'. Khi mã trở nên lộn xộn theo thời gian, khó hiểu và khó sửa (tức là khi code smell tích tụ), ta dọn dẹp bên trong bằng tái cấu trúc, và thứ chỉ ra phương hướng "cải thiện thành cấu trúc nào" chính là mẫu thiết kế. Martin Fowler trong cuốn 『Refactoring』 (1999, tái bản lần 2 năm 2018) định nghĩa tái cấu trúc là "thay đổi cấu trúc bên trong của phần mềm mà không thay đổi hành vi quan sát được, để dễ hiểu hơn và giảm chi phí sửa đổi"; ở đây, ràng buộc "không thay đổi hành vi" là tiêu chí quyết định phân biệt tái cấu trúc với sửa mã thông thường (thêm chức năng·sửa lỗi).
Khi tái cấu trúc, mã tự nhiên hội tụ về cấu trúc mẫu đã được kiểm chứng, và ngược lại, chính hành vi áp dụng mẫu cũng là tái cấu trúc. Thực tế, phần sau cuốn sách của Fowler chứa góc nhìn "tái cấu trúc hướng tới mẫu (Refactoring Toward Patterns)", và 『Refactoring to Patterns』 (2004) của Joshua Kerievsky là tác phẩm tiêu biểu hệ thống hóa cách tiếp cận này. Điểm chung của cả hai là nâng cao chất lượng phần mềm (khả năng bảo trì·tính linh hoạt·khả năng tái sử dụng), còn điểm khác là tái cấu trúc là 'hành vi (hoạt động cải tiến)' trong khi mẫu thiết kế là 'sản phẩm (cấu trúc giải pháp)'.
B. Bối cảnh ra đời và sự cần thiết
Phần mềm liên tục bị sửa đổi do thay đổi yêu cầu. Như các định luật tiến hóa phần mềm của Lehman chỉ ra, phần mềm đang được sử dụng liên tục chịu áp lực thay đổi, và nếu bỏ mặc thì cấu trúc sẽ mục nát (software entropy tăng) khiến chi phí bảo trì tăng theo cấp số nhân. Mỗi lần thêm một chức năng mới, những chỗ không ngờ tới bị hỏng do phụ thuộc chồng chéo, và cuối cùng trở thành "mã không dám động vào (legacy)".
Hai trụ cột để ngăn sự mục nát này là tái cấu trúc và mẫu thiết kế. Tái cấu trúc là hoạt động phòng ngừa·liên tục, dần dần chỉnh đốn mã mục nát để giữ chi phí thay đổi ở mức thấp, còn mẫu thiết kế là bộ từ vựng (vocabulary) để ngay từ đầu thiết kế cấu trúc chịu được thay đổi hoặc cung cấp đích hướng tới cho tái cấu trúc. Đặc biệt, trong môi trường Agile·DevOps lặp lại phát hành theo chu kỳ ngắn, tái cấu trúc — thứ luôn duy trì sức khỏe của mã — kết hợp với tích hợp liên tục (CI) đã trở thành thực hành bắt buộc.
2. Nguyên lý và quy trình tái cấu trúc
A. Tiền đề của tái cấu trúc an toàn — kiểm thử
Tái cấu trúc có đại tiền đề là không thay đổi chức năng, nên cốt lõi là làm sao bảo đảm "chức năng vẫn như cũ". Cơ chế bảo đảm đó chính là kiểm thử tự động. Không có kiểm thử thì không có cách nào xác nhận hành vi giống hệt sau khi sửa cấu trúc, và tái cấu trúc trở thành việc tiềm ẩn đưa lỗi vào. Do đó Fowler nhấn mạnh "hãy có bộ kiểm thử vững chắc trước khi tái cấu trúc", và nguyên tắc này thấm nguyên vẹn vào chu trình Red-Green-Refactor của TDD (Test-Driven Development).
Quy trình là lặp lại 'sửa mã theo đơn vị nhỏ → xác nhận bằng kiểm thử rằng chức năng không đổi'. Thay đổi lớn một lần thì khi thất bại khó truy nguyên nhân và rủi ro lớn, nên chia thành các bước nguyên tử (atomic) như một lần trích xuất phương thức·một lần đổi tên, và chạy kiểm thử ở mỗi bước. Nếu thất bại có thể lập tức quay lại bước ngay trước, nhờ đó bảo đảm an toàn. Ở điểm này, tái cấu trúc hiệu quả nhất khi kết hợp với quản lý phiên bản (quản lý cấu hình).
B. Tín hiệu kích hoạt tái cấu trúc — code smell (mùi mã)
Dấu hiệu báo thời điểm cần tái cấu trúc là code smell. Đó là mã "có mùi cần sửa"; bản thân nó không phải lỗi nhưng là vấn đề cấu trúc gây khó khăn cho bảo trì. Tiêu biểu có mã trùng lặp (Duplicated Code), phương thức dài (Long Method), lớp lớn (Large Class), danh sách tham số quá dài (Long Parameter List), sửa phân tán (Shotgun Surgery, sửa một chỗ phải sửa đồng thời nhiều chỗ), thiên vị chức năng (Feature Envy, tham chiếu quá mức dữ liệu của lớp khác), v.v.
Nhận ra smell không có nghĩa là sửa hết ngay lập tức. Quy tắc thực tiễn Fowler đưa ra là "tái cấu trúc khi lặp lại lần thứ ba (Rule of Three)" — cách tiếp cận tiết chế: chịu đựng tới lần thứ hai, khi trùng lặp xuất hiện lần thứ ba thì mới dọn dẹp. Đây là chủ nghĩa thực dụng cảnh giác với trừu tượng hóa trước quá mức, nghĩa là thay vì hễ có smell là động vào, hãy tập trung tái cấu trúc ở những phần thực sự thay đổi thường xuyên.
C. Các kỹ thuật tái cấu trúc chủ yếu
Kỹ thuật thường gặp nhất là trích xuất phương thức (Extract Method/Function), tách phương thức dài hoặc logic trùng lặp thành phương thức riêng có tên có ý nghĩa. Chẳng hạn, gom logic tính thuế bị trùng ở nhiều màn hình (code smell) về một calculateTax() thì khi thuế suất thay đổi chỉ cần sửa một chỗ. Ngoài ra còn có đổi tên (Rename) để thể hiện ý đồ, trích xuất/tách lớp (Extract Class) để chia lớp phình to theo đơn vị trách nhiệm, và đơn giản hóa điều kiện (Decompose Conditional, Replace Conditional with Polymorphism) để thay nhánh phức tạp bằng đa hình.
Bảng dưới tóm tắt các kỹ thuật tiêu biểu, nhưng điều quan trọng trong thực tế là phán đoán "đối ứng smell nào với kỹ thuật nào". Bảng chỉ để tham khảo; thực tế mấu chốt là thuần thục luồng smell → kỹ thuật → kiểm chứng bằng kiểm thử.
| Hạng mục | Nội dung |
|---|---|
| Mục đích | Giữ hành vi bên ngoài, cải thiện cấu trúc bên trong (dễ đọc·dễ bảo trì·linh hoạt) |
| Quy trình | Bảo đảm kiểm thử → cải thiện đơn vị nhỏ (nguyên tử) → kiểm chứng bằng kiểm thử → lặp lại |
| Kỹ thuật chính | Trích xuất phương thức, đổi tên, tách lớp, đơn giản hóa điều kiện, loại bỏ biến tạm |
| Code smell | Mã trùng lặp, phương thức dài, lớp lớn, tham số quá nhiều, sửa phân tán/thiên vị chức năng |
| Công cụ tiền đề | Kiểm thử tự động, quản lý cấu hình (VCS), chức năng tái cấu trúc tự động của IDE |
3. Phân loại và nguyên lý của mẫu thiết kế (GoF)
flowchart TB
subgraph CRE["Mẫu khởi tạo (Creational)"]
S1["Singleton"]
S2["Phương thức nhà máy (Factory Method)"]
S3["Builder"]
end
subgraph STR["Mẫu cấu trúc (Structural)"]
T1["Adapter"]
T2["Decorator"]
T3["Proxy"]
end
subgraph BEH["Mẫu hành vi (Behavioral)"]
B1["Observer"]
B2["Chiến lược (Strategy)"]
B3["Command"]
end
GoF["23 mẫu thiết kế GoF"] --> CRE
GoF --> STR
GoF --> BEH
Tiêu biểu cho mẫu thiết kế là 23 mẫu do GoF (Gang of Four; Gamma, Helm, Johnson, Vlissides) tổng hợp trong cuốn 『Design Patterns』 năm 1994, được chia theo mục đích thành mẫu khởi tạo·cấu trúc·hành vi. Cách phân loại này lấy "mẫu xử lý cái gì" làm tiêu chí, và mỗi nhóm đối ứng với một mối quan tâm thiết kế khác nhau.
Mẫu khởi tạo (Creational) đóng gói cách tạo·cấu hình đối tượng, để thay đổi logic khởi tạo không lan sang mã sử dụng. Ví dụ, Singleton bảo đảm chỉ có một thể hiện (quản lý cấu hình·log, v.v.), Factory Method ủy thác cho lớp con việc tạo lớp cụ thể nào, loại bỏ phụ thuộc trực tiếp vào new. Trong thực tế, hệ thống thường xuyên thêm phương thức thanh toán (thẻ·thanh toán nhanh·chuyển khoản) cô lập phần khởi tạo bằng factory để giảm thiểu việc sửa mã hiện có khi thêm phương thức mới.
Mẫu cấu trúc (Structural) kết hợp lớp·đối tượng để tạo cấu trúc lớn hơn trong khi bảo đảm tính linh hoạt. Adapter nối các giao diện không tương thích để tái sử dụng hệ thống cũ·thư viện ngoài, Decorator bọc (wrapping) đối tượng thay vì kế thừa để gắn thêm chức năng một cách động. BufferedReader(new FileReader(...)) trong thư viện chuẩn Java là ví dụ điển hình của Decorator.
Mẫu hành vi (Behavioral) xử lý việc phân bổ trách nhiệm và tương tác (thuật toán·giao tiếp) giữa các đối tượng. Observer thông báo thay đổi trạng thái cho bên đăng ký (sự kiện·cập nhật trong MVC) để giảm độ kết dính, Strategy đóng gói các thuật toán có thể hoán đổi, thay nhánh điều kiện bằng đa hình. Đặc biệt, mẫu Strategy là đích đến của phép tái cấu trúc "thay điều kiện bằng đa hình" đã nêu, cho thấy rõ điểm gặp nhau giữa tái cấu trúc và mẫu.
| Loại | Mục đích | Ví dụ tiêu biểu |
|---|---|---|
| Mẫu khởi tạo | Đóng gói việc tạo·cấu hình đối tượng | Singleton, Factory Method, Abstract Factory, Builder, Prototype |
| Mẫu cấu trúc | Tạo cấu trúc bằng cách kết hợp đối tượng·lớp | Adapter, Decorator, Proxy, Composite, Facade |
| Mẫu hành vi | Trách nhiệm·tương tác·thuật toán giữa các đối tượng | Observer, Strategy, Command, State, Template Method |
4. Quan hệ giữa tái cấu trúc và mẫu thiết kế — so sánh và ví dụ
Hai khái niệm không đối lập mà bổ trợ lẫn nhau. Sơ đồ dưới đây thể hiện luồng xuất phát từ code smell, qua tái cấu trúc, hội tụ về cấu trúc mẫu.
sequenceDiagram
participant Dev as Lập trình viên
participant Code as Mã nguồn
participant Test as Bộ kiểm thử
Dev->>Code: Nhận diện code smell(trùng lặp·điều kiện dài)
Dev->>Test: Bảo đảm kiểm thử bảo chứng hành vi hiện có
Dev->>Code: Tái cấu trúc đơn vị nhỏ(trích xuất phương thức)
Code->>Test: Chạy kiểm thử
Test-->>Dev: Đạt(xác nhận hành vi không đổi)
Dev->>Code: Thay nhánh điều kiện bằng mẫu Strategy
Code->>Test: Chạy lại
Test-->>Dev: Đạt → hội tụ về cấu trúc mẫu
Cốt lõi tạo nên khác biệt là 'tính chất' và 'thời điểm'. Tái cấu trúc là hoạt động cải tiến hậu kỳ·liên tục nhắm vào mã đã tồn tại, còn mẫu thiết kế là cấu trúc giải pháp tĩnh cho bài toán thiết kế. Hàm ý thực tiễn như sau. Việc dự đoán tương lai quá mức ở giai đoạn thiết kế ban đầu rồi cài sẵn mẫu (tổng quát hóa suy đoán) thường dẫn tới thiết kế quá mức, nên an toàn hơn là bắt đầu đơn giản, và khi thay đổi thực sự xảy ra làm lộ smell thì mới đưa mẫu cần thiết vào bằng tái cấu trúc. Tức là mẫu gần với "thứ đạt tới bằng tái cấu trúc" hơn là "thứ cài sẵn từ trước".
Ví dụ cụ thể: Giả sử một hệ thống đặt hàng tính phí vận chuyển bằng điều kiện dài kiểu if (region == "remote_island") ... else if (weight > 20) ..., rồi khi quy tắc khuyến mãi·giao hàng quốc tế liên tục tăng, điều kiện vượt quá 40 dòng (smell phương thức dài + điều kiện lặp lại). Lập trình viên trước tiên bảo đảm kiểm thử xác minh kết quả tính toán, sau đó tách mỗi quy tắc thành một lớp hiện thực của giao diện ShippingPolicy (mẫu Strategy). Kết quả là khi thêm quy tắc vận chuyển mới, chỉ cần thêm lớp mà không động vào điều kiện hiện có, thỏa mãn OCP (nguyên tắc đóng-mở). Toàn bộ quá trình này là tái cấu trúc, và điểm đến là mẫu Strategy.
| Phân loại | Tái cấu trúc | Mẫu thiết kế |
|---|---|---|
| Tính chất | Hành vi (hoạt động cải tiến, động từ) | Sản phẩm (cấu trúc giải pháp, danh từ) |
| Đối tượng | Cấu trúc bên trong của mã hiện có | Bài toán thiết kế lặp lại |
| Thời điểm | Hậu kỳ·liên tục | Luôn được tham chiếu như từ vựng thiết kế |
| Điểm chung | Nâng cao chất lượng như khả năng bảo trì·tính linh hoạt·khả năng tái sử dụng | (như bên cạnh) |
| Quan hệ | Cải tiến với mẫu làm đích hướng tới | Đạt tới·hiện thực bằng tái cấu trúc |
5. Chuyên sâu — xu hướng mới nhất và ứng dụng thực tiễn
Tái cấu trúc·mẫu thiết kế tuy đã trưởng thành về khái niệm, nhưng cách thực hành đang tiến hóa theo thay đổi của công cụ và môi trường phát triển. Thứ nhất, tái cấu trúc tự động của IDE đã được chuẩn hóa: IntelliJ IDEA·Eclipse·Visual Studio, v.v. thực hiện an toàn việc đổi tên·trích xuất phương thức·đổi chữ ký (tự động cập nhật tham chiếu). Rủi ro sai sót của tái cấu trúc thủ công giảm mạnh, tái cấu trúc không còn là sự kiện đặc biệt mà trở thành hoạt động vi mô thực hiện thường xuyên trong khi viết mã.
Thứ hai là kết hợp với phân tích tĩnh·cổng chất lượng. Các công cụ như SonarQube tự động đo code smell·mức trùng lặp·độ phức tạp (Cyclomatic Complexity), tích hợp làm cổng chất lượng của pipeline CI và chặn merge khi không đạt chuẩn. Nhờ đó, phán đoán "khi nào tái cấu trúc" được dựa trên dữ liệu (chỉ số smell).
Thứ ba là sự xuất hiện của trợ lý lập trình AI tạo sinh (AI review mã·đề xuất tái cấu trúc). Các trợ lý mã gần đây đề xuất ứng viên tái cấu trúc và phương án áp dụng mẫu theo đơn vị hàm, nhưng đề xuất của AI không tự động bảo đảm "hành vi bên ngoài không đổi", nên kiểm thử hồi quy vẫn là lưới an toàn bắt buộc. Tức là AI tăng tốc độ tái cấu trúc nhưng không thay thế chính nguyên tắc "kiểm chứng dựa trên kiểm thử".
Thứ tư, khái niệm đang mở rộng thành tái cấu trúc ở mức kiến trúc. Mẫu Strangler Fig (cây sung bóp cổ) dùng khi phân rã dần hệ thống nguyên khối (monolithic) thành microservice là chiến lược tiêu biểu để thực hiện an toàn tái cấu trúc quy mô lớn: không thay hệ thống hiện có một lần mà để cấu trúc mới dần bao bọc và thay thế cấu trúc cũ theo đơn vị chức năng. Đây là sự mở rộng nguyên lý tái cấu trúc "đơn vị nhỏ·lặp lại kiểm chứng" lên quy mô hệ thống.
6. Lưu ý và hàm ý (góc độ Kỹ sư chuyên nghiệp)
Tái cấu trúc không có kiểm thử là rủi ro (ưu tiên lưới an toàn chất lượng). Để bảo đảm chức năng không đổi, kiểm thử tự động phải là tiền đề, và khi kết hợp với Red-Green-Refactor của TDD thì có thể cải tiến liên tục một cách an toàn. Với mã không có kiểm thử như hệ thống cũ, cách chuẩn là cố định hành vi hiện tại bằng kiểm thử đặc tính hóa (Characterization Test) của Michael Feathers rồi mới bắt tay tái cấu trúc.
Lạm dụng mẫu là thiết kế quá mức (nguyên tắc tiết chế). Ép áp dụng mẫu khi không có vấn đề chỉ làm tăng các lớp gián tiếp, khiến độ phức tạp ngược lại tăng lên. Theo "Rule of Three", chỉ đưa vào một cách tiết chế các mẫu cần thiết khi smell thực sự lặp lại, và phải cảnh giác với tổng quát hóa suy đoán (vi phạm YAGNI).
Văn hóa cải tiến liên tục và nội tại hóa vào quy trình. Biến tái cấu trúc thành thói quen hằng ngày của phát triển (quy tắc hướng đạo sinh: "hãy để lại sạch hơn lúc bạn đến") chứ không phải công việc lớn cần phê duyệt riêng, đồng thời tích hợp phân tích tĩnh·cổng chất lượng vào CI để thường xuyên quản lý nợ kỹ thuật (Technical Debt).
Ưu tiên hóa theo góc độ chi phí·rủi ro (đánh đổi). Không thể sửa hết mọi smell, nên tập trung tái cấu trúc vào các điểm nóng (hotspot) có tần suất thay đổi cao và rủi ro lỗi lớn, còn cải tiến cấu trúc quy mô lớn thì thực hiện theo cách từng bước·có thể đảo ngược như mẫu Strangler để kiểm soát rủi ro phát hành.
Liên kết với quản trị và chỉ số đo lường. Quản lý hiệu quả của tái cấu trúc (giảm độ phức tạp·tỷ lệ lỗi·cải thiện lead time) bằng chỉ số để giải thích tính chính đáng với ban lãnh đạo, và đưa việc trả nợ kỹ thuật vào backlog chính thức để bền vững.
Tài liệu tham khảo
- Martin Fowler, "Refactoring", https://refactoring.com/
- Refactoring Guru — Design Patterns & Refactoring, https://refactoring.guru/
- Martin Fowler, "StranglerFigApplication", https://martinfowler.com/bliki/StranglerFigApplication.html
Tóm tắt một câu: Tái cấu trúc là hoạt động liên tục cải thiện cấu trúc bên trong trong khi giữ nguyên chức năng, còn mẫu thiết kế là cấu trúc giải pháp thiết kế đã được kiểm chứng; cần dọn dẹp code smell bằng tái cấu trúc đơn vị nhỏ dựa trên kiểm thử, lấy mẫu làm đích hướng tới để nâng cao chất lượng, nhưng phải cảnh giác với lạm dụng mẫu (thiết kế quá mức) theo nguyên tắc tiết chế.