Một cuộc tranh luận đang gia tăng trong cộng đồng lập trình xoay quanh một khuyết điểm cơ bản trong cách các ngôn ngữ lập trình hiện đại xử lý các loại thông tin khác nhau. Cuộc thảo luận này làm nổi bật cách các ngôn ngữ buộc các nhà phát triển phải đưa ra những thỏa hiệp khó xử khi biểu diễn dữ liệu đơn giản so với các đối tượng phức tạp có hành vi.
Vấn đề cốt lõi xuất phát từ việc các ngôn ngữ xử lý mọi thứ theo cùng một cách, dù đó là một con số đơn giản không bao giờ thay đổi hay một thành phần hệ thống phức tạp duy trì trạng thái theo thời gian. Cách tiếp cận một-kích-cỡ-phù-hợp-tất-cả này tạo ra sự phức tạp không cần thiết và dẫn đến những quyết định kiến trúc kém trong toàn ngành công nghiệp phần mềm.
Câu Chuyện Về Hai Mô Hình Lập Trình
Cộng đồng lập trình đã xác định hai cách tiếp cận riêng biệt mà các ngôn ngữ có xu hướng ưa chuộng. Các ngôn ngữ hướng đối tượng như Java đẩy mọi thứ vào các đối tượng, thậm chí cả những giá trị đơn giản như số. Điều này tạo ra chi phí và sự phức tạp không cần thiết. Mặt khác, các ngôn ngữ hàm như Haskell xuất sắc trong việc xử lý dữ liệu thuần túy nhưng gặp khó khăn khi bạn cần các thành phần có trạng thái duy trì danh tính theo thời gian.
Cuộc thảo luận cộng đồng tiết lộ rằng C# đã đạt được tiến bộ đáng kể trong việc giải quyết sự phân chia này. Không giống như Java , C# phân biệt giữa các kiểu giá trị (struct) cho dữ liệu đơn giản và các kiểu tham chiếu (class) cho các đối tượng có danh tính. Lựa chọn thiết kế này, có mặt từ phiên bản đầu tiên của ngôn ngữ, cho phép các nhà phát triển đưa ra quyết định có ý thức về cách dữ liệu của họ nên hoạt động.
Cách tiếp cận của các ngôn ngữ lập trình đối với sự phân biệt Data/Object
- C: Phân tách rõ ràng với các kiểu giá trị (structs) so với các kiểu tham chiếu (classes) từ phiên bản 1
- Java: Cách tiếp cận mọi thứ đều là đối tượng, từ từ bổ sung các kiểu giá trị thông qua Project Valhalla
- Haskell: Hỗ trợ dữ liệu mạnh mẽ với các kiểu đại số, khả năng giống đối tượng hạn chế
- Scala: Case classes cố gắng kết nối khoảng cách giữa dữ liệu và đối tượng
- Erlang / Elixir: Nền tảng dữ liệu bất biến vững chắc với mô phỏng đối tượng dựa trên tiến trình
- Rust: Cố gắng cung cấp cả tính an toàn và hiệu suất với độ phức tạp tăng lên
Tác Động Thực Tế Lên Kiến Trúc Hệ Thống
Sự nhầm lẫn giữa dữ liệu và đối tượng đã ảnh hưởng đến các xu hướng công nghệ lớn theo những cách không ngờ tới. Phong trào NoSQL trở nên phổ biến một phần vì các cơ sở dữ liệu truyền thống gặp khó khăn với cấu trúc dữ liệu dạng cây, buộc các nhà phát triển phải tìm cách khắc phục các ràng buộc quan hệ. Tương tự, các API REST trở nên phổ biến vì chúng cho phép các hệ thống trao đổi dữ liệu thực tế dưới dạng JSON , thay vì phải đối phó với sự phức tạp của cách tiếp cận hướng đối tượng của SOAP .
Tôi không đổ lỗi cho ai vì một chút phấn khích phi lý sau khi được giải thoát khỏi ràng buộc đó. Thật tự do khi lưu trữ dữ liệu, phải không?
Cộng đồng chỉ ra rằng nhiều vấn đề kiến trúc dịch vụ xuất phát từ sự nhầm lẫn cơ bản này. Các nhà phát triển thường tạo ra các dịch vụ chỉ xáo trộn dữ liệu thay vì cung cấp hành vi có ý nghĩa, dẫn đến mô hình phản thiết kế dai dẳng của các dịch vụ CRUD mà các chuyên gia tư vấn đã cố gắng loại bỏ trong hơn một thập kỷ.
Cân Nhắc Về Hiệu Suất và Thực Tiễn
Cuộc tranh luận cũng mở rộng đến các tác động về hiệu suất. Các cấu trúc dữ liệu bất biến, được ưa chuộng bởi những người ủng hộ lập trình hàm, cung cấp đảm bảo an toàn và hoạt động tốt trong môi trường đa luồng. Tuy nhiên, chúng đi kèm với chi phí hiệu suất so với việc biến đổi tại chỗ được sử dụng trong các tình huống hiệu suất cao như hệ thống giao dịch và công cụ game.
Các ngôn ngữ như Erlang và Elixir chứng minh lợi ích của một nền tảng vững chắc với dữ liệu bất biến, nhưng chúng thiếu hệ thống kiểu tĩnh mà nhiều nhà phát triển coi là thiết yếu cho các dự án quy mô lớn. Trong khi đó, các ngôn ngữ mới hơn như Rust cố gắng cung cấp cả an toàn và hiệu suất, mặc dù với sự phức tạp gia tăng mà các nhà phát triển phải thành thạo.
So sánh đặc điểm giữa Dữ liệu và Đối tượng
Khía cạnh | Dữ liệu | Đối tượng |
---|---|---|
Tính bằng nhau | Dựa trên giá trị: bất kỳ số 1 nào cũng bằng 1 | Dựa trên định danh: số 1 này ≠ số 1 kia |
Sao chép | Sao chép tự do, chỉ là các byte | Serialization tạo ra định danh mới |
Tính có thể thay đổi | Bất biến theo bản chất | Thường có thể thay đổi để quản lý trạng thái |
Cấu trúc bên trong | Được hiển thị, tuân thủ schema | Được đóng gói với quyền truy cập có kiểm soát |
Tính mở rộng | Biến thể cố định, hàm không giới hạn | Thao tác cố định, biến thể có thể mở rộng |
Con Đường Phía Trước
Cộng đồng lập trình ngày càng nhận ra rằng thiết kế ngôn ngữ tương lai nên hỗ trợ rõ ràng cả hai mô hình thay vì ép mọi thứ vào một mô hình. Các nhà phát triển cần các công cụ cho phép họ có ý thức lựa chọn giữa việc biểu diễn thứ gì đó như dữ liệu thuần túy hoặc như một đối tượng có hành vi và danh tính.
Một số ngôn ngữ đang phát triển theo hướng này. Java đang từ từ thêm các kiểu giá trị thông qua Project Valhalla , trong khi các case class của Scala cung cấp một điểm trung gian giữa dữ liệu thuần túy và các đối tượng đầy đủ. Tuy nhiên, những giải pháp này thường cảm thấy như những bản vá trên các hệ thống hiện có thay vì các nguyên tắc thiết kế cơ bản.
Cuộc thảo luận gợi ý rằng thiết kế ngôn ngữ lập trình tốt hơn có thể ngăn chặn nhiều vấn đề kiến trúc bằng cách làm cho việc lựa chọn dữ liệu-so-với-đối-tượng trở nên rõ ràng và hỗ trợ cả hai mô hình một cách bình đẳng. Điều này sẽ giúp các nhà phát triển đưa ra quyết định thiết kế có ý thức hơn và tránh sự nhầm lẫn hiện đang gây khó khăn cho kiến trúc phần mềm.
Tham khảo: Data, objects, and how we're railroaded into poor design