The WET codebase

Trong thế giới lập trình, có một câu thần chú phổ biến: “Đừng Lặp Lại Chính Mình” hay DRY (Don’t Repeat Yourself). Chúng ta thường được dạy rằng việc sao chép code là rất tệ, và chúng ta nên luôn cố gắng tạo ra các giải pháp thanh lịch, có thể tái sử dụng. Tuy nhiên, Dan Abramov giới thiệu một góc nhìn khác trong bài nói của anh ấy – ý tưởng về việc lãng phí một chút thời gian, hay cái mà anh ấy gọi là một “WET” codebase (Write Everything Twice). Lãng phí thời gian thực ra lại tốt!

DRY vs WET codebase

dry

Nguyên tắc DRY khuyên các nhà phát triển không nên lặp lại mọi thứ và tạo ra code mô-đun thông minh. Điều này rất logic; tại sao phải viết đi viết lại cùng một thứ khi bạn có thể tạo ra một trừu tượng (abstraction) thông minh, đúng không? Vâng, đó là nơi mà tình huống khó khăn bắt đầu.

Sử dụng DRY là tốt, nhưng đừng làm quá. Nếu bạn cố gắng quá mức để loại bỏ mọi sự lặp lại và làm cho mọi thứ trở nên quá phức tạp, thoạt đầu có vẻ tuyệt vời. Tuy nhiên, như Dan nói, việc quá tập trung vào điều này có thể làm cho code của bạn không chỉ đơn giản mà còn thực sự khó hiểu.

Thay vì háo hức cố gắng loại bỏ mọi sự trùng lặp, anh ấy gợi ý rằng đôi khi chúng ta nên lùi lại một bước, từ bỏ nỗi ám ảnh DRY và đầu tư một chút thời gian để hiểu thấu đáo vấn đề trong tay.

Trừu tượng hóa là một khái niệm cốt lõi trong lập trình cho phép chúng ta quản lý sự phức tạp thông qua việc chia nhỏ các vấn đề thành các thành phần có thể tái sử dụng. Tuy nhiên, giống như nhiều nguyên tắc mạnh mẽ khác, nó cũng mang lại những rủi ro nếu không được áp dụng cẩn thận.

Trong bài viết này, tôi sẽ giải thích cách các thay đổi nhỏ, tăng dần có thể khiến các trừu tượng dần dần “phình to” theo thời gian thông qua một ví dụ thực tế. Tôi cũng sẽ đi sâu vào những rủi ro cụ thể mà điều này gây ra và chia sẻ những cách thực tế bạn có thể áp dụng để giúp phát hiện ra sự suy giảm trừu tượng trước khi nó bóp nghẹt codebase của bạn.

Bloating Abstraction (Trừu Tượng Hóa Phình To)

Dan đã chia sẻ một câu chuyện từ những năm đầu sự nghiệp của anh ấy, khi anh ấy rơi vào cái bẫy của việc trừu tượng hóa code quá mức thông qua những thay đổi nhỏ nhưng có ý tốt.

Hai module chứa logic bất đồng bộ tương tự. Để tránh trùng lặp, điều này đã được trừu tượng hóa. Sau đó, một phiên bản đồng bộ cũng được yêu cầu. Thay vì sao chép logic, một flag đã được thêm vào để hỗ trợ cả hai trường hợp.

Sau đó, các vấn đề nhỏ như lỗi đã phát hiện ra những khác biệt nhỏ được vá bằng logic điều kiện (Conditional logic). Theo thời gian, sự trừu tượng tích lũy các trường hợp đặc biệt cho đến khi mục đích ban đầu của nó bị che khuất.

Điều này cho thấy cách các trừu tượng nhằm giảm sự trùng lặp có thể vô tình trở nên quá “chung chung” thông qua một loạt các sửa đổi hợp lý. Mỗi thay đổi đều có ý nghĩa riêng, nhưng tập hợp lại sẽ làm phình to sự trừu tượng.

ended

Example (Ví Dụ)

Để minh họa mô hình này, hãy xem xét một hàm async task có thể tái sử dụng đơn giản:

function asyncTask(data) {
  // async logic
}

Thêm một phiên bản đồng bộ thông qua một flag có vẻ như là một thay đổi nhỏ:

function task(data, async=true) {
  if (async) {
    // async logic
  } else { 
    // sync logic
  }
}

Sau đó, một tùy chọn định dạng được thêm vào vì một số dữ liệu là XML:

function task(data, async=true, format='json') {

  if (format === 'xml') {
    // xml logic
  }

  // existing logic
}

Điều này cho thấy cách các sửa đổi có ý tốt tích lũy, làm sai lệch mục đích ban đầu của một trừu tượng theo thời gian.

