1 điểm bởi GN⁺ 2024-02-08 | 1 bình luận | Chia sẻ qua WhatsApp
  • Biến vòng lặp for được thay đổi để được tạo mới ở mỗi lần lặp, nhằm tránh các lỗi chia sẻ ngoài ý muốn; cũng có thể dùng range với số nguyên
  • Tính tương thích vẫn duy trì cam kết tương thích Go 1, và kỳ vọng gần như mọi chương trình Go sẽ biên dịch và chạy như trước
  • Có thể dùng thư mục vendor được tạo bằng go work vendor trong workspace; nếu workspace có vendor thì -mod=vendor sẽ là giá trị mặc định
  • Ngừng hỗ trợ go get bên ngoài module trong chế độ legacy GOPATH khi GO111MODULE=off; các lệnh build khác như go buildgo test vẫn tiếp tục hoạt động với các chương trình legacy GOPATH
  • Runtime được thay đổi để đặt metadata GC dựa trên kiểu gần các đối tượng heap, giúp cải thiện hiệu năng CPU của chương trình Go 1–3% và giảm overhead bộ nhớ của nhiều chương trình khoảng 1%
  • Căn chỉnh địa chỉ của một số đối tượng có thể đổi từ mức 16 byte trở lên trước đây sang căn chỉnh 8 byte; mã assembly giả định mức căn chỉnh cao hơn cần được sửa, và có thể dùng GOEXPERIMENT=noallocheaders như cách обход tạm thời
  • Build PGO có thể devirtualize nhiều lời gọi hơn; khi bật PGO trên một tập hợp các chương trình Go đại diện, hiệu năng runtime cải thiện 2–14%
  • Thêm gói v2 đầu tiên vào thư viện chuẩn là math/rand/v2, không kế thừa Read, có bộ sinh toàn cục luôn được seed ngẫu nhiên, cung cấp các source ChaCha8PCG, cùng hàm generic N
  • Pattern định tuyến của net/http.ServeMux hỗ trợ method và wildcard; có một số phá vỡ tương thích ngược nhỏ với các pattern chứa {} và xử lý đường dẫn đã escape, có thể khôi phục hành vi cũ bằng GODEBUG=httpmuxgo121=1
  • Phiên bản tối thiểu mặc định của server crypto/tls khi không chỉ định được đổi thành TLS 1.2, và các cipher suite không có ECDHE trong handshake trước TLS 1.3 sẽ không được cung cấp mặc định
  • Execution tracer được cải tổ toàn diện, sử dụng đồng hồ OS trên hầu hết nền tảng, cung cấp trace được phân đoạn có thể xử lý dạng streaming, toàn bộ duration của syscall, và thông tin OS thread mà goroutine đã chạy trên đó
  • Web UI của công cụ trace được cập nhật như một phần của công việc hỗ trợ tracer mới, hỗ trợ hiển thị thread-oriented view và toàn bộ duration của syscall trong trace của các chương trình build bằng Go 1.22 trở lên
  • vet điều chỉnh cảnh báo loop closure theo ngữ nghĩa biến vòng lặp của Go 1.22, đồng thời thêm cảnh báo cho append không dùng giá trị trả về, time.Since không bị trì hoãn trong defer, và không khớp key/value của log/slog
  • Bootstrap yêu cầu bản point release cuối cùng của Go 1.20 trở lên; dự kiến Go 1.24 sẽ yêu cầu bản point release cuối cùng của Go 1.22 trở lên
  • Trên darwin/amd64, Go toolchain mặc định tạo PIE; Go 1.22 là bản phát hành cuối cùng chạy trên macOS 10.15 Catalina, còn Go 1.23 sẽ cần macOS 11 Big Sur trở lên
  • Thêm kiểu Alias và hàm Unalias vào go/types; với gotypesalias=1, client phải xử lý kiểu Alias
  • slices.Delete, DeleteFunc, Compact, CompactFunc, Replace sẽ zero các phần tử trong vùng bị thu nhỏ, và Insert luôn panic nếu i nằm ngoài phạm vi
  • Các hàm đọc thư mục trên Windows đọc mục theo lô để giảm số lần gọi hệ thống, cải thiện hiệu năng tối đa 30%

1 bình luận

 
GN⁺ 2024-02-08
Ý kiến trên Hacker News
  • Trong 3 năm qua tôi chủ yếu dùng TypeScript, gần đây mới bắt đầu dùng Go; ban đầu tôi hơi lo vì thiếu các hàm cho mảng hoặc hệ thống kiểu kém linh hoạt hơn
    Nhưng khi thử thiết lập lại một dự án TypeScript cho đội 4–5 người, tôi mất quá nhiều thời gian vào linting, chọn thư viện định tuyến server, chọn server, tiêu chuẩn coding, xử lý lỗi cơ bản, ép dùng lỗi tùy chỉnh hoặc kiểu Result để tránh địa ngục try/catch lồng nhau, test và mocking, cấu hình Prisma, v.v.
    Dù tôi thích TypeScript, dùng một ngôn ngữ có thư viện chuẩn tuyệt vời, type safety đúng nghĩa và tiêu chuẩn coding tích hợp sẵn thật sự rất tiện. Go cũng có những quirks riêng nhưng khá ổn, và việc định tuyến cũng được đưa vào thư viện chuẩn nghĩa là bớt thêm một thứ phải bận tâm, nên rất tốt. Giờ tôi chỉ còn chờ các hàm map/filter/find cho slice

    • Một newsletter dành cho CTO gần đây đã so sánh các bản phát hành TypeScript và Go, lấy Go 1.22 làm ví dụ
      TypeScript qua mỗi bản phát hành lại ngày càng phức tạp hơn cho power user, trong khi Go bổ sung những thứ còn thiếu như range trên số nguyên để khiến việc sử dụng đơn giản hơn
      Điều này giống như phần tiếp theo của một game cứ tiếp tục tăng thêm thiết lập và cơ chế rồi cuối cùng cần reset vì người dùng mới; với ngôn ngữ lập trình thì làm vậy khó hơn, nên thật tốt khi Go ý thức được điều này
    • Tôi cũng có cảm giác tương tự. Điều khiến tôi ở lại với Go là toolchain build thống nhất đến đáng ngạc nhiên, và những gì có thể làm với go:embedgo:generate lần nào nhìn thấy ở dự án khác cũng đều gây ấn tượng
      Các gói golang.org/x cũng rất hay vì gần như mọi triển khai liên quan đến RFC Internet đều đã sẵn sàng để dùng ngay
    • Trước khi map/filter/find cho slice xuất hiện, có thể dùng thư viện lo lấy cảm hứng từ lodash
      Đây là bộ công cụ đa năng tôi rất thích dùng trong các dự án Go: https://github.com/samber/lo
    • Vì không có quá nhiều tính năng nên ngược lại lại giảm được các quyết định nhỏ, và trái với trực giác, nó khá năng suất
    • Tôi từng làm một thời gian ở một khách hàng dùng TypeScript, về sau tôi gọi đó là “Tricks Driven Development”
      Cứ mỗi khi đọc tài liệu để làm gì đó, chẳng mấy chốc lại có ai đó chỉ cho một mẹo dùng được nhưng không có trong tài liệu; chuyện đó lặp đi lặp lại
  • Nếu ghi chú phát hành chính thức hơi khô khan, tôi đã làm một phiên bản tương tác: https://antonz.org/go-1-22

    • Vấn đề chia sẻ biến trong vòng lặp for ban đầu tôi không hiểu ngay, nhưng tự chạy rồi thấy kết quả ngoài dự đoán thì rất hữu ích
    • Ví dụ CompactReplace hơi khó hiểu, tôi không chắc như vậy có đúng không
  • Tôi đã dùng Go hơn 9 năm, và 4 năm gần đây dùng Dart khá nhiều vì Flutter; tôi thấy hai ngôn ngữ này có thái độ với độ phức tạp hoàn toàn trái ngược. Dart cố thêm mọi tính năng có thể, còn Go thì ngược lại
    Thứ nhất, trong Dart có quá nhiều cách khởi tạo giá trị bên trong class, khiến tốn rất nhiều thời gian. Tùy theo các tiền tố final/const/static/late, vị trí khởi tạo lại khác nhau giữa constructor, factory, initState() của Flutter StatefulWidget, v.v.; khi refactor thì phải sửa khá nhiều phần khởi tạo. Getter khiến hàm trông như biến cũng làm tăng thêm sự rối rắm trong codebase mới
    Thứ hai, Dart có nhiều tính năng rất tiện để nhồi map, stream, v.v. vào một dòng, nên rất dễ muốn nhanh chóng tạo ra code một dòng khó hiểu. Nhưng khi debug, hoặc khi lập trình viên junior gặp các thủ thuật như firstWhereOrNull và tham số tùy chọn như orElse: () => null, họ sẽ rất bối rối. Null safety khó hiểu hoặc lỗi kiểu cũng nhiều lúc làm mọi thứ khựng lại, và khi đổi sang vòng lặp for kiểu Go đơn giản thì cảm giác thật sự nhẹ nhõm

    • Một mẹo về Dart: tốt nhất không nên dùng initState cho những việc không liên quan trực tiếp đến UI. Ví dụ nếu không chỉ là đặt hint cho text field thì tôi thường tránh
      Phần lớn các trang Flutter hoặc có rất ít việc phải làm, hoặc parent tạo một đối tượng MyPageController, khởi tạo theo cách mong muốn rồi để trang con hoạt động theo controller đó. Chẳng hạn ở trang danh sách, khi người dùng muốn chỉnh sửa một mục, parent tạo controller, đưa mục vào đó và gửi sang trang con; sau khi quay lại, parent có thể xem mục hoặc biến/callback của controller để quyết định thay đổi UI
      Làm vậy có thể kiểm soát chi tiết tương tác giữa các widget mà không cần đụng đến những thứ như InheritedWidget. Tất nhiên dùng cùng thư viện quản lý trạng thái thì tốt, nhưng thực tế cũng có nhiều trường hợp không dùng
    • Tôi đồng ý rằng sự đơn giản của Go là lợi thế lớn nhất. range cũng vậy; rốt cuộc số ký tự phải gõ cũng tương tự comprehension kiểu forEach(f), chỉ là nó trải ra ba dòng nên trông dài hơn, còn tự viết tường minh thì chẳng có gì ghê gớm
      Tuy nhiên, về khởi tạo, tôi vẫn muốn Go có một cách idiomatic để khởi tạo field của struct bằng một giá trị cụ thể. Không có constructor thì cũng ổn, nhưng đôi khi tôi muốn bool mặc định được khởi tạo là true
    • initState là override của StatefulWidget, không khác gì sự kiện vòng đời trong các framework khác, và là khái niệm phía Flutter nên không phải thứ dễ nhầm với constructor
      Có thể sẽ bối rối không biết nên dùng constructor thường hay factory, nhưng khó có thể nói Go vượt trội chỉ vì initState
    • Nên tối ưu hóa khả năng để các chuyên gia đắt giá có thể giao tiếp súc tích với nhau. Tước khỏi tay người mới những công cụ cú pháp mạnh mẽ chỉ ngăn họ phát triển chuyên môn mà thôi
  • Khi io.Copy sao chép từ TCPConn sang UnixConn, nếu có thể nó sẽ dùng system call splice(2) của Linux, và sử dụng method mới TCPConn.WriteTo
    Với nâng cấp interface, zero-copy I/O lại được mở rộng một cách trong suốt thêm một lần nữa. Hiệu quả mà package io tạo ra bằng pattern này thật sự rất lớn

    • Thư viện chuẩn Go có rất nhiều điều chỉnh kiểu này. Không hẳn là xấu, nhưng các xử lý đặc biệt như vậy không mở rộng tốt, nên việc tích hợp với thư viện không chuẩn kém thỏa mãn hơn
      Dù vậy, nó vẫn nằm trong phạm vi kỳ vọng
  • Các mẫu định tuyến được cải thiện là một nâng cấp tốt trong bối cảnh tương lai của Gorilla Mux đang bất ổn
    Tuy nhiên thay đổi này phá vỡ tương thích ngược theo những cách nhỏ: có phần rõ ràng như hành vi của các pattern có {} thay đổi, và cũng có phần kém rõ ràng hơn như cách xử lý đường dẫn đã escape. Việc này được điều khiển bằng trường httpmuxgo121 của GODEBUG; đặt httpmuxgo121=1 sẽ quay lại hành vi cũ
    Nhưng tôi thắc mắc vì sao điều này không xung đột với cam kết tương thích Go 1 rằng “các chương trình được viết theo đặc tả Go 1 sẽ tiếp tục biên dịch và chạy đúng mà không cần sửa đổi trong suốt vòng đời của đặc tả đó”

    • Cách để lách cam kết tương thích Go 1 là gắn những thay đổi không tương thích ngược như vậy với giá trị chỉ thị go trong go.mod
      Nếu là 1.22 trở lên thì nhận hành vi thư viện mới, nếu không thì nhận hành vi cũ. Thực tế nó hoạt động tốt đến đâu thì còn phải chờ xem
    • Thường thì trước khi phá vỡ thứ gì đó, đội Go sẽ quét GitHub và các chương trình mã nguồn mở để xem mọi người dùng ra sao. Có lẽ họ thấy rất hiếm trường hợp dùng {} trong đường dẫn HTTP handler
      Họ cũng cung cấp cách tắt hành vi mới nên không bắt buộc phải sửa code, dù vẫn phải đặt biến môi trường mới
      Thay đổi ngữ nghĩa vòng lặp for trong bản phát hành này cũng là ví dụ tương tự, thực chất là một thay đổi gây phá vỡ. Dù vậy mọi chương trình Go vẫn tiếp tục biên dịch và chạy, chỉ là một số hành vi hơi khác đi. Tôi nghĩ Go đã chọn cách tiếp cận thực dụng, và đó là một trong những lý do nó thành công
    • Tôi xem đây là một sai lầm hiếm gặp của Go. Nếu đã dùng go.mod để phá vỡ theo từng phiên bản thì ít nhất cũng nên phá vỡ theo cách tốt hơn
      Khi đổi go.mod sang 1.22, để code biên dịch thất bại hẳn còn tốt hơn nhiều so với việc phải truy tìm các vấn đề runtime tinh vi
    • Nếu không thì sẽ mất đi câu khẩu hiệu quảng bá ngắn gọn và mạnh mẽ đó. Dù nhỏ hay không, đây vẫn là một thay đổi không tương thích
  • Trong Go 1.22, mỗi lần lặp của vòng lặp sẽ tạo một biến mới
    Nội dung này trước đây cũng đã được thảo luận ở đây: https://news.ycombinator.com/item?id=33160236 - Go: Redefining For Loop Variable Semantics (2022)

  • Có thể là tôi cổ hủ, nhưng tôi không thích việc thêm range trên hàm. Sự tiện lợi không đủ lớn để biện minh cho độ phức tạp được thêm vào ngôn ngữ, và phong cách functional có vẻ lệch khỏi phong cách rõ ràng, imperative, dài dòng nhưng ít tính năng của Go. Tôi nghĩ đó từng là một trong những điểm mạnh lớn của Go
    Vì cùng lý do, tôi cũng nghĩ range trên số nguyên là đi sai hướng. Tập tính năng mỏng và tải nhận thức thấp của Go luôn là điểm mạnh chính và yếu tố khác biệt

    • Hiện tính năng này là tùy chọn, mang tính thử nghiệm và phải bật rõ ràng. Nếu nó được bật mặc định, tôi mong có một cơ chế tắt để khuyến khích lập trình viên không dùng khi nó không phổ biến
      Một trong những vấn đề lớn trong phát triển phần mềm là ngăn lập trình viên thêm độ phức tạp hoặc phô diễn kỹ năng. Đặc biệt là lập trình viên giai đoạn đầu sự nghiệp thường có xu hướng làm giải pháp phức tạp quá mức, thay vì giải quyết vấn đề theo cách thẳng thắn nhưng kém thanh lịch và dài dòng hơn rồi đi tiếp
    • Tính năng này có vẻ còn hơi sớm. Lý tưởng thì nó nên xuất hiện sau khi lambda và generic parameter packs được đưa vào
      Hỗ trợ generics hiện tại của Go chưa đủ tinh vi để nâng đỡ việc này, nên sinh ra những giới hạn bị ép khá kỳ lạ
    • Tôi cũng thấy lửng lơ. So với việc truyền hàm inline, số dòng code có khi cũng chẳng giảm
  • Việc thêm sql.Null[T] là tốt. Có lẽ tôi sẽ bắt đầu dùng trong các dự án mới
    Trong công việc hiện tại, tôi đang phụ thuộc vào null [0] của sqlboiler, có API rất giống. Nó hoạt động như sql.Null, nhưng có thêm phương thức IsSet() bool cho biết giá trị đã từng được đặt rõ ràng hay chưa, giúp phân biệt giữa “null có chủ ý” và “null vì chưa khởi tạo”. Nghe có vẻ hay, nhưng thực tế tôi chưa từng dùng đến

    • Tôi tự hỏi khi nào cần biết null có phải là có chủ ý hay không. Trong cơ sở dữ liệu đâu có kiểu tương ứng cho việc đó, đúng không
  • Tôi đang mong xem lại mux của thư viện chuẩn và hy vọng có thể loại bỏ chi. Thật sự rất thích khi những tính năng như thế này đi vào thư viện chuẩn

    • Tôi cũng vào để nói đúng điều đó. chi thật sự rất tuyệt trong vài dự án, và tự nhiên đến mức thực ra tôi còn quên mất sự tồn tại của nó
      Khi chức năng như thế này đi vào thư viện chuẩn, nó được đảm bảo luôn được bảo trì, và đảm bảo nhiều chương trình Go dùng cùng một cách tiếp cận
  • Tôi tò mò là nếu dùng Go trong production thì mọi người có thể nhanh chóng chuyển sang phiên bản mới nhất không, hay bị kẹt ở các bản phát hành cũ
    Nhìn các dự án công khai, có vẻ nhiều nơi tránh dùng các tính năng tương đối mới. Dù không dùng generics, tôi vẫn muốn dùng any được đưa vào từ Go 1.18 thay cho interface{} cũ, và cũng từng nghe nói cái sau “idiomatic” hơn

    • Ở Square, chúng tôi thường chờ bản point release đầu tiên trước khi nâng phiên bản mặc định của monorepo Go. Chủ sở hữu từng ứng dụng riêng lẻ có thể nâng sớm hơn
      Lần này hơi rắc rối một chút: đội Go đã phát hành 1.22 trước khi chúng tôi nâng mặc định lên 1.21. Giờ việc chờ gần như chỉ còn là thông lệ, và rất hiếm khi bản phát hành Go làm hỏng thứ gì đó
    • Với tính năng mới, tôi thường chờ thử nghiệm cho đến khi thấy vấn đề phù hợp, nhưng phiên bản compiler thì gần như nâng ngay sau khi phát hành
      Ví dụ với generics, chúng tôi đã chờ khoảng 6 tháng trước khi dùng thật, để kiểm tra tính hữu ích, trải nghiệm lập trình viên, và liệu có ảnh hưởng đáng kể đến tốc độ build/test hay không
    • Ở chỗ làm, các container đang vận hành, tức dịch vụ SaaS/API, thì lập trình viên có thể tự do nâng lên bản build mới nhất
      Nhưng chúng tôi cũng cung cấp ứng dụng Go cho người dùng cuối; phần này phải ở lại 1.20 vì khả năng tương thích với các OS client cũ. Do 1.21 đã có một đợt cắt khá lớn, có khả năng chúng tôi sẽ phải ở lại 1.20 trong vài năm
    • CI build và test bằng container latest của Go, và bộ test khá chắc nên về cơ bản là tự động cập nhật. Nếu có gì không khớp, pipeline sẽ vỡ, rồi khi đó quyết định ghim vào phiên bản cuối cùng còn chạy được hay hoãn cập nhật cho đến khi sửa xong thay đổi gây lỗi
      Vài năm một lần có thể lộ ra bug edge case tinh vi giữa phiên bản mới và package bên thứ ba, nhưng những bug như vậy theo định nghĩa đã lọt qua bộ test, nên kể cả nâng cấp thủ công thì khả năng cao cũng không bắt được
    • Chuyển ngay
      Nếu lo hỗ trợ compiler Go cũ, bạn có thể đặt build condition cho phiên bản cũ và định nghĩa những thứ còn thiếu. Ví dụ có thể đặt type any = interface{} dưới // +build !go1.7