+2

Sự Lên Ngôi Của If/Else Và Cái Giá Phải Trả Khi Thiết Kế Hệ Thống Thiếu Tầm Nhìn

Lời mở đầu: Ngày đầu dự án, mọi thứ đều màu hồng

Ngày đầu tiên làm cái module thanh toán cho dự án E-commerce, nghiệp vụ nó đơn giản lắm. Khách hàng một là trả tiền mặt (COD), hai là chuyển khoản. Bạn code phát ăn ngay. Cấu trúc gọn gàng, sạch sẽ, test phủ kín, đi review code tự tin ưỡn ngực.

Nhưng mà business thì đâu có đứng yên, đúng không anh em?

  • Sáu tháng sau, Product Manager chạy sang vỗ vai: "Em ơi, tích hợp thêm cái ví điện tử với quẹt thẻ tín dụng cho anh."
  • Một năm sau: "Em ơi, đợt này bên mình chạy chương trình với ngân hàng, làm thêm cái thanh toán trả góp nhé."

Vấn đề ở đây là, mỗi cái yêu cầu (change) này khi đứng một mình thì nó... vô cùng hợp lý. Nhưng sự thật là: Change không bao giờ đến một lần rồi thôi. Nó đến liên tục như một cơn lũ. Và nếu thiết kế hệ thống của mình không tính đến cái áp lực này ngay từ đầu, thì sớm muộn gì nó cũng sẽ gãy

1. Căn bệnh kinh niên mang tên: "Thêm cái if/else là xong mà anh!"

Khi đối mặt với một yêu cầu mới, phản xạ đầu tiên của anh em – đặc biệt là các bạn Junior – là gì? Một câu nói nghe qua thì vô hại nhưng lại là khởi nguồn của mọi thảm họa về sau: "À, thêm cái if/else vào đoạn check là xong mà."

Đúng là như vậy thật. Ngay lúc đó, nó giải quyết được bài toán. Feature chạy, test pass, deploy ngon lành. Không có gì sai cả ở thời điểm đó.

Và thế là:

  • Lần đầu thêm phương thức thanh toán mới, ta nhét thêm 1 cái if.
  • Lần thứ hai, ta nối thêm cái else if.
  • Lần thứ ba, lại một cái if lồng bên trong cái else.

Cứ thế, cứ thế... hệ thống bắt đầu chịu một áp lực ngày càng lớn. Cho đến một ngày đẹp trời, hậu quả ập đến. Đoạn code xử lý thanh toán bỗng nhiên trở thành "Vùng Đất Cấm" (Legacy Code).

Chẳng ai dám đụng vào cái file dài 2000 dòng đầy rẫy if/else đó nữa. Sửa một dòng check khuyến mãi ở phương thức "Chuyển khoản" thì tự nhiên thanh toán "Momo" lại báo lỗi. Những buổi Code Review kéo dài cả buổi trong ngao ngán, và những con bug "tâm linh" bắt đầu ám ảnh cả team.

2. If/Else không có tội. Tội là ở Design Pattern!

Thực ra, if/else không có tội. Cái vấn đề thực sự về mặt thiết kế là: Chúng ta đã để cho tất cả mọi sự thay đổi (change) đổ dồn về một điểm duy nhất trong hệ thống.

Và đây là lúc tư duy về Design Pattern xuất hiện.

Nhiều người lầm tưởng Design Pattern là mấy cái công thức khô khan trong sách (như cuốn GoF) để đem ra khè nhau lúc phỏng vấn. Không phải đâu! Design pattern là kết quả tất yếu khi chúng ta bắt đầu suy nghĩ về tương lai, rèn luyện một tư duy để chống lại sự bào mòn của business change.

Tư duy đó bắt nguồn từ một câu hỏi duy nhất: "Nếu cái yêu cầu y hệt thế này (thêm cổng thanh toán mới) nó xảy ra thêm 5 lần nữa, thì chuyện gì sẽ xảy ra?"

