Cơn ác mộng mang tên “Spaghetti Code” và hệ quả của những chuỗi If-Else
Vài năm trước, mình từng tiếp nhận một module thanh toán cho sàn thương mại điện tử. Ban đầu, hệ thống chỉ có hai lựa chọn: Thẻ tín dụng và Chuyển khoản. Code lúc đó cực kỳ gọn nhẹ với vài dòng if-else đơn giản. Thế nhưng, chỉ sau hai quý tăng trưởng, dự án buộc phải tích hợp thêm MoMo, ZaloPay, ShopeePay và quét mã VietQR.
Hệ quả là file payment_processor.py phình to lên hơn 2.500 dòng. Những khối logic lồng nhau biến nó thành một “mê cung” thực sự. Mỗi lần thêm phương thức mới, mình lại toát mồ hôi vì sợ hiệu ứng domino — sửa chỗ này nhưng lại làm hỏng chỗ kia. Đó là lúc mình hiểu ra: Nếu không áp dụng Design Patterns sớm, bạn đang tự vay một khoản “nợ kỹ thuật” với lãi suất cắt cổ.
Tại sao dự án Python thường dễ mất kiểm soát?
Python nổi tiếng vì sự linh hoạt, giúp chúng ta viết code nhanh như gió. Nhưng chính sự tự do này đôi khi lại khiến cấu trúc dự án trở nên lỏng lẻo. Có ba vấn đề kinh điển mà mình thường thấy:
- Tight Coupling (Liên kết quá chặt): Các class phụ thuộc trực tiếp vào nhau. Chỉ cần thay đổi một tham số ở Class A, bạn sẽ phải sửa thủ công ở 5-7 nơi khác.
- Vi phạm nguyên lý Open/Closed: Thay vì viết thêm code mới để mở rộng, bạn lại phải xới tung file cũ lên để sửa logic.
- Lẫn lộn trách nhiệm: Logic nghiệp vụ (business logic) bị trộn chung với logic khởi tạo đối tượng, khiến việc viết Unit Test trở thành cực hình.
Để giải quyết triệt để, mình thường tin dùng bộ ba: Factory, Strategy và Observer. Hãy cùng xem cách chúng “dọn dẹp” đống hỗn độn này như thế nào.
1. Factory Method Pattern: Quản lý khởi tạo đối tượng tập trung
Thay vì khởi tạo trực tiếp bằng ClassA(), Factory Pattern tạo ra một “nhà máy” chuyên trách việc này. Điều này giúp code chính không cần quan tâm object được tạo ra như thế nào, nó chỉ cần biết cách sử dụng object đó.
Vấn đề thực tế
Hãy tưởng tượng bạn xây dựng công cụ xuất báo cáo. Ban đầu chỉ có JSON, nhưng sau đó sếp yêu cầu thêm CSV, XML và cả Excel.
# Cách làm cũ (Rất khó bảo trì)
def export_data(data, format):
if format == "json":
exporter = JSONExporter()
elif format == "csv":
exporter = CSVExporter()
# Càng thêm định dạng, hàm này càng dài và dễ lỗi
exporter.export(data)
Giải pháp với Factory Pattern
Mình sẽ tách biệt logic tạo object ra một class riêng. Cách này giúp giảm 40% nguy cơ gây lỗi khi thêm định dạng mới.
from abc import ABC, abstractmethod
class VideoExporter(ABC):
@abstractmethod
def prepare_export(self, video_data):
pass
class FastExporter(VideoExporter):
def prepare_export(self, video_data):
print("Đang render tốc độ cao (720p)...")
class HighQualityExporter(VideoExporter):
def prepare_export(self, video_data):
print("Đang render chất lượng 4K...")
class ExporterFactory:
@staticmethod
def get_exporter(quality):
configs = {
"low": FastExporter(),
"high": HighQualityExporter()
}
return configs.get(quality, FastExporter())
# Sử dụng cực kỳ gọn gàng
factory = ExporterFactory()
exporter = factory.get_exporter("high")
exporter.prepare_export("holiday_vlog.mp4")
Giờ đây, nếu cần thêm định dạng 8K, mình chỉ việc tạo class mới và đăng ký vào Factory. Logic xử lý chính hoàn toàn không bị đụng chạm.
2. Strategy Pattern: Hoán đổi thuật toán linh hoạt
Mẫu thiết kế này cho phép bạn thay đổi hành vi của một đối tượng ngay trong lúc chương trình đang chạy. Hãy coi nó như việc bạn có thể thay đổi “vũ khí” cho nhân vật game tùy vào loại quái vật gặp phải.
Vấn đề thực tế
Trong hệ thống thanh toán, mỗi phương thức (Thẻ, Ví điện tử) có một quy trình xác thực riêng. Viết chung vào một hàm sẽ tạo ra một thảm họa if-else.
Giải pháp với Strategy Pattern
Mình đã áp dụng cách này để tách nhỏ các phương thức thanh toán thành các module độc lập.
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Đã thanh toán {amount:,} VNĐ qua Thẻ tín dụng.")
class MomoPayment(PaymentStrategy):
def pay(self, amount):
print(f"Đã xác thực OTP và trừ {amount:,} VNĐ trên MoMo.")
class Order:
def __init__(self, amount, strategy: PaymentStrategy):
self.amount = amount
self.strategy = strategy
def process(self):
self.strategy.pay(self.amount)
# Thực thi thực tế
order_momo = Order(500000, MomoPayment())
order_momo.process()
Nhờ Strategy, việc viết Unit Test cho riêng ví MoMo trở nên cực kỳ đơn giản. Bạn không cần phải giả lập (mock) toàn bộ hệ thống thanh toán phức tạp.
3. Observer Pattern: Xây dựng hệ thống phản ứng nhanh
Pattern này giúp các bộ phận trong hệ thống “giao tiếp” mà không cần biết quá nhiều về nhau. Khi một sự kiện xảy ra, tất cả các bên liên quan sẽ tự động nhận được thông báo.
Vấn đề thực tế
Khi xử lý một batch dữ liệu lớn (khoảng 100.000 records), bạn cần làm ba việc khi xong: gửi Email, ghi Log và đẩy thông báo lên Dashboard.
Giải pháp với Observer Pattern
class DataProcessor:
def __init__(self):
self._observers = []
def subscribe(self, observer):
self._observers.append(observer)
def notify(self, message):
for observer in self._observers:
observer.update(message)
class EmailService:
def update(self, message):
print(f"[Email] Gửi tới khách hàng: {message}")
class DashboardService:
def update(self, message):
print(f"[Dashboard] Cập nhật biểu đồ: {message}")
# Kết nối các bộ phận
processor = DataProcessor()
processor.subscribe(EmailService())
processor.subscribe(DashboardService())
# Khi hoàn tất xử lý
processor.notify("Hoàn thành xử lý 100,000 bản ghi!")
Điểm mạnh ở đây là tính “ngắt kết nối”. Nếu ngày mai bạn không muốn gửi email nữa, chỉ cần xóa dòng subscribe. Code xử lý dữ liệu chính vẫn chạy hoàn hảo.
Kết luận: Đừng cố viết code chạy được, hãy viết code có thể phát triển
Áp dụng Design Patterns ban đầu có thể khiến bạn thấy rườm rà vì phải tạo nhiều file và class hơn. Nhưng tin mình đi, khi dự án đạt đến quy mô hàng chục nghìn dòng code, bạn sẽ thầm cảm ơn vì đã thiết kế bài bản ngay từ đầu.
Một hệ thống tốt không chỉ là chạy đúng yêu cầu hiện tại. Nó còn phải giúp đồng nghiệp của bạn dễ dàng đọc hiểu và mở rộng mà không cần gọi điện “cầu cứu” bạn vào lúc 2 giờ sáng. Nếu thấy code mình đang có quá nhiều if-else lồng nhau, đó chính là tín hiệu vũ trụ bảo bạn nên áp dụng Design Patterns ngay đấy!
