2 điểm bởi GN⁺ 2023-08-15 | 1 bình luận | Chia sẻ qua WhatsApp
  • Go 1.21 mở rộng khả năng tương thích dựa trên GODEBUG để bộ công cụ mới tái hiện hành vi của các phiên bản Go cũ một cách ổn định nhất có thể, tập trung vào việc giảm gánh nặng nâng cấp
  • Từ Go 1 năm 2012, Go đã cam kết tương thích mã nguồn và giảm hỏng hóc do việc loại bỏ hoặc thay đổi bằng cách kiểm tra API công khai và thử nghiệm nội bộ quy mô lớn
  • Ngay cả những cải tiến được tài liệu cho phép như tăng độ chính xác của time.Now, thay đổi triển khai sort, thay đổi đầu ra của compress/flate, mở rộng đầu vào của strconv.ParseInt, hay thay đổi cách phân tích của net.ParseIP cũng có thể làm hỏng các chương trình hiện có
  • Từ Go 1.21, các thiết lập GODEBUG dành cho tương thích sẽ được duy trì tối thiểu 2 năm hoặc 4 bản phát hành Go, và có thể giữ hành vi cũ làm mặc định tùy theo phiên bản go trong go.mod
  • Go 2 sẽ không xuất hiện như một đặc tả mới làm hỏng các chương trình Go 1, và Go ưu tiên tính tương thích để việc nâng cấp bộ công cụ luôn ổn định ngay cả khi bổ sung tính năng mới

Nguyên tắc cơ bản của tương thích Go 1

  • Trong Go 1 năm 2012, Go đã đặt ra mục tiêu qua tài liệu “Go 1 and the Future of Go Programs” rằng các chương trình tuân theo đặc tả Go 1 sẽ tiếp tục biên dịch và chạy đúng mà không cần thay đổi trong suốt vòng đời của đặc tả đó
  • Trọng tâm của cam kết này là tương thích mã nguồn
    • Khi cập nhật lên phiên bản Go mới, mã phải được biên dịch lại
    • Có thể bổ sung API mới, nhưng cần tránh những bổ sung làm hỏng mã hiện có
  • Không thể đảm bảo rằng mọi thay đổi trong tương lai sẽ tuyệt đối không làm hỏng bất kỳ chương trình nào
    • Nếu chương trình phụ thuộc vào hành vi lỗi, nó có thể bị hỏng khi lỗi đó được sửa
    • Go cố gắng duy trì việc nâng cấp ổn định trong khi giảm thiểu hỏng hóc nhiều nhất có thể

Ngăn phá vỡ tương thích bằng kiểm tra API công khai

  • Trong quá trình phát triển Go, danh sách API công khai của từng package được quản lý trong các tệp riêng biệt với package thực tế
    • Ví dụ, go/api/go1.21.txt ghi lại các mục hàm, phương thức và kiểu của bytes, cmp, context và các package khác
  • Các bài kiểm tra tiêu chuẩn xác minh rằng API thực tế của package khớp với các tệp này
    • Khi thêm API mới, cũng phải thêm vào tệp API thì kiểm tra mới qua
    • Nếu thay đổi hoặc xóa API hiện có, kiểm tra sẽ thất bại
  • Không chỉ việc xóa API mà thay đổi kiểu cũng có thể phá vỡ tương thích
    • os.Stdout là biến toàn cục có kiểu *os.File
    • Nếu đổi nó thành một interface có cùng phương thức, mã như greet(f *os.File) vốn yêu cầu *os.File sẽ bị hỏng
  • Kiểm tra API hữu ích trong việc phát hiện thay đổi hoặc xóa API, nhưng không thể ngăn mọi thay đổi không tương thích có thể xảy ra trong Go

