Tôi có được nghe hai lần câu chuyện ở một công ty lớn ở Việt Nam, một Solution Architect đã từ chối một pull request của đồng nghiệp với lý do các thay đổi không tuân theo nguyên tắc Đóng/Mở. Tôi nghĩ rằng đã có một yêu cầu từ khách hàng và lập trình viên đã sửa đổi một lớp sẵn có để thực hiện yêu cầu ấy. SA đã từ chối vì theo SA, không được sửa class sẵn có mà phải tìm cách mở rộng nó. Tôi không chắc đây là một câu chuyện có thật, nhưng việc nghe được nó hai lần từ hai người khác nhau, tôi nghĩ cũng có khả năng. Tại sao tôi lại không tin vào câu chuyện này? Nó có những lý do sau:
- Khá là kì quặc khi từ chối một PR bằng lý do nó không tuân theo SOLID. Chúng ta là những kĩ sư, và chúng ta nói chuyện bằng giải pháp, ví dụ. Dùng một luận điểm có tính lý thuyết để bác bỏ một PR là thiếu thuyết phục.
- Việc có thể mở rộng một chức năng mà không sửa đổi code cũ hay không phụ thuộc vào nhiều yếu tố. Chúng ta vẫn thường sửa đổi code là chính. Đa số các mã nguồn chúng ta tiếp xúc trong thực tế không hề tốt, không có sẵn các phương án để mở rộng.
- Nguyên tắc Đóng/Mở không đứng một mình. Trừ Đơn nhiệm, các nguyên tắc khác phụ thuộc vào nhau và vào Đơn nhiệm. Bạn sẽ đạt được cả 5 nguyên tắc SOLID hoặc không đạt được gì. Dẫn chứng chỉ một nguyên tắc một lần nữa rất thiếu thuyết phục.
Tôi muốn các bạn hiểu một điều:
Đóng/Mở là nguyên tắc về thiết kế hơn là nguyên tắc viết mã
Tức là bạn đạt được Đóng/Mở phải ngay khi bạn thiết kế mã, không phải lúc bạn có một yêu cầu từ khách hàng, và với kĩ năng đầy mình bạn sẽ mở rộng code cũ để thỏa mãn yêu cầu mới. Không hề có chuyện nó. Nếu bạn có thể mở rộng code cũ thì không phải do bạn, mà là do người đã thiết kế nên code cũ cho phép bạn làm điều ấy. Không phải bạn đạt được Đóng/Mở mà là người trước bạn đã đạt được Đóng/Mở. Tất nhiên không phải không có trường hợp những code khá đóng nhưng lập trình viên vẫn tìm ra cách để mở rộng. Những thành tựu ấy không mang tính hệ thống mà thường là trick, áp dụng cho trường hợp cụ thể, và chính trick ấy cũng trở nên khó mở rộng.
Một mã nguồn được thiết kế tốt thường thể hiện sẵn các khả năng và các giải pháp để mở rộng. Hoặc nếu chưa có sẵn giải pháp để mở rộng thì nó cũng dễ dàng cài đặt giải pháp để mở rộng, bởi nó đã đạt được SLID trong SOLID. Nhưng trong đa số trường hợp, giải pháp đã có sẵn cho bạn.
Một điều nữa mà có thể chúng ta nhầm lẫn về thay đổi mã nguồn. Không phải thay đổi nào cũng là mở rộng vì:
Các thay đổi đối với mã nguồn cũng phải tuân thủ theo Đơn nhiệm
Chúng ta cần xác định yêu cầu thay đổi có Đơn nhiệm không, tức là nó có độc lập với mã nguồn sẵn có hay không. Nếu nó độc lập - đơn nhiệm thì bạn có thể mở rộng. Nếu không chúng ta vẫn phải sửa đổi mã nguồn. Cùng xét quy trình xử lý đơn hàng:
Đơn hàng => Tính tổng giá => Nhập thông tin khách => Chọn phương thức thanh toán => Thanh toán => Giao hàng => Đóng.
Chúng ta có hai yêu cầu mới:
- Tính giảm giá/ khuyến mãi.
- Chọn phương thức giao hàng.
Yêu cầu Chọn phương thức giao hàng rõ ràng là đơn nhiệm, ta thêm nó vào sau bước Nhập thông tin khách.
Yêu cầu Tính giảm giá/ khuyến mãi lại không như thế. Đặt trước hay sau Tính tổng giá đều không hợp lý. Có những khuyến mãi dựa trên một sản phẩm, ta có thể tính trước. Có những khuyến mãi lại dựa trên tổng giá trị đơn hàng, ta lại phải tính sau. Chưa nói về nguyên tắc tính thuế nếu giá chưa bao gồm thuế. Chúng ta khó mà tách nó ra khỏi bước tính tổng giá. Khả năng cao nó sẽ là sửa đổi chứ không thể mở rộng và giữ nguyên mã nguồn cũ (Việc thiết kế để có thể mở rộng ở đây sẽ rất tinh vi và có thể là over-design so với sửa đổi).
Mặc dù trong thực tế, chúng ta không mấy quan tâm đến Đóng/Mở đâu, nhưng nếu bạn có để ý đến nó thì cũng không cần phải áp lực quá vì nếu có thể mở rộng thì chúng ta đã mở rộng được rồi, và nếu không thể mở rộng thì giải pháp cũng chỉ là sửa đổi cái có sẵn. Tôi nói thế không có nghĩa khuyến khích các bạn vô tư đi. Việc hiểu rằng chúng ta không thể mở rộng khác với việc cứ code bừa đi cho xong. Tôi chỉ mong lúc các bạn lăn tăn, các bạn sẽ có những lời này để thoát khỏi vướng mắc "Không được sửa đổi".
Hóa ra tôi lại đang bảo vệ việc sửa đổi khi nói về Đóng/Mở =))