Go có đúng hai mức truy cập, và bạn chọn bằng cách viết hoa hay viết thường chữ cái đầu.

Quy tắc

package kho

var A = 1           // xuất khẩu: package khác dùng được
var b = 2           // không xuất khẩu: chỉ trong package kho

func TimKiem() {}   // dùng được
func chuanHoa() {}  // không
  kho.A dùng được từ main vì A viết HOA
  kho.khoiTao KHÔNG dùng được vì viết thường -> lỗi biên dịch

Áp dụng cho mọi thứ: biến, hằng, hàm, kiểu, trường struct, method.

So với Java bốn mức (public, protected, default, private), Go chỉ có hai. Không có protected vì không có kế thừa; không có mức "cùng package" riêng vì mọi thứ trong cùng package đều thấy nhau, kể cả trường viết thường của struct khác.

Đơn vị đóng gói là package, không phải kiểu

Đây là điểm dễ nhầm nhất với người từ Java:

package kho

type Nguoi struct{ ten string }   // ten viết thường

func Doc(n Nguoi) string { return n.ten }   // CÙNG package -> đọc được

ten là "private", nhưng bất kỳ hàm nào trong package kho đều đọc và ghi được. Đóng gói bảo vệ ranh giới package, không bảo vệ ranh giới kiểu.

Hệ quả: nếu bạn muốn thật sự giấu một thứ, hãy đặt nó vào package riêng — chứ không phải chỉ viết thường.

internal/: mức thứ ba trên thực tế

Bài 3 đã nhắc: thư mục tên internal chỉ cho phép import từ cùng module.

github.com/toi/duan/
├── internal/kho/     <- chỉ duan/ dùng được
└── api/              <- ai cũng dùng

Đây là cách duy nhất để có "public trong dự án, private với bên ngoài" — thứ tương đương internal của C# hay module của Java 9.

Với thư viện, tôi khuyên: mặc định đặt mọi thứ vào internal/, chỉ đưa ra ngoài khi chắc chắn muốn giữ tương thích lâu dài. Cái gì đã xuất khẩu thì rất khó rút lại.

Hệ quả với JSON và phản chiếu

Đây là chỗ quy tắc chữ hoa gây bất ngờ nhất:

type Nguoi struct {
	Ten  string   // marshal được
	tuoi int      // BỊ BỎ QUA, im lặng
}

encoding/json, encoding/xml, và mọi thư viện dùng phản chiếu chỉ thấy trường xuất khẩu. Trường viết thường không được ghi ra, không được đọc vào, và không có cảnh báo nào.

Đây là lỗi tôi thấy người mới mắc rất nhiều: struct có vẻ đúng, JSON ra thiếu trường, và không biết tại sao. Bài 45 sẽ nói kỹ.

Muốn tên JSON viết thường mà trường vẫn xuất khẩu thì dùng tag:

type Nguoi struct {
	Ten string `json:"ten"`
}

Quy ước đặt tên

Go có quy ước khá chặt, và golint từng bắt bạn theo:

Không dùng gạch dưới. xuLyDonHang, không phải xu_ly_don_hang.

Viết tắt giữ nguyên chữ hoa: HTTPServer, userID, parseURL — không phải HttpServer, userId.

Tên ngắn cho phạm vi hẹp. for i, v := range chứ không phải for index, value. Biến sống một dòng thì một chữ cái là đủ; biến sống cả hàm thì nên dài hơn.

Getter không có tiền tố Get. Method đọc trường ten đặt tên là Ten(), không phải GetTen(). Setter thì vẫn SetTen().

Tên package ngắn, viết thường, không số nhiều: kho chứ không phải cac_kho hay khoUtils. Và tránh util, common, helper — chúng không nói gì cả.

Một ràng buộc bất ngờ tôi gặp khi viết bài này: đặt thư mục tên con làm Go từ chối biên dịch:

  malformed import path "b20/con": "con" disallowed as path element component on Windows

Go bảo vệ tính khả chuyển bằng cách cấm các tên thiết bị dành riêng của Windows — con, prn, aux, nul, com1com9, lpt1lpt9. Chi tiết nhỏ nhưng sẽ làm bạn mất mười phút nếu không biết.

Thử ba mươi giây

type T struct {
	A string
	b string
}
j, _ := json.Marshal(T{A: "x", b: "y"})
fmt.Println(string(j))

In ra {"A":"x"}. Trường b biến mất không dấu vết. Ba mươi giây, và bạn sẽ nhớ kiểm chữ hoa mỗi khi JSON thiếu trường.

Ngày mai: init() và thứ tự khởi tạo package.