Những hỏng hóc tinh vi được phát hiện qua kiểm thử

  • Các bản phát triển của bản phát hành Go mới liên tục được kiểm thử trên toàn bộ mã Go nội bộ tại Google
    • Nếu kiểm thử thành công, commit đó sẽ được cài vào bộ công cụ Go production của Google
    • Nếu kiểm thử nội bộ bị hỏng, điều đó được xem là dấu hiệu mã bên ngoài cũng có thể bị ảnh hưởng và nhóm phát triển sẽ tìm cách giảm tác động
  • Trong đa số trường hợp, thay đổi sẽ được hoàn tác hoặc viết lại để không làm hỏng chương trình
  • Một số thay đổi dù có thể làm hỏng chương trình vẫn có thể được giữ lại vì quan trọng và được xem là thay đổi tương thích theo tài liệu
    • Ngay cả khi đó, phạm vi ảnh hưởng vẫn được giảm thiểu và các vấn đề tiềm ẩn sẽ được ghi trong ghi chú phát hành

Hai ví dụ xuất hiện từ Go 1.1

  • Struct literal và trường mới

    • Trong Go 1, net.TCPAddr là struct có hai trường IP, Port, và composite literal không kèm tên trường vẫn biên dịch được
    • Khi Go 1.1 thêm trường Zone vào net.TCPAddr, mã cũ không còn biên dịch được và báo lỗi “too few initializers in struct literal”
    • Cách viết tương thích là dùng literal có gắn nhãn trường
var myAddr = &net.TCPAddr{
    IP: net.IPv4(18, 26, 4, 9),
    Port: 80,
}
  • Nếu không chỉ định Zone, trường đó sẽ dùng zero value là chuỗi rỗng
  • Tài liệu tương thích yêu cầu dùng tagged composite literal với struct của thư viện chuẩn, và go vet sẽ báo các literal không có nhãn ở những nơi cần để tương thích với các phiên bản sau
  • Độ chính xác thời gian

    • Sau Go 1, time.Now được thay đổi để trả về độ chính xác nano giây thay vì micro giây
    • Thay đổi này có thể làm hỏng các bài kiểm tra vốn mong đợi tính đồng nhất sau khi round-trip giá trị time.Now qua saveload
    • Nếu biểu diễn lưu trữ chỉ giữ được độ chính xác micro giây thì trên Go 1 sẽ thành công nhưng trên Go 1.1 có thể thất bại
    • Để hỗ trợ sửa các bài kiểm tra như vậy, Go đã thêm các phương thức RoundTruncate, đồng thời ghi lại vấn đề tiềm ẩn và các phương thức mới trong ghi chú phát hành
    • Vì độ chính xác cao hơn là hành vi tốt hơn và nằm trong phạm vi đã được tài liệu hóa của hàm, thay đổi này vẫn được phát hành dù làm hỏng một số chương trình