Cost of Abstraction (Cái Giá Của Trừu Tượng Hóa)

Accidental Coupling (Liên Kết Ngẫu Nhiên) - Code được trừu tượng hóa quá mức tạo ra các phụ thuộc không mong muốn giữa các module, hạn chế tính linh hoạt của việc tái cấu trúc code (code refactoring). Hãy tưởng tượng bạn có một ngôi nhà với các phòng phụ thuộc quá nhiều vào nhau. Nếu bạn thay đổi điều gì đó trong một phòng, nó sẽ ảnh hưởng bất ngờ đến một phòng khác, khiến việc cải tạo trở nên khó khăn mà không gây ra vấn đề ở những nơi khác.

Additional Indirection (Gián Tiếp Bổ Sung) - Các lớp trừu tượng giới thiệu sự phức tạp không cần thiết, khiến code khó hiểu và bảo trì hơn do có quá nhiều cấp độ chuyển hướng. Hãy nghĩ đến việc xây dựng một tòa tháp với nhiều tầng không cần thiết. Việc leo lên xuống trở nên khó hiểu, giống như việc điều hướng qua code trừu tượng quá mức trở nên khó hiểu và tốn nhiều thời gian hơn.

Inertia (Quán Tính) - Trừu tượng hóa cũng khiến việc thay đổi mọi thứ trong code của bạn trở nên khó khăn. Điều này không chỉ là về công nghệ, mà còn là về cách mọi người làm việc cùng nhau. Điều thường xảy ra là, bạn bắt đầu với một ý tưởng để làm cho mọi thứ đơn giản hơn bằng cách sử dụng một khái niệm trừu tượng. Theo thời gian, nó trở nên ngày càng phức tạp. Tuy nhiên, không ai thực sự có thời gian hoặc can đảm để sửa chữa hoặc đơn giản hóa sự phức tạp này, đặc biệt nếu bạn mới tham gia vào nhóm. Bạn có thể nghĩ rằng việc sao chép và dán code dễ dàng hơn, nhưng trước hết, bạn có thể không biết chính xác cách thực hiện vì code không quen thuộc. Thứ hai, bạn không muốn bị coi là người đề xuất các phương pháp thực hành tồi. Ai muốn được biết đến là người đề xuất sao chép và dán code? Bạn nghĩ bạn sẽ là một phần của nhóm đó trong bao lâu nếu đó là cách tiếp cận của bạn?

Quá nhiều thứ trừu tượng trong code gây ra các vấn đề như mọi thứ bất ngờ phụ thuộc vào nhau, khiến code trở nên phức tạp và khó thay đổi. Nó giống như mang một gánh nặng khiến code của bạn chậm chạp và khó làm việc.

Abstract Responsibly (Trừu Tượng Hóa Có Trách Nhiệm)

Việc nắm lấy trừu tượng hóa có trách nhiệm bao gồm một số phương pháp chính, như Dan đã nhấn mạnh:

Test Concrete Code (Kiểm Tra Code Cụ Thể): Thay vì tập trung vào cách code hoạt động đằng sau hậu trường, hãy kiểm tra những gì nó thực sự đạt được hoặc thực hiện. Ví dụ: nếu bạn đang xây dựng một ứng dụng ngân hàng, hãy kiểm tra xem chức năng chuyển tiền có hoạt động như dự định hay không, bất kể nó được triển khai nội bộ như thế nào.

Delay Adding Layers (Trì Hoãn Việc Thêm Lớp): Tránh làm cho mọi thứ trở nên quá phức tạp bằng cách thêm các lớp trừu tượng bổ sung quá sớm. Hãy đợi cho đến khi bạn thấy các mô hình hoặc sự lặp lại rõ ràng trước khi trừu tượng hóa mọi thứ.

Be Ready to Inline It (Sẵn Sàng Nội Tuyến Hóa Nó): Đôi khi, khi code trở nên khó hiểu do có quá nhiều trừu tượng, hãy chuẩn bị nội tuyến hóa logic để duy trì sự đơn giản và rõ ràng.

Các quy tắc này rất cần thiết để đảm bảo rằng có sự cân bằng phù hợp giữa các lớp đơn giản hóa và trừu tượng. Điều này đảm bảo rằng code vẫn đơn giản để làm việc và có thể được thay đổi dễ dàng khi dự án phát triển. Nó giống như xây dựng một thứ gì đó mà mỗi bộ phận đều có một công việc mà không làm cho mọi thứ trở nên quá phức tạp, vì vậy bạn có thể dễ dàng thay đổi mọi thứ khi cần thiết.

