Go vẫn chưa tốt
(blog.habets.se)- Nhiều quyết định trong thiết kế ngôn ngữ Go được đưa ra một cách không cần thiết hoặc phớt lờ kinh nghiệm đã có từ trước
- Vấn đề quản lý phạm vi của biến lỗi khiến việc đọc mã và truy tìm bug trở nên khó khăn hơn
- Tính hai mặt của nil, việc sử dụng bộ nhớ, tính khả chuyển của mã và nhiều khía cạnh khác cho thấy thiết kế thiếu trực quan và không khớp với thực tế
- Giới hạn của câu lệnh defer cùng cách xử lý ngoại lệ trong thư viện chuẩn khiến việc đảm bảo an toàn ngoại lệ trở nên khó khăn
- Các vấn đề tích lũy như quản lý bộ nhớ và xử lý UTF-8 chưa tốt đang ảnh hưởng tiêu cực đến chất lượng codebase Go trong dài hạn
Phê bình dài hạn đối với ngôn ngữ Go
- Như đã nêu trong các bài viết trước (Why Go is not my favourite language, Go programs are not portable), tôi đã chỉ ra nhiều vấn đề của ngôn ngữ Go trong hơn 10 năm
- Đặc biệt, những quyết định thiết kế không cần thiết vốn phớt lờ các thực tiễn tốt đã được biết đến ngày càng gây thất vọng hơn
Tính phi trực quan trong phạm vi của biến lỗi
- Cú pháp của Go mở rộng phạm vi của biến lỗi (
err) một cách không cần thiết, làm tăng khả năng phát sinh lỗi- Trong đoạn mã ví dụ, biến
errtồn tại xuyên suốt toàn bộ hàm và bị tái sử dụng, từ đó làm giảm độ dễ đọc và khả năng bảo trì của mã - Ngay cả lập trình viên dày dạn kinh nghiệm cũng có thể bị hiểu nhầm và tốn thời gian khi dò bug do vấn đề phạm vi này
- Cú pháp không cho phép một cách tự nhiên để giới hạn biến này trong phạm vi cục bộ phù hợp
- Trong đoạn mã ví dụ, biến
Hai dạng nil
- Trong Go tồn tại sự khó hiểu khi nil hoạt động khác nhau giữa kiểu interface và kiểu con trỏ
- Như ví dụ bên dưới, dù
s(con trỏ) vài(interface) đều được gán nil,s==ivẫn cho kết quả khác nhau, thể hiện hành vi thiếu nhất quán - Đây là kiểu vấn đề mà người ta thường muốn tránh trong xử lý null, cho thấy dấu vết của một thiết kế chưa được cân nhắc đủ kỹ
- Như ví dụ bên dưới, dù
Giới hạn về tính khả chuyển của mã
- Việc dùng chú thích cho biên dịch có điều kiện là cách làm rất kém hiệu quả xét về khả năng bảo trì và tính khả chuyển
- Nếu từng có kinh nghiệm xây dựng phần mềm portable thực sự, bạn sẽ thấy cách này phiền phức và dễ gây lỗi
- Kinh nghiệm được tích lũy trong lịch sử (tính khả chuyển của mã, các trường hợp thực tiễn) đã bị bỏ qua
- Xem chi tiết trong Go programs are not portable
Sự mơ hồ về quyền sở hữu của append
- Mối quan hệ quyền sở hữu giữa hàm
appendvà slice không rõ ràng, khiến hành vi của mã khó dự đoán- Qua ví dụ, khi một slice được
appendtrong hàmfoo, rất khó biết trước nó thực sự ảnh hưởng đến dữ liệu gốc ra sao - Số lượng các “quirk” của ngôn ngữ mà lập trình viên phải ghi nhớ cứ tăng lên, dễ dẫn đến sai sót
- Qua ví dụ, khi một slice được
Thiết kế của câu lệnh defer còn thiếu sót
- Go không hỗ trợ giải phóng tài nguyên một cách rõ ràng theo nguyên tắc RAII (Resource Acquisition Is Initialization)
- So với các cấu trúc quản lý tài nguyên có cấu trúc trong Java và Python, Go không cho thấy rõ tài nguyên nào nên được giải phóng bằng
defer - Như ví dụ về thao tác với tệp, lập trình viên còn phải tự xử lý cả vấn đề đóng hai lần, và thứ tự cũng như cách giải phóng đúng đắn đều không rõ ràng
- So với các cấu trúc quản lý tài nguyên có cấu trúc trong Java và Python, Go không cho thấy rõ tài nguyên nào nên được giải phóng bằng
Xử lý ngoại lệ trong thư viện chuẩn
- Go không hỗ trợ ngoại lệ (exception) tường minh, nhưng các tình huống ngoại lệ như
panicvẫn xảy ra- Trong một số trường hợp,
panicthậm chí không kết thúc hoàn toàn chương trình mà bị nuốt mất - Trong thư viện chuẩn (
fmt.Print, máy chủ HTTP, v.v.) có những mẫu xử lý bỏ qua ngoại lệ, khiến không thể đảm bảo an toàn ngoại lệ thực sự - Kết quả là việc viết mã an toàn trước ngoại lệ vẫn là điều bắt buộc, nhưng lại không thể trực tiếp sử dụng ngoại lệ
- Trong một số trường hợp,
Xử lý UTF-8 và chuỗi
- Ngay cả khi đưa dữ liệu nhị phân tùy ý vào kiểu
string, Go vẫn hoạt động mà không có xác thực đặc biệt nào- Bạn có thể gặp trường hợp tên tệp được tạo ra trước thời kỳ mã hóa UTF-8 bị âm thầm bỏ sót
- Dữ liệu quan trọng trong các tác vụ như sao lưu có thể bị mất, cho thấy một cách xử lý quá đơn giản và không phản ánh thực tế vận hành
Giới hạn của quản lý bộ nhớ
- Rất khó kiểm soát trực tiếp lượng RAM sử dụng, và độ tin cậy của GC (garbage collection) cũng có giới hạn
- Mức sử dụng bộ nhớ của Go tăng lên, kéo theo các vấn đề chi phí và hiệu năng trong dài hạn
- Trong môi trường nhiều instance hoặc container, các vấn đề về chi phí và khả năng mở rộng thực sự có thể phát sinh
Kết luận: đã từng có con đường tốt hơn
- Dù đã có sẵn những thiết kế ngôn ngữ được chứng minh là hiệu quả, Go vẫn quay lưng với chúng ở nhiều khía cạnh
- Khác với những vấn đề của các bản thiết kế Java thuở đầu, vào thời điểm Go ra mắt đã tồn tại những cách tiếp cận tốt hơn
Tài liệu tham khảo
- Uber: Data race patterns in Go
- FasterThanLime: Lies we tell ourselves to keep using Golang
- FasterThanLime: I want off Mr Golang’s wild ride
1 bình luận
Ý kiến Hacker News
Tôi đã dùng Go từ thời kỳ pre-1.0 ở gần như mọi công việc toàn thời gian. Nó đủ đơn giản để đồng đội học những điều cơ bản, và nhìn chung chạy ổn định. Khi cập nhật lên phiên bản Go mới nhất hầu như không có gì phải lo, và phần lớn tính năng hữu ích đều có sẵn mặc định. Tốc độ biên dịch nhanh là một điểm hấp dẫn. Xử lý song song hơi rắc rối một chút, nhưng nếu bỏ thời gian thì nó lại rất hợp để biểu đạt luồng dữ liệu. Hệ thống kiểu phần lớn là tiện dụng, dù đôi khi hơi dài dòng. Nhìn chung đây là một công cụ đáng tin cậy. Nhưng tôi cũng đồng cảm với nhiều điểm phê bình được nêu trong bài. Rõ ràng Go có những chỗ mà các lập trình viên thế hệ cũ quá bám vào nguyên tắc nên bỏ lỡ sự tiện lợi thực dụng. Tất nhiên đó chỉ là cảm nhận của tôi, và tôi cũng nghĩ nếu sửa hết mọi nhược điểm thì chưa chắc nó đã tốt hơn bây giờ. Tôi cũng muốn nói thêm rằng vài năm gần đây tôi có cảm giác cộng đồng cởi mở hơn trong việc sửa những điểm kỳ quặc. Có thời điểm tôi không thể tưởng tượng nổi việc thêm generics hay custom iterator. Còn những chỉ trích về RAM và tính di động thì có phần giống bất mãn cá nhân hơn. Sẽ tốt nếu được cải thiện, nhưng GC cực hiếm khi gây ra vấn đề nghiêm trọng trong đa số chương trình, và việc debug cũng không quá khó. Hơn nữa, Go hỗ trợ gần như mọi nền tảng quan trọng. Tuy vậy, tôi vẫn luôn thấy khó chịu với cách xử lý error và nil. Tôi thường nhớ những cú pháp như Result[Ok, Err], Optional[T]
Ngược lại, tôi thấy Go không phải là cố chấp với nguyên tắc, mà là ám ảnh với sự tiện lợi để giải quyết thật nhanh vấn đề đang ở ngay trước mắt. Nó không phân tích tận gốc vấn đề để giải quyết cho đúng, mà giống như bỏ qua tinh thần “Not Invented Here” rồi ứng biến làm đại cho xong. API filesystem của Go là ví dụ tiêu biểu. Cần hàm mở file thì chỉ việc làm kiểu
func Open(name string) (*File, error)là xong. Nhưng nếu tên file không phải UTF-8 thì sao? Vì 5 năm không gặp vấn đề đó nên chẳng quan tâmTôi thường có cảm giác các nguyên tắc thiết kế của Go quá thiên về mục tiêu “làm compiler dễ viết và compile thật nhanh”. Cấu trúc của nó tập trung vào compiler/quá trình compile hơn là sự tiện lợi cho lập trình viên
Sau 20 năm, đây là lần đầu tôi thực sự dùng Go ở một công việc mới với ngôn ngữ biên dịch. Có thể chỉ là sở thích cá nhân, nhưng thành thật mà nói tôi còn thấy khó chịu khi dùng. Không có giá trị tham số mặc định, cách xử lý error không hợp ý, không có stack trace tử tế trong production. Cú pháp hướng đối tượng cũng xấu vì phải gắn reference kỳ cục vào từng hàm. Pointer cũng là gánh nặng. Cuối cùng nó cho cảm giác quay lại công nghệ cũ của C/C++. Không khí lập trình y như hồi tôi học đại học khoảng năm 1999
Về mặt xử lý song song, theo trải nghiệm của tôi, Go là hệ thống duy nhất mà bản thân ngôn ngữ xử lý song song một cách tự nhiên trong môi trường CPU đa lõi. Nhờ công thức goroutine/channel kiểu CSP, logic song song được biểu đạt rất trực quan. Python gây đau đầu với GIL và các thư viện async khó hiểu. C, C++, Java đều cần thêm thư viện bên ngoài nên khó suy luận về song song ở cấp độ ngôn ngữ. Vì vậy tôi thấy go cực kỳ phù hợp cho HTTP server hay service. Theo kinh nghiệm của tôi, gần như không có lựa chọn thay thế nào ngang tầm
Từ góc nhìn của lập trình viên, về ergonomics, tức mặt chuẩn hóa và tính nhất quán, tôi thấy nó gần như hoàn hảo. Dù làm ở nhiều codebase microservice khác nhau cũng không phải lo style mỗi nơi một kiểu, và cũng chẳng cần tranh cãi về format. Tuy nhiên, khi chọn cách chuẩn của riêng Go, có vẻ họ quá bám vào phong cách cũ ở một vài điểm. Lập trình viên ngày nay thường kỳ vọng các method kiểu hàm như map/filter, nhưng Go chỉ cung cấp vòng lặp với nguy cơ sai index. Hệ thống kiểu cũng không thông minh cỡ TypeScript. Xử lý error thì bất tiện. Tôi hiểu rằng thêm những tính năng này có thể làm tăng các cách dùng “sáng tạo nhưng tệ”, nhưng tôi cũng thấy khó thuyết phục thế hệ JS dùng go
Tôi đã gắn bó hơn 5 năm với một dự án Golang lớn, và mỗi khi phải làm component cần tối thiểu hóa dùng bộ nhớ thì lại thường xuyên đụng phải những phần lỏng lẻo của Go. GC không dọn đủ nhanh hoặc vấn đề phân mảnh heap trở nên nghiêm trọng hơn (do Go không phải compacting garbage collector). Vì thế tôi cố tránh allocation hoàn toàn, nhưng như vậy rất dễ phát sinh bug. Debug cũng cực kỳ khó. Dù có lấy heap profile thì cũng chỉ thấy thông tin object còn sống, chứ không thấy rác thực tế đã tích lại hay lịch sử phân mảnh, nên đành phải đoán. Ví dụ, hàm X có thể hiện ra là chỉ allocate 1KB trên heap, nhưng nếu bị gọi liên tục trong vòng lặp thì có thể tạo ra hàng chục MB rác. Vì vậy tôi phải cấp phát buffer tĩnh trước để tái sử dụng, nhưng rồi vấn đề ownership trở nên phức tạp và lại có các lỗ hổng như
append. Đôi khi còn phải tự viết lại cả standard library. Tôi cũng biết trường hợp của chúng tôi không phổ biến, nhưng đúng là có cảm giác phải vật lộn với ngôn ngữ nên khá tiếcTrong những trường hợp như vậy, có khi đưa bộ nhớ ra ngoài heap lại đỡ đau khổ hơn. Tất nhiên vì đây là ngôn ngữ có GC nên không dễ, nhưng thay vì cố ép viết code đậm chất C++/Rust bằng Go, tốt hơn là chuyển hẳn phần đó sang chính các ngôn ngữ ấy
Tôi nghĩ việc chọn go cho tình huống này ngay từ đầu đã là vấn đề lựa chọn ngôn ngữ. Theo tôi thì C/C++/Rust/Zig phù hợp hơn
Có tin là garbage collector mới "Green Tea" có thể giúp được phần nào. Nó là thuật toán parallel mark xử lý tốt hơn các object gần nhau trong bộ nhớ, dù không hẳn tập trung vào bộ nhớ. Có thể tham khảo thông tin ở đây
Đã từng có thử nghiệm arena nhưng hiện tại đã bị dừng. Dù vậy vẫn là thứ đáng quan tâm
Xin lỗi vì nói điều này có lẽ không giúp ích gì, nhưng nhìn tình hình hiện tại thì tôi nghĩ lựa chọn ngôn ngữ hoàn toàn sai. Tôi đoán có lẽ công ty đang ép dùng go vì chính sách ngôn ngữ chính thức nội bộ. Các tập đoàn lớn thường chỉ phê duyệt production với những ngôn ngữ được dùng rộng rãi
Tôi vẫn không hiểu vì sao
defercủa Go chỉ hoạt động ở function scope chứ không áp dụng cho lexical scope. Tôi biết chuyện này cũng là vì từng xử lý file trong vòng lặp, rồi khi danh sách file lớn lên thìdeferkhông đóng handle cho tới lúc hàm kết thúc nên gây crash. Các lập trình viên Go quanh tôi bảo hãy bọc thân vòng lặp trong anonymous function. Ngoài vài điểm nhỏ nhặt khác, tôi thấy Go khá dễ chịu, cú pháp hiệu quả và còn ngăn được văn hóa “khoe mẽ” vô ích. Tôi từng rewrite một dự án C# lớn sang Go, và dù chỉ có 1/10 số tính năng, lượng code lại còn ít hơn. Nó khuyến khích dùng các mặc định hiệu năng tốt thay vì ép allocation của GC, và khả năng code generation tích hợp cho những việc như serialization cũng tiện. Khác với cú pháp C# kiểu muốn thay mọi thứ bằng ngôn ngữ như ORM, trong Go người ta có xu hướng cứ để SQL là SQL, còn gRPC thì xử lý bằng spec protobufĐôi khi cần
defertheo lexical scope, nhưng đôi khi lại cần theo function scope. Ví dụ, nếu trong vòng lặp bạn muốn mở nhiều file và giữ tất cả mở cho đến khi hàm kết thúc thì function scope là cần thiết. Hiện giờ nó là function scope, còn khi cần lexical scope thì có thể bọc bằngfunc. Nếu chỉ hỗ trợ lexical scope mà lại cần function scope thì sẽ không rõ phải làm thế nàoƯu điểm là giảm được một mức thụt lề vì không cần hàm wrapper, hành vi của nó gắn với call stack hay stack unwinding, và nhìn từ phong cách
goto failcủa C thì cũng khá tự nhiên. Tất nhiên, dùngdefertrong vòng lặp thì phải bọc riêng trong hàm nên hơi bất tiệnTôi đã dùng cả ngôn ngữ có defer cấp block lẫn cấp function, và đôi khi lại ước mình có thể dùng defer cấp function ngay trong cả câu lệnh điều kiện
Tôi không nghĩ có lý do gì đặc biệt sâu xa, và cũng tự hỏi liệu chuyện đó có thật sự quan trọng không
Trong C# cũng có thể làm việc bằng SQL hay spec protobuf. Khác biệt chỉ là nó còn có những lựa chọn khác nữa
Go có rất nhiều nhược điểm, nhưng trong nhóm ngôn ngữ server-side thì tôi không thấy ngôn ngữ nào cân bằng được như thế này. Nó nhanh hơn Node hay Python, và tôi cũng thấy hệ thống kiểu của nó tốt hơn. Rào cản tiếp cận thấp hơn Rust, còn standard library và tooling thì rất xuất sắc. Tôi cũng thích cú pháp đơn giản và việc nó ép chỉ có một cách làm. Xử lý error có vấn đề, nhưng vẫn còn tốt hơn kiểu
catchcủa Node có thể nhận bất cứ error gì. Tôi tò mò không biết còn ngôn ngữ nào tốt hơn mà vẫn đáp ứng đủ các tiêu chí này không. Tôi không phải tín đồ Go; suốt sự nghiệp tôi làm backend bằng Node rất nhiều, chỉ là gần đây đang thử dùng GoThật ra mọi ưu điểm này cũng có thể nói y hệt về Java hay C#
Tôi hơi khó chịu khi gọi 'Node' là ngôn ngữ lập trình. Node là runtime của JavaScript, và ngày nay khá nhiều dự án chạy trên Node lại được viết bằng TypeScript. Tức là nói Node thôi thì chưa rõ đang dùng ngôn ngữ nào. Nếu lấy TypeScript làm chuẩn thì tôi lại thấy nó còn hiệu quả hơn hệ thống kiểu của Go. Cũng có thể nói điều tương tự khi so với Rust
Đa số ngôn ngữ đều có kiểu bất tiện riêng. Go có hiệu năng, tính di động, runtime và hệ sinh thái đều tốt. Ngược lại, nó cũng có nhược điểm như nil pointer, zero value, không có destructor, không có macro (vì thiếu macro nên code generation bị lạm dụng để né hạn chế của Go). Có những ngôn ngữ tốt hơn nữa, chẳng hạn Rust, nhưng chúng cũng phức tạp hơn Go rất nhiều. Lý do nằm ở chỗ người tạo ra Go đặt sự đơn giản lên ưu tiên cao nhất, và đó là nguồn gốc của các vấn đề này
Nếu tính đến sự phát triển gần đây của hệ thống kiểu Python, tôi cho rằng nó vượt Go rất xa. Chỉ xét riêng structural typing thì Python còn ấn tượng hơn
Tôi nghĩ hệ thống kiểu của Go còn thiếu sót rất nhiều
Tôi từng mở rộng một static site generator viết bằng Go. Code rất rõ ràng và dễ đọc, nhưng khả năng mở rộng thấp vì những lỗ hổng của ngôn ngữ. Chỉ một thay đổi đơn giản cũng phải sửa rất khó ở nhiều chỗ. Việc làm encapsulation và abstraction ở nhiều mức độ khác nhau khá khó, và abstraction đã bị hy sinh vì “sự đơn giản”. Trong khi abstraction là cách quan trọng nhất để tạo ra code dễ mở rộng, Go lại chọn sự đơn giản thay vì khả năng mở rộng. Phần lớn chương trình Go cho tôi cảm giác chỉ đạt tới “sự đơn giản không có khả năng mở rộng”. Mọi người cứ khăng khăng rằng Go vốn là vậy, nhưng với trải nghiệm của tôi thì không thuyết phục. Dù sao thì riêng “trải nghiệm phát triển” cũng không tệ
Các cuộc trò chuyện về Go lúc nào cũng cho cảm giác kỳ lạ. Hễ phê bình là thường nhận được phản ứng kiểu “ngôn ngữ này vốn thế, cứ chấp nhận đi”. Người ta nói đơn giản là điểm mạnh, nhưng việc phải tự viết vòng lặp chỉ để lấy danh sách key của map liệu có thực sự đơn giản hơn không thì tôi nghi ngờ
Tôi muốn hỏi liệu chỉ dùng Go một thời gian ngắn mà đã có thể dễ dàng đưa ra kiểu phê bình này chưa. Tôi đã trải qua vô số codebase Go lớn từ năm 2015 đến nay, hàng triệu dòng code, làm với nhiều team khác nhau. Khả năng mở rộng của Go không hề kém đáng kể so với C, C# hay Java. Go thiên về sự rõ ràng hơn là tính biểu đạt. Vì vậy nó có ít lớp abstraction hơn, và khiến người ta có thói quen viết cụ thể, tường minh hơn. Nhưng tôi không cho rằng điều đó đồng nghĩa với không thể mở rộng. Thiết kế module hóa và mở rộng được là thứ thuộc về việc lập trình viên học và thực hiện, chứ không phải do ngôn ngữ quyết định. Có lẽ code bạn từng xử lý chỉ được thiết kế tệ, chứ không phải giới hạn của bản thân Go
Tôi đã dùng Go vài năm, và đúng là có thể làm thứ nhỏ rất nhanh, nhưng khi quy mô lớn lên thì bắt đầu bị hành bởi vô số bất tiện nhỏ. Debug đặc biệt như ác mộng: chỉ cần có một biến X không dùng đến (điều này xảy ra suốt khi comment tạm một phần nào đó lúc debug) là chương trình thậm chí không compile được. Quá nhiều kiểu khuôn mẫu không cần thiết, tên file đặc biệt, field name dành riêng cũng gây phiền. Những panic ẩn trong standard library và cả các lần copy heap bất ngờ cũng vừa chậm vừa bực. Phần “ma thuật” của Go phần lớn là tác dụng phụ của việc cố tận dụng lại các cơ chế cũ như tên file đặc biệt, chữ hoa chữ thường v.v. Nếu thật sự muốn biểu thị “public” thì hoàn toàn có thể cho gõ
pub, thế mà lại cố chấp một cách kỳ quặc. Dạo này AI tốt hơn nhiều, nên khi gặp lỗi kiểu hay borrow checker trong Rust tôi chỉ cần hỏi AI là giải quyết rất nhanh, vui hơn hẳn. Không còn phải phí thời gian lục tài liệu hay Stack Overflow như trướcGần đây tôi chưa thực sự làm Rust nghiêm túc, nhưng khi thử qua hồi tháng 12 năm ngoái thì rất ngạc nhiên vì AI xử lý Rust quá tốt. Có lẽ vì cú pháp chi tiết và thông tin kiểu tường minh rất nhiều nên AI còn giải tốt hơn cả con người
Mỗi khi phàn nàn về chuyện debug trong Go dẫn tới lỗi compile, phía cộng đồng Go lại quở trách rằng “hãy dùng công cụ cho đúng”. Họ áp dụng nguyên tắc quá cực đoan nên thành ra bất tiện
Tôi từng nói chuyện về sự bất tiện khi debug này với chính người sáng lập Go, mà ông ấy còn không hiểu vấn đề. Tôi thấy khá thất vọng vì quá nghiệp dư. Nhân tiện, AI lại không xử lý Go tốt đến vậy. Dù ngôn ngữ khá đơn giản, ChatGPT vẫn hỗ trợ Java, C#, Python tốt hơn
Cá nhân tôi không thích Go và cũng thấy nhiều nhược điểm chí mạng, nhưng tôi hiểu rõ vì sao nó vẫn rất phổ biến. Go tương đối nhanh, và nhờ nền tảng goroutine nên có thể dễ dàng viết các service độ đồng thời cao, ổn định và đáng tin mà không cần trực tiếp xử lý multithread. Khi Google đưa Go ra mắt, gần như không có ngôn ngữ biên dịch, tĩnh kiểu, đại chúng nào tương tự. Đến giờ đối thủ ở vị trí gần nhất có lẽ chỉ là Java (giờ đã hỗ trợ virtual thread). Các ngôn ngữ hỗ trợ async/await cũng hứa hẹn điều tương tự, nhưng trên thực tế lại đi kèm rất nhiều độ phức tạp như tránh blocking trong async task, function coloring v.v. Erlang thì thuộc hẳn nhóm khác. Rốt cuộc, dù có nhiều nhược điểm, Go vẫn nổi tiếng nhờ goroutine và tên tuổi của một dự án từ Google
JVM đang dần thu hẹp khoảng cách với Go. Nhờ các dự án như virtual threads, zgc, lilliput, Leyden, Valhalla mà nó ngày càng tốt hơn. Sự thay đổi từ Java 8 lên 25 là rất lớn. Có vẻ tương lai nó sẽ còn tiện hơn nữa
Tính tường minh và đơn giản của Go cực kỳ phù hợp với lập trình có LLM hỗ trợ. Cả code Go 1.x cũ cũng vẫn chạy tốt nguyên vẹn trên phiên bản mới nhất
Thực tế trong nội bộ Google, Java có virtual threads được dùng thường xuyên hơn Go rất nhiều
Tôi tò mò theo bạn thì “ngôn ngữ hiện đại” nào là phù hợp nhất cho các dự án mới
Tôi thích Go từ trước cả bản 1.0, nhưng tôi không đồng ý với nhận xét kiểu “đến giờ vẫn chưa làm được”. Tất nhiên nó có khuyết điểm và điều gây khó chịu, nhưng tôi cũng nghĩ rằng khi người sáng lập rời khỏi dự án thì rất khó giữ được tầm nhìn trung tâm, và ngôn ngữ có thể trở nên tệ đi. Việc chỉ bị định vị là “ngôn ngữ server” cuối cùng cũng sẽ khiến người ta chuyển sang Rust hay Python. Hồi xưa Visual Basic cũng từng bị chê bai, nhưng rốt cuộc những ai cần vẫn cứ dùng tốt
Những bài viết chỉ trích nhược điểm của Go, nếu thật sự xem xét kỹ, thì phần lớn không phải vấn đề lớn. Đa số đều đúng về mặt kỹ thuật nhưng khá nhỏ nhặt. Ngược lại, những vấn đề thiết kế ngôn ngữ thực sự nghiêm trọng lại là zero value, không hỗ trợ constructor, xử lý null kém, mutability mặc định, hệ thống kiểu không được thiết kế sẵn cho generics,
intkhông hỗ trợ độ chính xác tùy ý, vàslicecó ownership mơ hồ (vấn đề liên quan 1, vấn đề liên quan 2). Việc không có sum type, không hỗ trợ string interpolation cũng là nhược điểmCó thể tôi thiên vị đến mức viết cả sách về Go, nhưng với tư cách người đã dùng Go hơn 10 năm, ban đầu tôi thật sự thấy nó rất mới mẻ. Ít boilerplate hơn Java, dễ học, và hiệu năng cũng ổn. Không có ngôn ngữ nào là tốt nhất tuyệt đối; mỗi mục đích có lựa chọn tối ưu riêng, nhưng với công việc backend điển hình thì đây là một lựa chọn không phải hối hận