Ba loại thay đổi có thể phá vỡ tương thích

  • Thay đổi đầu ra

    • Thay đổi đầu ra xảy ra khi hàm tạo ra kết quả khác trước đây, nhưng kết quả mới vẫn đúng bằng hoặc đúng hơn kết quả cũ
    • Việc thêm độ chính xác nano giây cho time.Now là ví dụ điển hình
    • Trong Go 1.6, triển khai sort được thay đổi để nhanh hơn khoảng 10%, khiến thứ tự các phần tử được xem là cùng giá trị thay đổi
    • Đầu ra Go 1.5: [red blue green white black yellow orange indigo violet]
    • Đầu ra Go 1.6: [red blue white green black orange yellow indigo violet]
    • Việc sắp xếp được phép trả về cùng một kết quả theo bất kỳ thứ tự nào, nhưng các chương trình mong đợi một thứ tự cụ thể sẽ bị hỏng
    • Trong Go 1.8, compress/flate được cải tiến để tạo ra đầu ra nhỏ hơn với chi phí CPU và bộ nhớ tương tự
    • Vì vậy, các bản dựng archive có thể tái tạo trong nội bộ Google không còn tái tạo chính xác các archive cũ nữa
    • Dự án đó đã fork compress/flatecompress/gzip để giữ lại thuật toán cũ
    • Để chuẩn bị cho thay đổi đầu ra, tốt hơn nên viết chương trình và bài kiểm tra sao cho chấp nhận mọi đầu ra hợp lệ
    • Nếu thật sự cần đầu ra tái tạo tuyệt đối, có thể fork mã, nhưng đổi lại sẽ tách khỏi các bản sửa lỗi
  • Thay đổi đầu vào

    • Thay đổi đầu vào xảy ra khi hàm thay đổi loại đầu vào mà nó chấp nhận hoặc cách xử lý đầu vào đó
    • Go 1.13 thêm cú pháp dấu gạch dưới để tăng tính dễ đọc của số, và strconv.ParseInt cũng được thay đổi để chấp nhận cú pháp mới này
    • Mã của một người dùng bên ngoài, vốn sử dụng số có dấu gạch dưới như một định dạng dữ liệu riêng, đã bị hỏng
    • Mã đó ban đầu thử ParseInt trước và chỉ xử lý dấu gạch dưới khi thất bại, nhưng giờ ParseInt không còn thất bại nữa
    • net.ParseIP từng chấp nhận địa chỉ IP thập phân có số 0 ở đầu theo các ví dụ RFC IP ban đầu
    • Go đọc 18.032.4.011 thành 18.32.4.11
    • Các thư viện C kiểu BSD lại diễn giải số 0 ở đầu là bắt đầu bát phân và đọc chuỗi đó thành 18.26.4.9
    • Go 1.17 thay đổi net.ParseIP để từ chối hoàn toàn số 0 ở đầu
    • Đây là lựa chọn nhằm đảm bảo rằng khi cả Go và C đều phân tích thành công địa chỉ IP, chúng sẽ có cùng ý nghĩa
    • Kubernetes lo ngại các cấu hình đã lưu trước đó có thể không còn phân tích được trên Go 1.17, nên bắt đầu dùng bản fork của net.ParseIP gốc
    • Với đầu vào từ người dùng, tốt hơn là trước tiên xác thực cú pháp được chấp nhận trước khi phân tích giá trị, nhưng trong một số trường hợp có thể vẫn phải fork mã
  • Thay đổi giao thức

    • Thay đổi giao thức xảy ra khi thay đổi trong package xuất hiện ra bên ngoài dưới dạng thay đổi ở giao thức giao tiếp với thế giới bên ngoài
    • Go 1.6 thêm hỗ trợ HTTP/2 tự động
    • Client Go 1.5 chỉ dùng HTTP/1.1 nên có thể hoạt động bình thường trong một số môi trường có thiết bị mạng trung gian đặc thù
    • Khi nâng cấp lên Go 1.6, HTTP/2 được sử dụng và chương trình có thể bị hỏng trong những môi trường đó vì HTTP/2 không hoạt động
    • Go muốn hỗ trợ mặc định các giao thức hiện đại, nhưng việc bật HTTP/2 vẫn có thể làm hỏng chương trình ngay cả khi không phải lỗi của chương trình hay của chính Go
    • Go 1.6 đã ghi lại thay đổi trong ghi chú phát hành và cung cấp cách tắt HTTP/2
    • Đặt rõ trường TLSNextProto
    • Đặt GODEBUG=http2client=0, GODEBUG=http2server=0, hoặc cả hai cùng lúc
    • Hỗ trợ chứng chỉ HTTPS dựa trên SHA1 cũng là một ví dụ tinh vi hơn về thay đổi giao thức
    • Các tổ chức cấp chứng chỉ đã ngừng phát hành chứng chỉ SHA1 từ năm 2015, và các trình duyệt lớn không còn chấp nhận chúng từ năm 2017
    • Go 1.18 tắt hỗ trợ chứng chỉ SHA1 theo mặc định và cho phép vượt qua bằng GODEBUG
    • Do một số cài đặt Kubernetes vẫn tiếp tục dùng chứng chỉ SHA1 riêng tư, Go quyết định giữ thiết lập vượt qua này lâu hơn dự kiến