Java ecosystem’s Over-Abstraction (Trừu Tượng Hóa Quá Mức Của Hệ Sinh Thái Java)

stacktrace

Bạn đã bao giờ cố gắng sửa một vấn đề trong Java và cuối cùng thấy các stack trace dài như cả một cuốn sách chưa?

Trong bối cảnh các ngôn ngữ lập trình, Java từ lâu đã được ca ngợi về tính mạnh mẽ và linh hoạt của nó. Tuy nhiên, sức mạnh cũng có thể trở thành một con dao hai lưỡi trong trường hợp trừu tượng hóa quá mức, dẫn đến sự phức tạp quá mức.

Ví dụ, việc xử lý các giao thức HTTP trong Java bằng cách sử dụng Servlets. Mặc dù Servlets là nền tảng trong Java để xây dựng các ứng dụng web, nhưng việc triển khai của chúng thường giới thiệu một lớp trừu tượng rộng lớn có thể làm phức tạp những gì lẽ ra phải là một quy trình đơn giản.

Hơn nữa, Java Servlets (và hệ sinh thái của nó) thường khuyến khích một cách tiếp cận phân lớp cao, trong đó logic được ẩn sau nhiều trừu tượng. Mặc dù trừu tượng hóa rất hữu ích để đối phó với sự phức tạp, việc phân lớp quá mức có thể làm cho luồng code thực tế không rõ ràng và ngăn cản các nhà phát triển hiểu những gì họ đang làm.

Java thường tìm cách tạo ra code có thể tái sử dụng và bảo trì. Tuy nhiên, nó có thể chứng tỏ là khá phức tạp khi xử lý những thứ đơn giản nhất như HTTP thông qua Servlets vì sự phức tạp quá mức của các lớp. Nó cũng minh họa thực tế là việc làm cho mọi thứ trở nên phức tạp ngay cả đối với các tác vụ tầm thường có thể gây khó khăn cho các nhà phát triển.

Go’s Code Generation (Sinh Code Của Go)

Nguyên tắc DRY đã là một ánh sáng dẫn đường trong sự phát triển của phần mềm trong nhiều năm nay. Tính chính thống này đã bắt đầu bị thách thức bởi các ngôn ngữ và framework hiện đại, chẳng hạn như Go. Họ thích gen code hơn là ẩn logic thông qua nhiều cấp độ trừu tượng (Java kiểu cũ).

Go nổi bật vì sự đơn giản và tập trung vào sự rõ ràng. Câu nói nổi tiếng “Clarity is better than cleverness” (Rõ ràng tốt hơn thông minh) bắt nguồn từ Basics of the Unix Philosophy được những người tạo ra Go tán thành. Triết lý này mở rộng sang cách tiếp cận của Go đối với tổ chức và trùng lặp code.

Trong Go (và hệ sinh thái của nó), các nhà phát triển thường chọn cách tiếp cận gen code thay vì dựa vào các trừu tượng rộng lớn. Điều này có nghĩa là gen code một cách rõ ràng cho các trường hợp sử dụng cụ thể, ngay cả khi nó liên quan đến việc sao chép một số phần nhất định. Quyết định này dựa trên quan điểm rằng code rõ ràng dễ đọc hơn, dễ hiểu hơn và dễ quản lý hơn.

Sự thành công và việc áp dụng Go của các công ty công nghệ lớn đã ảnh hưởng đến quan điểm của ngành về việc trùng lặp code. Mặc dù tầm quan trọng của trừu tượng hóa và mô-đun hóa không bị bác bỏ, nhưng cách tiếp cận của Go nhấn mạnh rằng có những kịch bản mà code trùng lặp, rõ ràng có thể có lợi hơn.

goclear

*Xem Opening keynote: Clear is better than clever - GopherCon SG 2019

Tóm lại, trừu tượng hóa là một con dao hai lưỡi. Nó nhân lên cả sức mạnh và sự nguy hiểm trong code. Bằng cách tuân theo các phương pháp phát hiện và giải quyết tình trạng phình to sớm, chúng ta có thể tận dụng lợi ích của trừu tượng hóa và tránh sự phức tạp không mong muốn. Chấp nhận một chút “WETness” có thể khiến bạn nhận ra rằng không phải tất cả các trùng lặp code đều xấu. Trên thực tế, nó có thể là một giải pháp thực dụng trong trường hợp các trường hợp cụ thể. Dành một chút thời gian để thực hiện các hoạt động có vẻ dư thừa có thể dẫn đến một codebase đơn giản và dễ quản lý hơn, có thể được hiểu bởi bạn trong tương lai và các đồng nghiệp của bạn.

Refs: