Quy ước đặt tên Convention: Hướng dẫn thực hành
Go Naming Conventions: A Practical Guide
Bài viết này đi sâu vào các quy ước đặt tên trong Go, một yếu tố cực kỳ quan trọng để viết code rõ ràng và dễ bảo trì. Nó phác thảo các quy tắc nghiêm ngặt cho định danh (identifier), bao gồm các ký tự cho phép (chữ cái, chữ số, dấu gạch dưới), không được bắt đầu bằng chữ số và không được trùng với từ khóa (keyword) của ngôn ngữ. Về các hướng dẫn thực tế, các lập trình viên nên áp dụng `camelCase` cho các định danh không xuất khẩu (unexported) và `PascalCase` cho các định danh xuất khẩu (exported). Việc xử lý các từ viết tắt (acronym) cần nhất quán, tránh sử dụng ký tự không phải ASCII, và đặc biệt là không được trùng lặp với các kiểu dữ liệu (built-in types), hàm (functions) hay tên gói (imported package names) có sẵn trong Go. Nói tóm lại, việc hiểu và áp dụng đúng các quy ước đặt tên này sẽ giúp code trở nên dễ đọc hơn rất nhiều, đồng thời giảm thiểu các lỗi tiềm ẩn.
Chọn đúng tên trong cơ sở mã của bạn là một phần quan trọng (và đôi khi khó khăn!) Trong quá trình lập trình trong Go. Đó là một điều nhỏ nhưng tạo nên sự khác biệt lớn — những cái tên hay làm cho mã của bạn rõ ràng hơn, hơn nữa...
Chọn đúng tên trong cơ sở mã của bạn là một phần quan trọng (và đôi khi khó khăn!) của việc lập trình trong Go. Đó là một điều nhỏ tạo nên sự khác biệt lớn — những cái tên hay làm cho mã của bạn rõ ràng hơn, dễ dự đoán hơn và dễ điều hướng hơn; những cái tên xấu làm điều ngược lại.
Go có những quy ước khá mạnh mẽ — và một vài quy tắc cứng nhắc — để đặt tên cho mọi thứ. Trong bài đăng này, chúng tôi sẽ giải thích các quy tắc và quy ước này, cung cấp một số mẹo thiết thực và trình bày một số ví dụ về tên tốt và tên xấu trong Go. Nếu bạn chưa quen với ngôn ngữ, tất cả thông tin này có thể cảm thấy như rất nhiều để tiếp nhận, nhưng nó sẽ nhanh chóng trở thành bản chất thứ hai với một chút thực hành 😊
Mã định danh
Hãy bắt đầu với các quy tắc cứng cho mã định danh. Theo định danh, ý tôi là các tên mà bạn sử dụng cho các biến, hằng số, loại, hàm, tham số, trường cấu trúc, phương thức và trình nhận trong mã của bạn.
- Mã định danh chỉ có thể chứa các chữ cái unicode, chữ số và dấu gạch dưới.
- Mã định danh không thể bắt đầu bằng một chữ số.
- Bạn không thể sử dụng bất kỳ từ khóa Go nào sau đây làm định danh:
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
Miễn là bạn tuân thủ ba quy tắc đó, bất kỳ tên định danh nào cũng hợp lệ về mặt kỹ thuật và mã của bạn sẽ biên dịch tất cả OK. Nhưng có một loạt các hướng dẫn khác mà bạn nên tuân theo:
Bạn nên sử dụng
camelCasecho số nhận dạng chưa xuất hoặcPascalCasecho số nhận dạng đã xuất. Không sử dụng các biến thể vỏ thay thế nhưsnake_case,Pascal_Snake_Case,SCREAMING_SNAKE_CASEhoặcALLUPPERCASE.Các từ viết tắt hoặc viết tắt (như API, URL hoặc http) nên sử dụng một trường hợp nhất quán trong định danh. Vì vậy, ví dụ,
apiKeyhoặcAPIKeylà thông thường, nhưngApiKeythì không. Quy tắc này cũng áp dụng choIDkhi nó được sử dụng làm tốc ký cho các từ "nhận dạng" hoặc "định danh" — nghĩa là ghiuserIDthay vìuserId.Mặc dù tất cả các chữ cái unicode đều được cho phép, nhưng việc sử dụng các chữ cái không phải ASCII thường có thể khiến mã của bạn khó đọc hơn và khó viết hơn, và rất hiếm khi thấy chúng được sử dụng. Trừ khi bạn có một trường hợp sử dụng thực sự thích hợp, bạn nên sử dụng các chữ cái ASCII trong số nhận dạng. Ví dụ: sử dụng
pithay vìπ, sử dụngbetathay vìβ, sửdụng naiveBayesthay vìnaïveBayes.Để tránh nhầm lẫn cho người đọc và các lỗi tiềm ẩn, tránh chọn số nhận dạng xung đột với Go's loại tích hợp. Vì vậy, ví dụ, không tạo các biến có tên như
int,boolhoặcbất kỳ. Tương tự, tránh tạo các hàm có tên xung đột với các hàm tích hợp của Go. Vì vậy, ví dụ: không tạo các hàm có tên nhưmin,max,lenhoặcclear.Nói chung, tránh đưa loại vào số nhận dạng — ví dụ: không sử dụng các tên
nhưfullNameString,scoreInthoặcfloat64Amount. Ngoại lệ chính cho điều này là khi bạn phải chuyển đổi một biến sang một loại khác và bạn muốn phân biệt giữa biến ban đầu và biến có chứa giá trị được chuyển đổi. Trong tình huống này, bao gồm cả loại trong định danh là một cách phổ biến và chấp nhận được để phân biệt giữa hai loại. Ví dụ: mã như thế này là OK:userID := 42 userIDStr := strconv.Itoa(userID)
Nếu có thể, hãy cố gắng tránh chọn các mã định danh xung đột với tên gói thư viện tiêu chuẩn. Đây là một quy ước 'mềm hơn' so với các quy ước khác vì thư viện tiêu chuẩn đánh cắp rất nhiều tên định danh tốt — chẳng hạn như
json,js,mail,user,csv,path,filepath,log,regexp,timevàurl— và đôi khi có thể khó đưa ra các lựa chọn thay thế hợp lý. Tuy nhiên, bạn chắc chắn nên tránh tạo các mã định danh xung đột với tên gói mà mã của bạn đang thực sự nhập và sử dụng. Vì vậy, ví dụ: nếu bạn đang viết mã nhập khẩuurlvà góinet/mail, thì đừng sử dụng các từurlvàmaillàm định danh trong mã đó.
Dưới đây là một vài ví dụ về tên mã định danh tốt và xấu:
| Tệ | Lý do | Tốt hơn |
|---|---|---|
order.total := 99.99func load-user() |
Không cho phép dấu chấm câu | orderTotal := 99.99func loadUser() |
const 3rdParty = "x"func 2FactorAuth() |
Không thể bắt đầu bằng một chữ số | const thirdParty = "x"func twoFactorAuth() |
max_value := 10func Fetch_user() |
Vỏ bọc không đạt tiêu chuẩn | maxValue := 10func FetchUser() |
loại HttpClient struct{}func parseXml() |
Vỏ chữ viết tắt không nhất quán | loại HTTPClient struct{}func parseXML() |
func GetSessionId()loại OrderId string |
Giấy tờ tùy thân phải là tất cả các chữ hoa | func GetSessionID() loại Chuỗi ID thứ tự |
sơ yếu lý lịch:= 2const Σ = 100 |
Các chữ cái không phải ASCII | resumeCount := 2const sum = 100 |
func clear()int := cache.Internal() |
Xung đột với các loại hoặc chức năng tích hợp | func clearQueue()data := cache.Internal() |
intCount := 42resultSlice := []int{} |
Loại có trong tên | count := 42kết quả := []int{} |
kiểu json struct{}var log = newLogger() |
Xung đột với tên gói stdlib | loại tải trọng cấu trúc{}var logger = newLogger() |
Số nhận dạng đã xuất và chưa xuất
Mã định danh trong cờ vây phân biệt chữ hoa chữ thường. Ví dụ: các số nhận dạng apiKey, apikey và APIKey đều khác nhau.
Như bạn có thể đã biết, khi một mã định danh bắt đầu bằng một chữ in hoa, nó sẽ được xuất — nghĩa là mã sẽ hiển thị bên ngoài gói mà nó được khai báo. Điều này có nghĩa là vỏ của chữ cái đầu tiên là đáng kể. Nó ảnh hưởng đến hành vi của cơ sở mã của bạn. Đổi lại, điều này có nghĩa là bạn không nên bắt đầu định danh bằng chữ in hoa chỉ vì chúng trông đẹp mắt — bạn chỉ nên bắt đầu chúng bằng chữ in hoa nếu bạn muốn chúng được xuất và có thể truy cập để mã bên ngoài gói mà chúng được khai báo.
Như một mẹo, hãy thử viết các gói bằng cách sử dụng mã định danh chưa được xuất theo mặc định. Chỉ xuất chúng khi bạn thực sự có nhu cầu.
Thông thường, bạn xuất càng ít, việc cấu trúc lại mã trong một gói càng dễ dàng mà không ảnh hưởng đến các phần khác trong cơ sở mã của bạn. Có một trích dẫn hay từ The Pragmatic Programmer, mà tôi sẽ điều chỉnh một chút cho danh pháp Go:
Viết mã nhút nhát - các gói không tiết lộ bất cứ điều gì không cần thiết cho các gói khác và không dựa vào các triển khai của các gói khác.
Mẹo thứ hai là rất hiếm khi một gói hàng chính được nhập bằng bất cứ thứ gì, vì vậy các mã định danh trong đó thường không được xuất và bắt đầu bằng chữ thường. Ngoại lệ thường gặp nhất là khi bạn cần xuất trường cấu trúc để hiển thị cho các gói sử dụng phản chiếu để hoạt động, như encoding/json, encoding/gob hoặc github.com/jmoiron/sqlx.
Độ dài và tính mô tả của bộ định danh
Nói chung, một mã định danh được sử dụng càng xa nơi nó được khai báo, tên càng mang tính mô tả.
Nếu bạn có một mã định danh có phạm vi hẹp và chỉ được sử dụng gần nơi nó được khai báo, bạn thường có thể sử dụng một tên ngắn và không mang tính mô tả. Ví dụ: nếu bạn đang đặt tên cho thứ gì đó chỉ được sử dụng trong một vòng lặp for nhỏ, khối phạm vi hoặc hàm rất ngắn, thì việc sử dụng tên ngắn hoặc thậm chí một chữ cái là rất phổ biến trong Go.
Nhưng nếu bạn đang đặt tên cho một cái gì đó có phạm vi lớn hơn hoặc được sử dụng cách xa nơi nó được khai báo, bạn nên sử dụng một cái tên mô tả rõ ràng những gì nó đại diện.
Dưới đây là một ví dụ tốt đẹp mà Dave Cheney đã đưa ra như là một phần của bài thuyết trình Thực hành của mình:
loại Person struct {
Name string
Age int
}
func AverageAge(people []Person) int {
if len(people) == 0 {
return 0
}
var count, sum int
for _, p := range people {
sum += p.Age
count += 1
}
return sum / count
}
Trong mã này, trong khối phạm vi ngắn, chúng tôi sử dụng mã định danh p để đại diện cho một giá trị trong người slice — khối phạm vi quá nhỏ và chặt đến nỗi chỉ cần sử dụng một tên chữ cái là đủ rõ ràng.
Ngược lại, các biến count và sum được khai báo, sau đó được sử dụng trong phạm vi, sau đó một lần nữa trong câu lệnh return. Cung cấp cho họ những cái tên mô tả rõ ràng hơn ngay lập tức những gì mã đang làm và những gì chúng đại diện, so với các tên chữ cái đơn lẻ như c và s. Nhưng các biến này chỉ được sử dụng bên trong hàm AverageAge, vì vậy việc đặt cho chúng những cái tên thậm chí nhiều mô tả hơn như peopleCount và agesSum sẽ không cần thiết.
Nó không phải là một khoa học chính xác, nhưng khi viết mã Go, bạn được khuyến khích sử dụng định danh độ dài phù hợp — đôi khi có thể dài và mô tả, đôi khi nó có thể ngắn gọn và ngắn gọn.
Gói đặt tên
Các quy tắc cứng cho tên gói giống như đối với số nhận dạng: chúng có thể chứa các chữ cái unicode, số và dấu gạch dưới, không được bắt đầu bằng một số và không được là từ khóa Go. Nhưng trên thực tế, các quy ước để đặt tên cho một gói hàng chặt chẽ hơn nhiều. Thông thường:
Tên gói chỉ được chứa chữ cái và số ASCII thường.
Bởi vì tên gói sẽ cần phải được gõ ra rất nhiều khi viết mã, tên lý tưởng nên ngắn gọn, dễ gõ và phản ánh nội dung của gói. Thông thường các danh từ một từ đơn giản (như
đơn đặthàng, khách hàngvàcon sên) hoạt động tốt.Nếu bạn muốn sử dụng nhiều hơn một từ trong tên gói, bạn nên nối tất cả các từ bằng chữ thường mà không có dấu phân cách. Vì vậy, ví dụ, người
quảnlý đơn hàng là một tên gói thôngthường—orderManager hoặc order_managerthì không.Nếu tên gói cảm thấy quá dài, bạn có thể sử dụng chữ viết tắt trong tên. Bạn có thể thấy điều này trong một số tên gói thư viện tiêu chuẩn,
nhưexpvar (thayvìexportvariables)vàstrconv (thay vìstringconversion).Để tránh xung đột và nhầm lẫn, hãy cố gắng tránh sử dụng cùng tên với các gói thư viện tiêu chuẩn thường được sử dụng.
Tên gói có tiền tố
.or_là 'vô hình' để đi và hoàn toàn bị bỏ qua khi bạn chạyđi xây dựng,đi chạy,đi kiểm tra, v.v. Vì vậy, đừng bắt đầu tên gói của bạn với những ký tự này, trừ khi bạn đặc biệt muốn chúng bị bỏ qua.Thư mục có tên
nhà cung cấp,testdatavànội bộcó một ý nghĩa đặc biệt trong cờ vây, vì vậy để tránh bất kỳ sự nhầm lẫn hoặc lỗi nào, đừng sử dụng những từ này làm tên gói.Tránh sử dụng các tên gói 'catch all' như
common,util,helpers,typeshoặcinterfaces, những tên này không thực sự cung cấp bất kỳ manh mối nào về nội dung của gói. Ví dụ: gói được gọi làngười trợ giúp có chứa người trợ giúpxác thực, người trợ giúp định dạng, người trợ giúp SQL không? Một sự kết hợp của tất cả những điều trên? Bạn không thể đoán được chỉ từ cái tên. Cũng như không rõ ràng, những cái tên 'bắt tất cả' này cung cấp rất ít ranh giới hoặc phạm vi tự nhiên, điều này có thể dẫn đến việc gói hàng trở thành bãi rác cho nhiều thứ khác nhau. Đổi lại, gói có thể được nhập và sử dụng trong toàn bộ cơ sở mã của bạn — điều này làm tăng nguy cơ chu kỳ nhập và có nghĩa là những thay đổi đối với gói có khả năng ảnh hưởng đến toàn bộ cơ sở mã, thay vì chỉ một phần cụ thể của nó. Nói cách khác, bắt tất cả các tên gói khuyến khích tạo các gói có 'bán kính nổ' lớn. Nếu bạn thấy mình muốn tạo một gói đồ dùnghoặctrợ giúp, hãy tự hỏi liệu bạn có thể chia nhỏ nội dung thành các gói nhỏ hơn với trọng tâm cụ thể và tên rõ ràng hơn không.
| Tệ | Lý do | Tốt hơn |
|---|---|---|
gói bên thứ3 gói 2fa |
Không thể bắt đầu bằng chữ số | gói bên thứ bagói twofa |
trình quản lý đơn hàng góigói order_manager |
Vỏ / dải phân cách không chuẩn | người quản lý đơn hàng gói |
gói othứ trong gói |
Quá mơ hồ và không có tính mô tả | đơn đặt hàng trọn góisên gói |
hệ thống quản lý đơn hàng gói |
Quá dài / khó gõ | đơn đặt hàng trọn góiđơn đặt hàng gói |
url góithư gói |
Xung đột với tên gói stdlib | liên kết góingười gửi gói hàng |
gói _cachegói .hidden |
Bỏ qua công cụ Go | bộ đệm góigói bị ẩn |
nội bộ góinhà cung cấp góidữ liệu kiểm tra gói |
Tên thư mục đặc biệt trong Go | gói nội bộauthnhà cung cấp gói |
tiện ích góitrình trợ giúp gói |
Bắt tất cả các tên có phạm vi không rõ ràng | xác thực góiđịnh dạng gói |
| Xấu | Lý do | Tốt hơn |
customer.NewCustomer()customer.CustomerOrders() |
Gọi hàm trò chuyện | customer.New()customer.Orders() |
customer.CustomerAddresscustomer.CustomerPhoneNumber |
Tham chiếu loại trò chuyện | customer.Addresscustomer.PhoneNumber |