Hỗ trợ GODEBUG mở rộng trong Go 1.21

  • Go 1.21 mở rộng và chính thức hóa việc dùng GODEBUG để giảm cả các vấn đề tương thích tinh vi
  • Với những thay đổi được quy tắc tương thích Go 1 cho phép nhưng vẫn có thể làm hỏng chương trình hiện có, Go định nghĩa các thiết lập GODEBUG để từng chương trình có thể từ chối hành vi mới
    • Có thể có trường hợp không thể thêm thiết lập, nhưng đó được xem là rất hiếm
  • Các thiết lập GODEBUG phục vụ tương thích sẽ được duy trì tối thiểu 2 năm, tức 4 bản phát hành Go
    • Các thiết lập như http2client, http2server có thể được giữ lâu hơn nhiều, thậm chí trong một số trường hợp là vô thời hạn
  • Khi có thể, mỗi thiết lập GODEBUG sẽ gắn với một bộ đếm runtime/metrics
    • Tên bộ đếm có dạng /godebug/non-default-behavior/<name>:events
    • Ví dụ, nếu đặt GODEBUG=http2client=0, thì /godebug/non-default-behavior/http2client:events sẽ đếm số HTTP transport được cấu hình không dùng HTTP/2
  • Giá trị mặc định của GODEBUG cho chương trình được xác định theo phiên bản Go ghi trong go.mod của package main
    • Nếu go.modgo 1.20 và bạn nâng cấp lên bộ công cụ Go 1.21, thì các hành vi do GODEBUG kiểm soát và đã thay đổi trong Go 1.21 sẽ tiếp tục giữ hành vi của Go 1.20 cho đến khi go.mod được đổi thành go 1.21
  • Có thể thay đổi từng thiết lập GODEBUG bằng dòng //go:debug trong package main
  • Tất cả thiết lập GODEBUG được tổng hợp trong danh sách trung tâm

Trường hợp panic(nil)

  • Trong Go 1.21, panic(nil) giờ sẽ tạo ra runtime panic không phải nil
  • Thay đổi này giúp kết quả recover có thể cho biết một cách ổn định liệu goroutine hiện tại có đang panic hay không
  • Hành vi mới được điều khiển bằng thiết lập GODEBUG và phụ thuộc vào dòng go trong go.mod của package main
    • Nếu là go 1.20 trở xuống thì panic(nil) vẫn tiếp tục được cho phép
    • Nếu là go 1.21 trở lên thì panic(nil) sẽ chuyển thành panic với runtime.PanicNilError
  • Có thể ghi đè rõ ràng giá trị mặc định theo phiên bản bằng cách thêm dòng sau vào package main
//go:debug panicnil=1
  • Cách kết hợp này cho phép cập nhật lên bộ công cụ mới mà vẫn giữ hành vi của bộ công cụ cũ, kiểm soát tinh chỉnh chỉ các thiết lập cần thiết, đồng thời dùng giám sát production để biết có đang sử dụng hành vi không mặc định hay không
  • Chi tiết xem tại “Go, Backwards Compatibility, and GODEBUG

Go 2 không làm hỏng Go 1

  • Tài liệu “Go 1 and the Future of Go Programs” từng để ngỏ khả năng một ngày nào đó có thể xuất hiện đặc tả Go 2
  • Go 2 theo nghĩa không còn biên dịch được chương trình Go 1 sẽ không xuất hiện
  • Go 2 theo nghĩa là một cuộc đại tu lớn của Go 1 khởi đầu từ năm 2017 thì thực chất đã diễn ra rồi
  • Go đánh giá tính tương thích có giá trị cao hơn nhiều so với việc cắt đứt với quá khứ, và đã chọn hướng củng cố tương thích hơn nữa
  • Trong tương lai, các công việc mới mẻ và thú vị vẫn sẽ tiếp tục, nhưng sẽ được thực hiện theo cách thận trọng và tương thích để việc nâng cấp giữa các bộ công cụ ổn định nhất có thể

