Go ra đời năm 2009 tại Google, từ một sự bực bội rất cụ thể: build một dịch vụ C++ lớn mất bốn mươi lăm phút. Ba người — Rob Pike, Ken Thompson, Robert Griesemer — bắt đầu phác thảo một ngôn ngữ trong lúc chờ trình biên dịch chạy. Hình dung Go như một cái bếp có thực đơn ngắn cố tình: ít món hơn hẳn, nhưng đầu bếp nào cũng làm được mọi món y như nhau, nhanh, và người mới vào ca là nấu được ngay.

Bài này không kể lịch sử. Nó trả lời câu hỏi thực tế hơn: Go đánh đổi cái gì lấy cái gì, và vì sao đánh đổi đó thắng ở mảng hạ tầng — Docker, Kubernetes, Terraform, Prometheus đều viết bằng Go.

Ba con số

Chương trình Hello World, và tôi đo bằng Go 1.23:

  build lần đầu (cache rỗng) : 1333 ms
  build lại                  :   48 ms
  kích thước binary          :  2,1 MB
  phụ thuộc động             :  not a dynamic executable

Bốn mươi tám mili giây. Đó là lý do tồn tại của ngôn ngữ này, và mọi quyết định thiết kế khác đều xoay quanh việc giữ con số đó nhỏ.

Dòng cuối quan trọng không kém: binary không phụ thuộc thư viện động nào. Không cần cài runtime, không cần JRE, không cần python3, không cần libc đúng phiên bản. Chép một tệp lên máy chủ và chạy — món ăn ra đĩa đã đậy hộp sẵn, không phải mang nguyên liệu tới lắp ráp tại nhà khách.

Đây chính là thứ làm Go thắng trong container: bài 97 của sê-ri Java đo được ảnh Docker nhỏ nhất cho một ứng dụng Java là 126 MB. Với Go, ảnh chứa đúng một binary 2,1 MB trên scratch — và ta sẽ đo nó ở bài 55.

Cross-compile là một biến môi trường

  windows/amd64   2.2M
  darwin/arm64    2.2M
  linux/386       2.0M

Ba dòng trên chạy trên cùng một máy Linux ARM, không cài thêm gì:

GOOS=windows GOARCH=amd64 go build -o app.exe

Không toolchain riêng, không máy ảo, không CI đa nền tảng. Đây là thứ khiến các công cụ dòng lệnh hay được viết bằng Go: tác giả build một lần ra mười nền tảng.

Cái giá phải trả

Không có gì miễn phí. Go đổi rất nhiều thứ để có bốn mươi tám mili giây kia.

Ngôn ngữ cố tình nhỏ. Hai mươi lăm từ khoá, so với năm mươi mấy của Java. Không nạp chồng toán tử, không nạp chồng phương thức, không giá trị mặc định cho tham số, không kế thừa. Nhiều thứ bạn quen ở Java hay Python đơn giản là không có — thực đơn ngắn, cố tình.

Mã dài hơn. Xử lý lỗi bằng if err != nil lặp đi lặp lại là thứ mọi người mới học đều phàn nàn. Ta sẽ nói kỹ ở bài 23 — có lý do cho nó, nhưng lý do đó không làm mã ngắn lại.

Generics đến muộn. Mãi tới Go 1.18 (2022) mới có, và vẫn hạn chế hơn Java. Bài 16 sẽ đo xem nó làm được gì và không làm được gì.

Binary lớn cho chương trình nhỏ. 2,1 MB cho Hello World, vì runtime của Go — bộ lập lịch goroutine, bộ thu gom rác — nằm luôn trong đó. Đổi lại là dòng "not a dynamic executable" ở trên.

Go hợp và không hợp với việc gì

Hợp: dịch vụ mạng và API, công cụ dòng lệnh, hạ tầng và DevOps, thứ gì cần nhiều kết nối đồng thời, và bất cứ khi nào "chép một tệp lên là chạy" có giá trị.

Không hợp: giao diện người dùng, tính toán khoa học và học máy (hệ sinh thái Python vượt xa), miền nghiệp vụ rất phức tạp cần mô hình hoá sâu (hệ thống kiểu của Go hơi nghèo cho việc đó), và những dự án mà thư viện sẵn có chỉ tồn tại ở nền tảng khác.

