- Bài viết giải thích những giới hạn và vấn đề của ngôn ngữ Go mà tác giả cảm nhận được sau nhiều năm sử dụng rồi chuyển sang Java
- Bài viết đưa ra góc nhìn rằng đặc tính đơn giản và nhàm chán (
boring) của Go có thể không phải là ưu điểm mà lại là nhược điểm
- Triết lý của Go: đội ngũ thiết kế Go tại Google nhấn mạnh sự đơn giản và tính hạn chế, nhưng điều đó lại gây ra nhiều công việc lặp đi lặp lại mà người dùng phải tự giải quyết
1. Việc Go “không thú vị” có thể là một nhược điểm
- Lập luận của Russ Cox:
- Nhấn mạnh rằng việc Go “nhàm chán (
boring)” là một ưu điểm
- Chỉ có một vòng lặp là
for, và không có sẵn các tính năng như filter, map, reduce
- Việc thiếu nhiều tính năng nâng cao vốn có ở phần lớn ngôn ngữ khác được xem là một phần của sự đơn giản
- Ý kiến người dùng Reddit:
- Ranh giới giữa “nhàm chán” và “mạnh mẽ” là khá mơ hồ
- Cho rằng việc Go thiếu các tính năng cơ bản rồi sớm muộn cũng sẽ phải bổ sung vào ngôn ngữ
- Phụ thuộc vào gói bên thứ ba:
- Gói
samber/lo thường được dùng để bù đắp các tính năng còn thiếu:
- Bao gồm các chức năng thiết yếu như filter, map, tìm kiếm
- Có 18.1k sao trên GitHub và được dùng trong hơn 12.6k dự án
- Một số chức năng đã được thêm vào gói
slices, nhưng vẫn còn thiếu về mặt tính năng
- Sự bất mãn của tác giả:
- Bị buộc phải viết các vòng lặp lặp đi lặp lại
- Khó xử lý gọn gàng các tác vụ như filter và map
- Có thể tách ra thành các phương thức receiver riêng, nhưng làm giảm sự sạch sẽ của mã
- Sự đơn giản của Go trong nhiều trường hợp là ưu điểm, nhưng việc thiếu các tính năng tiện dụng cơ bản có thể trở thành nhược điểm làm giảm năng suất và độ dễ đọc của mã
2. Cản trở các nguyên tắc Clean Code
- Vấn đề xử lý lỗi:
- Phần lớn hàm đều trả về thêm giá trị
error:
- Phải lặp đi lặp lại mẫu
if err != nil
- Trong quá trình dọn dẹp mã, vấn đề này lại khiến mã trở nên phức tạp hơn
- Ngay cả mã HTTP handler trong dự án đơn giản cũng tăng lên hơn 20 dòng
- Trong khi mục tiêu ban đầu là giữ ở khoảng 4 dòng
- Từng thất vọng đến mức cân nhắc dùng
panic() cùng middleware phục hồi để xử lý lỗi
- Khuyến nghị dùng tên ngắn:
- Khuyến khích dùng tên ngắn cho biến, phương thức, hàm:
- Những tên như
c, a không rõ mang nghĩa gì
- Ví dụ:
c là Command, Controller, Argument hay Amendment?
- Dùng tên dài có thể rõ ràng hơn, nhưng triết lý của Go lại chuộng tên ngắn
- Điều này dẫn đến các cuộc tranh luận bất tận trong review code của nhóm, chẳng hạn về tên phương thức test
- Triết lý của Go nhấn mạnh sự ngắn gọn và đơn giản của mã, nhưng kết quả lại có thể tạo ra sự phức tạp và kém hiệu quả đi ngược với nguyên tắc clean code
3. Triết lý ngôn ngữ cố ý nhỏ gọn và văn hóa DIY
- Thiếu tính năng mặc định:
- Việc triển khai một HTTP handler đơn giản là dễ, nhưng nếu cần middleware cơ bản (ví dụ: exponential backoff, thiết lập cross-site) thì phải đi tìm nhiều gói khác nhau
- Khó chắc chắn rằng các gói đó (1) còn được bảo trì hay không, (2) có hoạt động đúng như mong đợi hay không
- Gia tăng công việc lặp lại:
- Triết lý thiết kế muốn giữ Go đơn giản lại dẫn đến việc buộc lập trình viên phải “phát minh lại bánh xe”
- Ví dụ: ngay cả tính năng filter đơn giản cũng có thể phải tự triển khai
- Hệ sinh thái gói còn non:
- Nhiều dự án GitHub bị bỏ hoang hoặc chỉ phát hành vài phiên bản
- Dù việc so sánh với .NET/Java có thể không công bằng vì Go là ngôn ngữ trẻ hơn, nhưng trên thực tế độ ổn định và mức độ trưởng thành của hệ gói Go vẫn còn thiếu
- Giới hạn của ORM:
- Gói ORM chủ đạo của Go là Gorm thua kém Hibernate hay Entity Framework về mặt tính năng
- Có các hành vi kỳ lạ và vấn đề thiếu tài liệu
- Phản ứng từ cộng đồng Go: “Go không cần ORM, hãy tự làm lấy!”
- Sự đơn giản của Go có thể là ưu điểm tùy dự án và nhóm, nhưng việc thiếu các tính năng có sẵn lại có thể ảnh hưởng tiêu cực đến năng suất và trải nghiệm phát triển
4. Go không thực sự chỉ có một cách làm
- Hiểu lầm về tính nhất quán và đồng nhất:
- Table test
- Sử dụng test suite như
stretchr/testify (được dùng trong 557k dự án)
- Viết các subtest tùy biến bên trong table test
- Điều này cho thấy có khoảng cách giữa triết lý “cách làm thống nhất” của Go và thực tế
- Gây xung đột trong nhóm:
- Các cuộc thảo luận về phong cách test và cách triển khai trong nhóm lại tăng lên
- Ngay cả triết lý và chính đội ngũ thiết kế Go cũng thiếu nhất quán:
- Ví dụ: sự không thống nhất trong cách đặt tên getter
- Từ chối tính năng và phụ thuộc gói:
- Đội Go từ chối thêm tính năng assertion và bị chỉ trích vì thái độ quy lỗi cho sự thiếu sót của lập trình viên
- Kết quả là để dùng tính năng cần thiết, người ta lại phải cài thêm một gói khác bằng
go get
- Go hướng tới sự đơn giản và đồng nhất, nhưng trên thực tế tồn tại nhiều cách triển khai khác nhau cùng các cuộc tranh luận đi kèm, và sự mơ hồ trong triết lý thiết kế ngôn ngữ càng làm vấn đề trầm trọng hơn
5. Debug trong Go không thú vị
- Không thể đánh giá biểu thức khi debug:
- Trong một phiên debug, không thể đánh giá biểu thức hay kiểm tra biểu diễn chuỗi tùy biến của đối tượng
- Khó nắm bắt rõ trạng thái của đối tượng tại runtime
- Stack trace và log thiếu trực quan:
- Khi test quy mô lớn thất bại (ví dụ: chạy hàng nghìn test trong CI), stack trace và log trở nên rối rắm
- Kết quả là việc debug khó hơn và năng suất giảm đi
- Trải nghiệm debug kiểu C:
- Chuỗi công cụ debug của Go vận hành theo kiểu dựa trên C:
- Mang lại trải nghiệm debug nguyên thủy giống C
- Không thân thiện với lập trình viên
- So sánh với Rust:
- Rust cải thiện các giới hạn của Go:
- Cung cấp thông tin lỗi rõ ràng và hữu ích
- Thông báo lỗi kèm gợi ý sửa chính xác
- Trải nghiệm debug của Go dựa trên triết lý thiết kế ưu tiên tạo ra binary tối ưu, nhưng điều đó dẫn tới việc phải hy sinh trải nghiệm lập trình viên. Trong môi trường coi trọng hiệu quả debug, nên cân nhắc các ngôn ngữ thay thế
Tóm tắt: Mức độ phù hợp và giới hạn của Go
- Ưu điểm của bộ công cụ tích hợp trong Go:
- Cung cấp sẵn toolchain cơ bản cho quản lý gói, test và theo dõi hiệu năng
- Có thể dùng ngay mà không cần cấu hình riêng, giúp đơn giản hóa việc thiết lập môi trường phát triển ban đầu
- Giới hạn:
- “Mã nhàm chán” và công việc lặp lại:
- Toolchain của Go tuy hữu dụng nhưng lại buộc người viết mã phải làm nhiều công việc lặp đi lặp lại (plumbing code)
- Ví dụ: cú pháp đơn điệu và tính năng hạn chế làm giảm hứng thú khi làm việc
import cycle not allowed:
- Không cho phép phụ thuộc vòng (import cycle) trong test
- Khi làm domain-driven design (DDD), điều này làm tăng độ phức tạp do các ràng buộc cấu trúc
- Sự phụ thuộc vào cơ chế nhúng
struct:
- Cơ chế nhúng
struct kỳ lạ và hạn chế gây nhiều khó khăn khi sử dụng
- Lĩnh vực phù hợp:
- Phù hợp cho phát triển hạ tầng:
- Các công cụ cấp hệ thống như Docker, Drone, Hugo được viết bằng Go
- Hữu ích cho phát triển server nhẹ và ứng dụng CLI
- Lĩnh vực không phù hợp:
- Phát triển ứng dụng doanh nghiệp phức tạp (ví dụ: hệ thống ERP):
- Triết lý ngôn ngữ và công cụ hạn chế khiến việc quản lý logic nghiệp vụ quy mô lớn kém hiệu quả
- Go mang lại hiệu quả rất cao cho một số loại công việc nhất định, đặc biệt là hạ tầng, nhưng không phải là công cụ phù hợp cho các ứng dụng có miền nghiệp vụ phức tạp. Ngay cả khi CTO thiên về stack công nghệ của Google, việc lựa chọn công nghệ vẫn cần được cân nhắc cẩn trọng
5 bình luận
Giá mà Rust có
?thì có lẽ giờ đã đỡ hơn nhiều...Khi dùng Go, tôi nhận ra trước giờ mình đã xử lý lỗi một cách ngầm ẩn nhiều đến mức nào.
Tất nhiên, việc xử lý lỗi tại một điểm có thể trông gọn gàng hơn về mặt cấu trúc, nhưng tôi nghĩ rằng việc thể hiện rõ ràng đây là một thao tác có thể trả về lỗi lại giúp mình viết code theo cách an toàn hơn.
if err != nil {}đúng là hơi phiền thật. Tôi cũng đồng ý với những nhược điểm đã được chỉ ra. Dù vậy, nếu hiểu rõ định hướng mà ngôn ngữ này theo đuổi và suy nghĩ xem nên tận dụng tốt hơn những điểm nào, thì có lẽ vẫn có thể khai thác nó hiệu quả hơn bất chấp các nhược điểm đó. Nó giống C nhưng có GC, hỗ trợ cả generics dù còn hạn chế, lại còn cross-compile được nữa! Nhìn theo cách này thì chẳng phải đây vẫn là một ngôn ngữ rất hời sao?Khi chuyển từ Java sang Go, lúc đầu tôi cũng có cảm giác khá giống như vậy.
Bây giờ tôi còn thích Go đến mức cảm thấy quãng thời gian dùng Java thật đáng tiếc. Việc nói rằng nó không phù hợp với các ứng dụng nghiệp vụ phức tạp khiến tôi nghĩ rằng ứng dụng đó đã không được cân nhắc đủ kỹ để đơn giản hóa hệ thống.
Ý kiến trên Hacker News
Vấn đề phát sinh khi các lập trình viên Java cố ép Go theo phong cách Java
Nhiều lập trình viên cố gắng trừu tượng hóa quá sớm
Thư viện chuẩn của Go lớn, nhưng không lớn đến mức có thể làm được mọi thứ
Có những thách thức còn lớn hơn cả việc chọn ngôn ngữ lập trình
Khó hiểu được vì sao mọi người lại thích Go
Việc đội ngũ nòng cốt của Go đảo ngược các quyết định sai lầm gây cảm giác thất vọng
Go có những vấn đề giống UNIX