1 bình luận

 
GN⁺ 2023-08-15
Ý kiến trên Hacker News
  • Câu hỏi quan trọng về tính tương thích không phải là “có làm hay không”, mà là “làm như thế nào”. Thực ra, thay vì tương thích ngược với quá khứ, điều người ta muốn gần hơn là từ bây giờ mã của mình cứ tiếp tục chạy bình thường
    Go 1.21 cung cấp hai điểm cốt lõi khó thấy đồng thời ở các hệ sinh thái ngôn ngữ khác: mỗi thay đổi đều có thiết lập GODEBUG, có thể hoàn tác theo từng thay đổi, và cũng có chỉ số để phát hiện việc có đang dùng cách triển khai cũ hay không. Ngoài ra còn có phiên bản toolchain theo từng module, và có thể tự động lấy toolchain Go cũ hơn hoặc mới hơn một cách an toàn như module
    Thêm nữa, nếu chỉ định một phiên bản cụ thể như go 1.21.2, thì ngay cả khi chạy trên Go mới hơn, các cấu hình opt-out liên quan sẽ được tự động áp dụng cho đến khi bạn yêu cầu rõ ràng hành vi mới. Có thể khai báo ở bất cứ đâu: trong code, go.mod, hoặc biến môi trường, nên đây là một cách đơn giản và đẹp, bao phủ gần như mọi trường hợp sử dụng tương thích từ nhà phát triển đến người triển khai

    • Perl cũng tương tự: nếu viết use v5.24 ở đầu file thì có thể khiến nó hoạt động như Perl 5.24, và áp dụng theo từng file chứ không phải theo module
    • Xin lỗi, nhưng nếu có đủ nhiều đội và codebase từ 1 triệu đến hơn 10 triệu dòng, cách này nghe như ác mộng
      Chỉ riêng việc có hỗ trợ phiên bản mới hay không đã tạo ra một vấn đề phức tạp đến phi lý, rồi còn hình thành một thung lũng sâu kiểu “chúng tôi nghĩ mình hỗ trợ phiên bản mới, và phiên bản mới cũng nghĩ nó hỗ trợ chúng tôi, nhưng hai bên lại bỏ sót nhau”
    • Những tính năng như vậy chắc chắn cũng có ở các ngôn ngữ phổ biến khác. Haskell còn đi xa hơn, cho phép bật/tắt tính năng ngôn ngữ trong từng file
    • Nói là “tính năng cốt lõi không có trong hệ sinh thái ngôn ngữ khác” rồi lại liệt kê các tính năng phổ biến ở ngôn ngữ khác, nghe như kiểu người dùng Go ít nhiệt tình nhất
  • Tôi rất thích hướng đi này. Không gì tốt bằng việc bước vào một codebase Go và có thể kỳ vọng rằng chỉ cần nâng phiên bản Go là mọi thứ vẫn chạy tốt
    Tuy nhiên, điều đáng lo là hệ thống kiểu khó có thể được cải thiện lớn nếu không có các thay đổi gây vỡ kiểu “đây là code sai nên từ giờ không biên dịch nữa”. Tôi không biết đội Go có quan tâm đến việc này không, nhưng có nhiều “quả thấp dễ hái” có thể tăng đáng kể độ chắc chắn khi biên dịch mà không cần thêm tính năng ngôn ngữ
    Ví dụ như báo cáo nil chưa được kiểm tra, kiểm tra truy cập mảng, suy luận kiểu cho literal struct lồng nhau, kiểm tra đầy đủ enum. Đặc biệt khi viết các lời gọi gRPC lồng nhau, chỉ cần thêm một tầng suy luận kiểu là đủ, vì kiểu đã có sẵn trong chữ ký hàm được gọi, nhưng trong Go việc này quá đau khổ

    • Nói lại cùng một ý từ bên ngoài cộng đồng Go: enum có tham số kiểu Rust hay Swift đúng là món quà trời cho. Vô số chương trình sẽ trở nên dễ dùng hơn nhiều
      Nếu hệ thống kiểu của Go được cải thiện, tôi mong những kiểu dữ liệu đại số đơn giản và đẹp như vậy được thêm vào. Chúng thật sự rất hợp với Go, nên mong Go tự tặng mình món quà đó
  • Hoàn toàn đồng ý. Lý do các bản phát hành Go đáng mong chờ là vì thường chúng thêm những thứ hữu ích mà không làm hỏng gì cả
    Không phần nào của ngôn ngữ trông hỏng đến mức không thể sửa bằng thay đổi tương thích ngược. Ngay cả vài cạm bẫy như gán biến vòng lặp nhìn chung cũng đã có đề xuất giữ tương thích

    • C# cũng duy trì hoàn toàn tương thích ngược, nhưng trong giới lập trình viên .NET tôi lại thấy bầu không khí ngược hẳn. Họ nói “ngôn ngữ đang phình to”, “ngày càng khó học”
      Cá nhân tôi đồng cảm với góc nhìn mong chờ các bản phát hành Go, nhưng sự khác biệt thái độ giữa hai hệ sinh thái thật thú vị
    • Góp thêm ý kiến cá nhân, tôi thường gặp khá nhiều lỗi vỡ khi nâng cấp Go. Khi nâng cấp Rust hoặc gcc thì ít vấn đề hơn nhiều, và các bản cập nhật compiler C và Rust thật sự có cảm giác tương thích ngược
      Trước đây tôi từng tổng hợp một số lỗi vỡ đã gặp: https://news.ycombinator.com/item?id=29763324
      Với Rust hay gcc, khi nâng compiler bạn nhận được tính năng ngôn ngữ, nhưng phần lớn thư viện phức tạp vẫn giữ nguyên và có thể nâng riêng. Ví dụ HTTP của Rust tách ra thành hyper, C thì có libcurl
      Ngược lại, khi nâng compiler Go, bạn không chỉ nhận generics, embed, tính năng toolchain, mà còn kéo theo thay đổi ở tls, thư viện bảo mật, client/server HTTP. Những khối thư viện chuẩn lớn như http, tls, crypto lẽ ra nên là thư viện riêng thì tốt hơn nhiều. Như vậy có thể nâng compiler mà không phải sợ, còn thư viện thì nâng theo nhịp của mình
    • Nhìn chung tôi đồng ý, nhưng không ai biết trước tương lai. Edition Rust 2018 đã đưa vào từ khóa async, mà ở edition 2015 vẫn có thể tạo biến hoặc hàm tên async, nên đó là thay đổi gây vỡ
      Tương thích ngược là tốt, nhưng khó chắc rằng một tương lai nơi Go không thể thực hiện đổi mới X nào đó vì phải giữ tương thích là ổn
    • Sẽ tốt nếu có sum type và một cách tốt hơn để giảm sự dài dòng trong xử lý lỗi
  • Tôi rất ủng hộ lập trường rằng Go 2 trong tương lai sẽ tuyệt đối không phá vỡ tính tương thích với Go 1. Nếu phải thay đổi ngôn ngữ đến mức lớn như vậy thì tôi nghĩ tốt hơn là cứ fork rồi đổi tên
    Tuy nhiên tôi thắc mắc vì sao không đi xa hơn và nói rằng “sẽ không có Go 2” để loại bỏ sự mơ hồ. Nếu Go 2 trên lý thuyết chạy được mọi chương trình Go 1, thì nó khác gì với một bản phát hành Go 1.xx? Bài viết nói “sẽ không có Go 2 làm hỏng chương trình Go 1”, nhưng có vẻ không nói xa hơn thế

    • 5 năm trước đã nói “sẽ là như vậy” rồi. Chỉ là có kèm tiền đề “nếu”
      https://github.com/golang/proposal/blob/d661ed19a203000b7c54...
      Tài liệu giải thích rằng nếu quá trình trên vận hành đúng kế hoạch, thì theo một nghĩa quan trọng sẽ không có Go 2, mà sẽ chuyển đổi dần dần bằng các tính năng ngôn ngữ và thư viện mới. Đến một thời điểm nào đó, về mặt marketing có thể gọi là “giờ là Go 2”, nhưng cũng có thể bỏ qua luôn. Lập luận là C không có 2.0 thì tại sao Go 2.0 lại cần thiết
      Các ngôn ngữ phổ biến như C, C++, Java thực ra cũng gần như luôn là phiên bản 1.N, và tôi nghĩ Go nên làm theo cách đó. Một Go 2 thật sự theo nghĩa một ngôn ngữ mới hoặc thư viện lõi mới không tương thích không phải là lựa chọn tốt cho người dùng, và có thể còn gây hại
    • Java đã đi qua con đường này rồi. Từng có Java 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, rồi sau đó đổi thành “dù sao cũng không phá vỡ tương thích ngược, vậy hãy gọi là Java 8, 9, 10…21 thay vì 1.x”
      Cuối cùng tôi thấy cách đó hợp lý
    • Có thể đang đánh giá quá cao quy mô thay đổi cần thiết để phá vỡ tương thích mã nguồn. Một ví dụ nhẹ là thêm keyword
      Nếu có một tính năng ngôn ngữ mới mà cộng đồng rõ ràng mong muốn, và cách đúng hoặc cách duy nhất để đưa tính năng đó vào là một keyword mới, thì vì quy tắc tuyệt đối không được phá vỡ tương thích mã nguồn, tính năng đó sẽ mãi mãi không thể được thêm. Tôi đồng ý với các thay đổi lớn làm đổi ngữ nghĩa của ngôn ngữ, nhưng thay đổi không tương thích ngược không phải lúc nào cũng là thay đổi khổng lồ
    • Edition của Rust là một phản ví dụ hay. Khác biệt giữa các edition là breaking change, nhưng trên thực tế không quá lớn
      Về lý thuyết, ý tưởng rằng tương thích ngược tuyệt đối không thay đổi nghe rất tốt, nhưng trong thực tế cũng có những breaking change có ý nghĩa. Việc phải gánh vĩnh viễn một tính năng ngôn ngữ vì lúc thiết kế ban đầu chưa xét đến X hay Y không hẳn lúc nào cũng là lợi ích
    • Ở cuối bài đã nói như vậy rồi. Go 2 theo nghĩa cắt đứt với quá khứ và không còn biên dịch các chương trình cũ nữa sẽ tuyệt đối không xảy ra, còn Go 2 theo nghĩa một đợt sửa đổi lớn của Go 1 bắt đầu hướng tới từ năm 2017 thì đã xảy ra rồi
  • Tôi dùng Go rất nhiều, và hướng đi này thật sự khiến tôi thấy ấm lòng
    Tính tương thích có thể không thú vị lắm đối với đội ngũ ngôn ngữ. Vì họ luôn phải đặt chắc một chân ở “quá khứ xa xôi”. Nhưng với người phải duy trì các hệ thống Go lớn, đó thật sự là một món quà lớn

    • Tôi đã làm việc với trình biên dịch Go trong vài năm, và chuyện đó không phải vấn đề quá lớn. Chúng tôi suy nghĩ cẩn trọng và từ chối rất nhiều ý tưởng
      Nếu không thể đưa vào cho đúng cách thì nghĩa là nó chưa đúng, nên chúng tôi thử lại; nếu vẫn không khớp thì có lẽ chúng tôi chưa hiểu vấn đề đủ rõ, vì vậy nên để nó chín muồi lâu hơn
      Tôi thật sự thích được làm việc với những người suy nghĩ cẩn trọng và cố gắng để các ý tưởng đúng được đưa vào. Tôi biết ơn việc Russ đóng vai trò BDFL, cũng như được làm việc với Ian, Rob, Rob; nhờ vậy tôi đã trở thành một kỹ sư tốt hơn nhiều
  • Bài liên quan: Forward Compatibility and Toolchain Management in Go 1.21 - https://news.ycombinator.com/item?id=37122932

  • Câu “nhàm chán là tốt. Nhàm chán là ổn định. Nhàm chán nghĩa là bạn có thể tập trung vào việc của mình mà không cần bận tâm Go đã thay đổi gì” thật sự rất thấm
    Ở công việc chính tôi dùng NodeJS và hệ sinh thái JS nói chung, và đúng là cực khổ. Hệ sinh thái bị phân mảnh, ai cũng làm theo cách riêng nên rất khó tạo ra sự ổn định. Dù vậy tôi vẫn thích công việc này, nhưng sẽ thật tốt nếu hệ sinh thái JS cũng có một nền tảng hiện đại ổn định mà ta có thể tin tưởng và phụ thuộc vào

    • Hệ sinh thái JS, ví dụ npm hay phía React, có thể như vậy. Nhưng cũng phải thừa nhận rằng JavaScript với tư cách một ngôn ngữ đã ưu tiên tương thích ngược từ trước khi Go tồn tại
    • Tôi thắc mắc vì sao Go vẫn chưa trở thành Java/.NET mới
      Rất nhiều công cụ và API đã được viết bằng Go, và việc không cần runtime riêng trên hệ thống đích thường được nêu là một lợi thế lớn. Ngôn ngữ cũng có vẻ đơn giản để học và dùng, hỗ trợ VSC và GoLand đều tốt, còn những phàn nàn phổ biến như xử lý lỗi cũng không có vẻ là khiếm khuyết chí mạng
      Tôi tò mò Go còn cần gì nữa để trở thành dòng chính của phát triển trong vài thập kỷ tới, hoặc ít nhất chiếm một phần lớn trên thị trường tuyển dụng. Vì ở một số nơi nó vẫn bị xem là ngôn ngữ ngách
    • JavaScript nổi tiếng về tương thích ngược. Chính vì vậy nó mới rơi vào trạng thái hỗn loạn mà bạn mô tả
    • Giờ tôi nghĩ thực sự đã có một nền tảng ổn định. ES modules và mã ES2020 được cả Node lẫn các trình duyệt lớn hỗ trợ. Node cũng có trình chạy test tích hợp
      Vấn đề là làm sao đưa tất cả mọi người lên được baseline này. Khi tới đó, tôi nghĩ mọi thứ sẽ tốt hơn nhiều
      Ngoài ra hệ sinh thái JS còn bao gồm cả công việc UI front-end, và phạm vi ứng dụng quá rộng nên việc có nhiều triển khai là không thể tránh khỏi. Thậm chí còn là điều nên có
  • Việc chuyển một phần mã từ Python sang Golang đã giúp ích rất nhiều cho việc mở rộng. Tôi thật sự vui khi thấy Go sẽ tiếp tục giữ lời tuyên bố cốt lõi của mình là tương thích ngược

  • Nói đến chuyện phân tích cú pháp IP mới nhớ. Tôi luôn tò mò không biết inet_ntoa của BSD ban đầu rốt cuộc được viết như thế nào
    sscanf dùng atoi/atol%d/%u thì luôn phân tích đúng theo số nguyên thập phân, nên để tạo ra hiệu ứng kỳ lạ như vậy hẳn phải dùng %i hoặc strtou theo cơ số 0

    • Không cần đoán. Trang manual inet_aton của tôi nói nó đến từ 4.3BSD: https://github.com/dank101/4.3BSD-Reno/blob/master/lib/libc/...
      inet_addr của 4.2BSD còn sớm hơn cũng dùng logic tương tự: https://github.com/dank101/4.2BSD/blob/master/lib/libc/inet/...
      inet_atoninet_addr phân tích địa chỉ theo cách tự nhiên nhất. Nếu dùng những thứ như strtoul hay đặc biệt là sscanf thì hẳn sẽ khá gượng. Vẻ đẹp của con trỏ C nằm ở chỗ nó khiến các tác vụ phân tích cú pháp đơn giản trở nên rất dễ, và có lẽ là quá dễ
    • Đọc đến đoạn đó tôi bật cười. Trước đây tôi từng đệm bằng số 0 cho từng octet để “dọn dẹp” /etc/hosts
      Kết quả là lại phải quay lại và xóa các số 0 đó
  • Với tư cách là một người thiết kế ngôn ngữ, tôi tôn trọng các lựa chọn được đưa ra ở đây, bao gồm cả quyết định không tạo ra một Go 2 thật sự
    Tôi cũng định áp dụng các kỹ thuật để đảm bảo khả năng tương thích