Một cách nói tôi thấy đúng: Go được thiết kế cho đội đông người viết dịch vụ, không phải cho cá nhân viết mã tinh xảo. Cú pháp nghèo là có chủ đích — nó làm mã của người khác dễ đọc, và làm số cách viết một việc giảm xuống còn một. Một bếp thực đơn ngắn, ai vào cũng nấu ra cùng một món.

Ba thứ Go làm khác hẳn

Ba điều này sẽ chiếm phần lớn sê-ri, nên tôi nêu trước để bạn biết cái gì đang tới:

Đồng thời nằm trong ngôn ngữ. go f() khởi động một goroutine, và channel là kiểu dữ liệu có sẵn với cú pháp riêng. Không phải thư viện — là từ khoá. Bài 31 tới 42 nói về chuyện này, và tôi sẽ đo chi phí thật của một goroutine.

Lỗi là giá trị, không phải ngoại lệ. Không có try/catch. Hàm trả về hai giá trị, cái thứ hai là error, và bạn kiểm nó. Bài 23.

Interface được thoả mãn ngầm định. Không có implements. Một kiểu thoả mãn interface chỉ vì nó có đủ phương thức. Bài 13 — và đây là ý tưởng tôi thấy hay nhất của ngôn ngữ.

Về sê-ri này

Sáu mươi bài, đi từ cú pháp tới đưa dịch vụ lên sản xuất. Nguyên tắc giống sê-ri Java trước đó: không viết điều chưa chạy thử. Mọi con số trong sê-ri đều đo được, và khi phép đo đi ngược lời khuyên phổ biến, tôi sẽ nói rõ.

Bạn cần Go 1.21 trở lên. Bài sau sẽ cài.

Nếu máy bạn đã có Go, tự bấm giờ hai con số định nghĩa ngôn ngữ này trong ba mươi giây:

echo 'package main
import "fmt"
func main(){ fmt.Println("chào") }' > /tmp/c.go
cd /tmp && go mod init tmp 2>/dev/null; time go build -o c c.go && ls -lh c

Con số real in ra và kích thước tệp là hai thứ định nghĩa Go. Nếu chúng làm bạn thấy đáng, sê-ri này dành cho bạn.

Mẫu số chung

Một ngôn ngữ — mọi công cụ — là một bó đánh đổi có chủ đích chỉnh cho một mục tiêu, và "tốt nhất" vô nghĩa nếu không kèm "cho việc gì". Go mua tốc độ biên dịch, khả năng triển khai, và tính dễ đọc xuyên đội bằng cách trả bằng sức biểu đạt và tính năng — đúng món hời cho hạ tầng và dịch vụ, sai món cho giao diện, học máy, hay mô hình miền nghiệp vụ sâu. Ngôn ngữ nào cũng là một cuộc mặc cả như vậy: Rust mua an toàn và kiểm soát bằng thời gian biên dịch và độ dốc học; Python mua sức biểu đạt và hệ sinh thái bằng tốc độ và sức nặng triển khai; Java mua công cụ và hệ sinh thái bằng nghi thức rườm rà. Chọn ngôn ngữ có đánh đổi khớp với ràng buộc lớn nhất của bài toán, và biết rõ mình đã bỏ đi cái gì — cùng phán đoán công-cụ-theo- bài-toán như sức nặng framework hay render-máy-chủ-so-với-SPA.

Điều thứ hai, hai cái thắng của Go đáng học ở bất kỳ ngôn ngữ nào. Vòng phản hồi nhanh cộng dồn theo cấp số nhân: một bản build 45 phút đánh thuế lên mọi lần lặp, một bản 48ms đổi cả cách bạn nghĩ — cùng lý do test phải nhanh, CI phải gọn, hot-reload tồn tại; hãy rút ngắn quãng giữa sửa và biết kết quả. Và triển khai tự-đủ giết sạch một lớp lỗi "chạy tốt trên máy tôi": một binary tĩnh, như một ảnh container hay một fat jar, mang theo cả thế giới của nó, và cái phụ thuộc bạn không phải cài ở đích đến là cái không bao giờ sai phiên bản được. Rút ngắn vòng lặp; và giao hiện vật kèm phụ thuộc của nó, đừng giao một danh sách phụ thuộc.

Ngày mai: cài Go cho tử tế, và hiểu go run khác go build chỗ nào.