Thay vì cắm đầu giải quyết vấn đề trước mắt bằng if/else, chúng ta ngẩng lên nhìn về tương lai. Và khi trả lời câu hỏi đó, một giải pháp sẽ hiện ra rất tự nhiên.

Trong trường hợp thanh toán này, đó chính là Strategy Pattern.

Mỗi phương thức thanh toán giờ đây được tách ra thành một chiến lược (Strategy) riêng, một module/class độc lập.

Muốn thêm Momo? Thêm class Momo. Muốn thêm VNPay? Thêm class VNPay. Code cũ không bị đụng chạm dù chỉ một dấu phẩy (Tuân thủ nguyên lý Open/Closed Principle - OCP).

Hãy nhớ lấy điều này: Không ai tự nhiên rảnh rỗi "chọn" dùng Strategy Pattern cả. Chính cái áp lực thay đổi liên tục của business đã ép chúng ta phải suy nghĩ theo cách đó.

3. Lằn ranh giữa Junior và Senior

Sự khác biệt cốt lõi khi đối mặt với sự thay đổi (change) nằm ở đây:

  • Một bạn Junior thường sẽ hỏi: "Case này dùng pattern nào bây giờ anh?" -> Tức là họ đi tìm một cái "bùa chú" có sẵn để ốp vào code.
  • Một Senior sẽ hỏi: "Cái change tiếp theo nó sẽ đánh vào đoạn nào?" -> Họ không đi tìm giải pháp đóng hộp, họ đi tìm và dự đoán hướng đi của dòng chảy sự thay đổi.

Design pattern không phải là một bộ sưu tập để học thuộc lòng. Nó là một phản xạ tự nhiên được hình thành khi chúng ta quan sát thấy change đang lặp đi lặp lại theo một quy luật nào đó.

4. Nhưng khoan đã... Pattern nào rồi cũng sẽ có "Điểm gãy"

Tìm ra được Strategy Pattern mới chỉ là bước đầu. Bởi vì mọi pattern, dù xịn đến mấy, cũng có điểm giới hạn của nó. Việc của chúng ta là phải biết cái "điểm gãy" đó nằm ở đâu.

Ví dụ tiếp với Strategy Pattern: Nó cực kỳ mạnh trong việc tách biệt các hành vi tính toán độc lập. Nhưng nó lại không quan tâm đến thứ tự xử lý, trạng thái hiện tại của hệ thống, hay các workflow phức tạp.

Một ngày, Business lại yêu cầu: "Em ơi, nếu đơn hàng đang ở trạng thái 'Chờ xử lý' thì cho thanh toán mọi hình thức. Nhưng nếu đơn hàng đã chuyển sang 'Đang giao', thì chỉ cho thanh toán bằng Tiền mặt (COD) thôi nhé!"

Bùm! Ngay lúc này, hành vi thanh toán không còn độc lập nữa. Nó đã bị trói buộc vào Trạng thái (State) của đơn hàng. Strategy Pattern bắt đầu có dấu hiệu bị gãy.

Đây chính là lúc chúng ta bước vào một cuộc đối đầu kinh điển trong kiến trúc phần mềm: Strategy Pattern vs. State Pattern. Nếu dùng sai pattern ở thời điểm giao thời này, code của bạn không những không tốt lên mà còn trở thành một mớ rác rối rắm gấp đôi cái file if/else lúc đầu.

Lời kết

Thông điệp cuối cùng mà mình muốn chốt lại với anh em là thế này:

Design pattern không tồn tại để làm cho một lập trình viên trông có vẻ "nguy hiểm" hay code trông "thượng đẳng" hơn.

Nó tồn tại với một sứ mệnh duy nhất: Để hệ thống của chúng ta không sụp đổ khi sự thay đổi (change) ập đến.

Chỉ đơn giản vậy thôi. Lần tới, trước khi gõ chữ if, hãy dừng lại 3 giây và tự hỏi: "Liệu mình có đang đào một cái hố cho chính mình 6 tháng sau không?"


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí