Các quy ước dùng từ trong bản dịch, và phần giải nghĩa dễ hiểu cho những thuật ngữ hay gặp trong tài liệu Go.
Giữ nguyên tiếng Anh
| Thuật ngữ | Ghi chú |
|---|---|
| goroutine, channel, select, defer, panic, recover | từ khoá / khái niệm lõi |
| slice, map, struct, interface, pointer | dùng nguyên; lần đầu xuất hiện có thể chú thích |
| package, module, import path | hệ thống module |
| receiver, method set, embedding | OOP trong Go |
| generics, type parameter, type constraint, type inference | |
| build, test, benchmark, fuzzing, profiling, tracing | |
| garbage collector (GC), heap, stack, escape analysis | |
| race condition, data race, deadlock, mutex | |
| compiler, linker, runtime, toolchain | |
| repository, commit, branch, merge, pull request | git |
| deploy, endpoint, request, response, handler, middleware | web |
go.mod, go.sum, GOPATH, GOROOT, vendor |
giữ nguyên dạng code |
| placeholder | ký hiệu chỗ dành cho tham số, ví dụ ? hay $1; dev Việt dùng nguyên tiếng Anh |
| connection pool, prepared statement, transaction, commit, rollback | truy cập cơ sở dữ liệu |
| schema, DBMS, DSN, driver, row, scan | truy cập cơ sở dữ liệu |
| fuzzing, fuzz test, fuzz target, seed corpus, coverage | kiểm thử |
| threat model, disclosure, triage, supply chain | bảo mật |
Dịch sang tiếng Việt
| Tiếng Anh | Tiếng Việt |
|---|---|
| function | hàm |
| variable | biến |
| constant | hằng số |
| type | kiểu (dữ liệu) |
| value | giá trị |
| argument / parameter | đối số / tham số |
| return value | giá trị trả về |
| statement | câu lệnh |
| expression | biểu thức |
| loop | vòng lặp |
| condition | điều kiện |
| error | lỗi |
| error handling | xử lý lỗi |
| library | thư viện |
| dependency | phụ thuộc |
| version | phiên bản |
| release | bản phát hành |
| source code | mã nguồn |
| memory | bộ nhớ |
| performance | hiệu năng |
| concurrency | tính đồng thời |
| parallelism | tính song song |
| documentation | tài liệu |
| comment | chú thích |
| security / vulnerability | bảo mật / lỗ hổng |
| best practices | thực hành tốt |
| directory / file | thư mục / tệp |
| install | cài đặt |
| feature | tính năng |
| implement | hiện thực / cài đặt |
| declare | khai báo |
Giải nghĩa thuật ngữ
goroutine
Một việc chạy song song, nhẹ tới mức tạo hàng nghìn cái vẫn thoải mái.
Bình thường chương trình chạy từng dòng một, xong dòng này mới sang dòng kia. Thêm chữ go trước lời gọi hàm là bạn bảo Go: “cứ chạy cái này riêng đi, tôi không đợi”. Việc đó thành một goroutine. Nó giống luồng (thread) của hệ điều hành nhưng nhẹ hơn rất nhiều — một thread tốn khoảng 1 MB bộ nhớ, một goroutine chỉ vài KB, nên chạy 100.000 goroutine cùng lúc là chuyện thường.
channel
Đường ống để các goroutine gửi dữ liệu cho nhau một cách an toàn.
Khi nhiều goroutine cùng chạy, chúng cần trao đổi kết quả. Channel là cái ống: một bên bỏ giá trị vào (ch <- x), bên kia lấy ra (x := <-ch). Điểm hay là nó tự đồng bộ — bên lấy sẽ đứng chờ nếu chưa có gì, bên bỏ vào sẽ chờ nếu ống đầy. Nhờ vậy bạn không phải tự khoá/mở khoá bộ nhớ thủ công.
select
Câu lệnh chờ nhiều channel cùng lúc, cái nào có dữ liệu trước thì xử lý cái đó.
Giống switch nhưng dành riêng cho channel. Bạn liệt kê vài channel, select sẽ đợi và chạy nhánh nào sẵn sàng trước. Thường dùng để vừa nhận dữ liệu vừa canh tín hiệu dừng hoặc hết giờ.
defer
Hoãn một lệnh lại, để nó chạy ngay trước khi hàm kết thúc.
Viết defer f.Close() ngay sau khi mở tệp, thì dù hàm kết thúc bình thường hay gặp lỗi giữa chừng, tệp vẫn được đóng. Ý tưởng là đặt việc dọn dẹp ngay cạnh việc tạo ra thứ cần dọn, thay vì phải nhớ dọn ở mọi lối thoát của hàm.
panic
Lỗi nghiêm trọng làm chương trình dừng khẩn cấp.
Khác với lỗi thường (được trả về dưới dạng giá trị error để bạn kiểm tra), panic làm chương trình lập tức tháo lui và dừng hẳn, in ra dấu vết nơi hỏng. Trong Go, panic dành cho những tình huống thật sự bất thường — chia cho 0, truy cập ngoài mảng — chứ không phải cách xử lý lỗi thông thường.
recover
Bắt lại một panic để chương trình không sập.
Đặt trong một hàm defer, recover chặn panic đang lan ra và cho bạn cơ hội xử lý. Dùng khi bạn không muốn một yêu cầu hỏng làm sập cả server. Nhưng lạm dụng nó để giấu lỗi là thói quen xấu.
slice
Danh sách có thể co giãn, thêm bớt phần tử thoải mái.
Mảng (array) trong Go có kích thước cố định lúc khai báo. Slice là lớp bọc linh hoạt quanh mảng: khai báo []string không cần nói trước bao nhiêu phần tử, cứ append là nó tự lớn ra. Đây là kiểu danh sách bạn dùng gần như mọi lúc trong Go.
map
Bảng tra cứu theo cặp khoá–giá trị, như danh bạ điện thoại.
map[string]int nghĩa là tra bằng chuỗi, nhận về số. Ví dụ tra tên người ra số tuổi. Tra cứu rất nhanh bất kể bảng lớn cỡ nào. Lưu ý: thứ tự các phần tử trong map là ngẫu nhiên, đừng trông cậy vào nó.
struct
Kiểu dữ liệu tự định nghĩa, gom nhiều trường thông tin lại thành một.
Thay vì truyền rời rạc tên, tuổi, địa chỉ, bạn gom chúng thành một struct Person. Giống như tờ khai có nhiều ô. Đây là cách chính để tổ chức dữ liệu trong Go — Go không có class như Java hay C++, struct cộng với method thay thế vai trò đó.
interface
Bản mô tả “làm được những việc gì”, không quan tâm là kiểu dữ liệu nào.
Interface liệt kê một số method. Bất kỳ kiểu nào có đủ những method đó thì tự động dùng được ở nơi cần interface — không phải khai báo gì thêm. Ví dụ io.Writer chỉ đòi có method Write; tệp, kết nối mạng, vùng nhớ đều thoả, nên hàm của bạn viết một lần là chạy được với cả ba.
pointer
Địa chỉ trỏ tới một giá trị, thay vì bản sao của giá trị đó.
Truyền giá trị thường thì Go chép ra một bản mới, sửa bản chép không ảnh hưởng bản gốc. Truyền pointer (*Person) thì truyền địa chỉ, sửa là sửa vào đúng cái gốc. Dùng khi cần thay đổi dữ liệu gốc, hoặc khi dữ liệu lớn và chép lại thì tốn.
package
Một thư mục mã nguồn được nhóm lại và đặt tên.
Mọi tệp Go đều thuộc về một package, khai báo ở dòng đầu. Các tệp trong cùng thư mục thuộc cùng package và thấy được mã của nhau. Muốn dùng mã từ package khác thì import nó vào. fmt, strings, net/http đều là package của thư viện chuẩn.
module
Một tập hợp package được phát hành và đánh phiên bản cùng nhau.
Nếu package là từng thư mục thì module là cả dự án, được mô tả bởi tệp go.mod ở gốc. Module là đơn vị mà người khác tải về và ghim phiên bản. Khi bạn chạy go mod init, bạn đang tạo một module.
import path
Chuỗi định danh duy nhất để trỏ tới một package.
Ví dụ github.com/gin-gonic/gin. Thường trùng với địa chỉ nơi chứa mã nguồn, để công cụ Go biết tải ở đâu về. Đây là thứ bạn viết trong câu lệnh import.
module path
Tên của cả module, ghi ở dòng đầu tệp go.mod.
Ví dụ github.com/gin-gonic/gin. Nó là tiền tố chung cho import path của mọi package bên trong module. Nếu định cho người khác dùng, module path phải là nơi công cụ Go tải được mã về.
receiver
Giá trị mà một method gắn vào, viết trong ngoặc trước tên method.
Trong func (p Person) SayHi(), phần (p Person) là receiver — nghĩa là SayHi là method của kiểu Person, và trong thân hàm bạn gọi p để lấy dữ liệu. Nó tương đương this hoặc self ở ngôn ngữ khác, chỉ khác là Go cho bạn tự đặt tên.
method
Hàm gắn với một kiểu dữ liệu cụ thể.
Hàm thường gọi là f(x). Method gọi là x.f() — nó thuộc về x. Trong Go, bạn gắn method vào kiểu của mình bằng cách khai báo receiver. Đây là cách Go làm hướng đối tượng mà không cần class.
embedding
Nhúng một kiểu vào kiểu khác để dùng ké method của nó.
Khai báo một trường không tên trong struct, ví dụ nhúng sync.Mutex vào struct của bạn, thì struct đó tự có luôn Lock() và Unlock(). Đây là cách Go thay cho kế thừa: ghép các mảnh lại chứ không xây cây cha–con.
blank identifier
Dấu gạch dưới _, nghĩa là “tôi bỏ qua giá trị này”.
Go bắt buộc mọi biến khai báo ra phải được dùng. Khi hàm trả về hai giá trị mà bạn chỉ cần một, viết _ vào chỗ không cần: _, err := f(). Cũng dùng trong vòng lặp khi không cần chỉ số: for _, v := range list.
closure
Hàm nhớ được các biến ở nơi nó được tạo ra.
Bạn định nghĩa một hàm bên trong hàm khác, hàm con vẫn dùng được biến của hàm cha kể cả sau khi hàm cha đã kết thúc. Hữu ích khi cần tạo hàm mang sẵn một chút trạng thái, ví dụ một bộ đếm.
generics
Viết một hàm dùng được cho nhiều kiểu dữ liệu khác nhau.
Trước đây muốn có hàm tìm giá trị lớn nhất cho cả int lẫn float64 thì phải viết hai hàm giống hệt nhau. Generics (có từ Go 1.18) cho phép viết một lần với kiểu để trống, lúc gọi mới điền vào. Giảm trùng lặp mà vẫn giữ được kiểm tra kiểu lúc biên dịch.
type parameter
Chỗ để trống dành cho kiểu dữ liệu trong hàm generic.
Trong func Max[T int|float64](a, b T) T, chữ T là type parameter. Lúc viết hàm bạn chưa biết T là gì; lúc gọi hàm Go mới điền kiểu thật vào.
type constraint
Giới hạn xem type parameter được phép là những kiểu nào.
Không phải kiểu nào cũng so sánh được bằng <. Constraint là lời khai báo “T chỉ được là một trong các kiểu này”, để trình biên dịch bảo đảm mọi phép toán trong hàm đều hợp lệ.
type inference
Go tự đoán kiểu dữ liệu, bạn không cần viết ra.
Viết x := 5 thì Go tự hiểu x là int, không phải khai báo var x int = 5. Với generics cũng vậy — gọi Max(3, 7) là Go tự suy ra T chính là int.
variadic
Hàm nhận số lượng đối số tuỳ ý.
Viết ... trước kiểu tham số cuối, ví dụ func Sum(nums ...int), thì gọi Sum(1,2,3) hay Sum(1,2,3,4,5) đều được. fmt.Println chính là một hàm như vậy.
exported name
Tên viết hoa chữ đầu, nghĩa là package khác dùng được.
Go không có từ khoá public/private. Quy tắc rất đơn giản: tên bắt đầu bằng chữ in hoa (Println) thì bên ngoài package gọi được; chữ thường (doWork) thì chỉ dùng trong nội bộ package. Đổi chữ hoa thành thường là bạn vừa ẩn nó đi.
identifier
Tên do bạn đặt cho biến, hàm, kiểu, package.
Nói chung là mọi cái tên trong mã nguồn không phải từ khoá của ngôn ngữ.
compiler
Chương trình dịch mã nguồn bạn viết thành mã máy chạy được.
Bạn viết tệp .go bằng chữ người đọc được; máy tính chỉ hiểu mã máy. Compiler làm việc chuyển đổi đó, đồng thời bắt luôn các lỗi cú pháp và lỗi kiểu trước khi chương trình kịp chạy.
linker
Công cụ ghép các mảnh đã biên dịch thành một tệp chạy được duy nhất.
Sau khi compiler dịch từng package riêng, linker nối chúng lại cùng với thư viện cần dùng thành một tệp thực thi hoàn chỉnh. Go nhét tất cả vào một tệp duy nhất, nên đem tệp đó sang máy khác là chạy được ngay.
toolchain
Bộ công cụ đi kèm Go: biên dịch, kiểm thử, định dạng mã, quản lý phụ thuộc.
Khi cài Go bạn không chỉ được trình biên dịch mà cả một bộ công cụ, gọi chung bằng lệnh go: go build, go test, go fmt, go mod. Mỗi phiên bản Go đi kèm một toolchain riêng.
runtime
Phần mã của Go luôn chạy ngầm cùng chương trình của bạn.
Nó lo những việc bạn không phải tự làm: điều phối goroutine, thu gom rác, cấp phát bộ nhớ. Bạn không gọi nó trực tiếp nhưng nó luôn có mặt trong mọi chương trình Go.
build
Quá trình biến mã nguồn thành chương trình chạy được.
Chạy go build là Go biên dịch toàn bộ package cùng phụ thuộc rồi tạo ra tệp thực thi. go run thì build rồi chạy luôn, không giữ lại tệp.
binary
Tệp chương trình đã biên dịch xong, bấm vào là chạy.
Đối lập với mã nguồn. Binary Go có đặc điểm là gói sẵn mọi thứ bên trong, không cần cài thêm thư viện ở máy đích.
workspace
Cách gộp nhiều module lại để sửa chúng cùng lúc.
Bình thường mỗi module độc lập. Khi bạn đang sửa một thư viện và đồng thời sửa ứng dụng dùng thư viện đó, workspace (tệp go.work) cho phép cả hai nhìn thấy nhau ngay trên máy, khỏi phải phát hành thư viện rồi mới thử được.
vendor
Chép toàn bộ mã của các thư viện phụ thuộc vào ngay trong dự án.
Thay vì tải từ mạng mỗi lần build, go mod vendor bỏ hết mã phụ thuộc vào thư mục vendor/. Dùng khi cần build được cả lúc không có mạng, hoặc muốn kiểm soát tuyệt đối mã nào lọt vào sản phẩm.
repository
Kho chứa mã nguồn và toàn bộ lịch sử thay đổi của nó.
Thường là một dự án trên GitHub hoặc GitLab. Viết tắt là “repo”.
commit
Một lần lưu thay đổi vào lịch sử dự án (git).
Giống như đánh dấu một mốc: “đến đây mã đang như thế này”. Mỗi commit có mã định danh riêng, quay lại mốc nào cũng được.
branch
Một nhánh phát triển song song, tách khỏi dòng chính.
Bạn tách nhánh ra để làm tính năng mới mà không đụng vào mã đang chạy ổn định. Làm xong thì gộp (merge) trở lại.
pull request
Lời đề nghị gộp thay đổi của bạn vào dự án, kèm chỗ để mọi người xem xét.
Bạn sửa trên nhánh riêng rồi mở pull request; người bảo trì đọc, góp ý, rồi mới gộp vào. Viết tắt là PR. Riêng dự án Go không dùng PR mà dùng hệ thống Gerrit.
semantic version
Cách đánh số phiên bản dạng v1.2.3, mỗi số mang một ý nghĩa.
Số đầu (major) đổi khi có thay đổi phá vỡ tương thích — mã cũ sẽ hỏng. Số giữa (minor) đổi khi thêm tính năng mà vẫn tương thích. Số cuối (patch) đổi khi chỉ sửa lỗi. Nhìn số là đoán được nâng cấp có rủi ro không.
pseudo-version
Số phiên bản tự sinh cho module chưa gắn nhãn phát hành.
Trông như v0.0.0-20230101120000-abcdef123456, ghép từ thời điểm và mã commit. Go dùng nó khi bạn phụ thuộc vào một commit cụ thể chứ không phải bản phát hành chính thức.
checksum
Chuỗi ký tự tóm tắt nội dung tệp, dùng để phát hiện tệp bị đổi.
Tệp go.sum lưu checksum của mọi phụ thuộc. Lần sau tải về, Go tính lại và đối chiếu — khác nhau nghĩa là mã đã bị thay đổi so với lần đầu, và Go sẽ báo lỗi thay vì chạy tiếp.
unit test
Đoạn mã tự động kiểm tra một mẩu chức năng có chạy đúng không.
Bạn viết hàm tên bắt đầu bằng Test trong tệp kết thúc bằng _test.go, rồi chạy go test. Nó gọi mã của bạn với dữ liệu biết trước và so kết quả. Sửa mã về sau mà lỡ làm hỏng thứ gì, test sẽ báo ngay.
benchmark
Phép đo xem một đoạn mã chạy nhanh chậm thế nào.
Giống test nhưng đo tốc độ thay vì đo đúng sai. Go chạy đoạn mã rất nhiều lần rồi báo thời gian trung bình mỗi lần. Dùng để đối chiếu trước và sau khi tối ưu.
coverage
Tỉ lệ phần trăm mã nguồn được test chạm tới.
Coverage 80% nghĩa là khi chạy test, 80% số dòng mã được thực thi, 20% còn lại chưa ai kiểm. Con số cao không đảm bảo mã đúng, nhưng con số thấp thì chắc chắn có vùng chưa được kiểm tra.
fuzzing
Kỹ thuật tự sinh hàng loạt dữ liệu đầu vào lạ để tìm chỗ vỡ.
Test thường thì bạn tự nghĩ ra vài trường hợp. Fuzzing để máy tự chế ra vô số đầu vào kỳ quặc — chuỗi rỗng, ký tự lạ, số cực lớn — rồi ném vào xem mã có sập không. Rất hiệu quả với các trường hợp biên mà con người không nghĩ ra.
fuzz target
Hàm mà bộ fuzzing sẽ ném dữ liệu ngẫu nhiên vào.
Bạn viết nó giống hàm test nhưng nhận thêm tham số đầu vào; Go sẽ gọi nó liên tục với dữ liệu tự sinh.
seed corpus
Bộ dữ liệu mẫu ban đầu để fuzzing lấy đó biến tấu ra cái mới.
Bạn đưa vài đầu vào hợp lệ làm điểm xuất phát, fuzzing sẽ đột biến từ đó ra các biến thể lạ. Có mẫu tốt thì nó tìm ra lỗi nhanh hơn nhiều so với mò từ số không.
doc comment
Chú thích viết ngay trên hàm hoặc kiểu, sẽ thành tài liệu tự động.
Go quy ước: chú thích đặt sát ngay trên khai báo và bắt đầu bằng chính tên của thứ đó (// Hello trả về lời chào...). Công cụ sẽ gom những chú thích này thành trang tài liệu mà bạn thấy trên pkg.go.dev.
gofmt
Công cụ tự định dạng lại mã Go theo một chuẩn duy nhất.
Cộng đồng Go không tranh cãi về thụt lề hay đặt dấu ngoặc ở đâu, vì gofmt quyết hết. Chạy nó là mã của mọi người trông như nhau. Hầu hết trình soạn thảo chạy nó tự động mỗi lần bạn lưu tệp.
godoc
Hệ thống sinh tài liệu từ chính chú thích trong mã nguồn.
Không cần viết tài liệu ở nơi riêng rồi lo nó lạc hậu — tài liệu nằm ngay cạnh mã. Trang pkg.go.dev chính là kết quả của cơ chế này.
issue
Một mục báo lỗi hoặc đề xuất trên hệ thống theo dõi của dự án.
Mỗi issue có số riêng để tiện dẫn chiếu, ví dụ “đã sửa ở issue #12345”.
release
Một bản phát hành chính thức, được gắn số phiên bản.
Khác với mã đang phát triển dở. Người dùng thường chỉ ghim vào các bản release chứ không dùng mã mới nhất trên nhánh chính.
garbage collector
Bộ dọn rác: tự tìm và thu hồi vùng nhớ không còn ai dùng.
Ở C bạn phải tự xin bộ nhớ rồi tự trả lại, quên trả là rò rỉ, trả hai lần là sập. Go có bộ dọn rác chạy ngầm: nó rà xem vùng nhớ nào không còn biến nào trỏ tới nữa, rồi thu hồi. Bạn được rảnh tay, đổi lại chương trình tốn thêm chút CPU cho việc dọn.
heap
Vùng nhớ chung, dùng cho dữ liệu cần sống lâu hơn hàm tạo ra nó.
Khi một giá trị còn được dùng sau khi hàm đã kết thúc, nó phải nằm ở heap chứ không thể nằm ở stack. Heap là nơi bộ dọn rác làm việc.
stack
Vùng nhớ riêng của mỗi lần gọi hàm, hàm xong là tự mất.
Biến cục bộ thường nằm ở đây. Ưu điểm là cực nhanh và không cần dọn rác — hàm kết thúc thì toàn bộ tự biến mất. Mỗi goroutine có stack riêng, ban đầu rất nhỏ rồi tự lớn dần khi cần.
live heap
Phần dữ liệu trên heap vẫn đang được dùng tại thời điểm dọn rác.
Đây là phần bộ dọn rác phải giữ lại; phần còn lại là rác cần thu hồi. Live heap càng lớn thì mỗi vòng dọn càng tốn công.
escape analysis
Trình biên dịch tự xét xem một giá trị được để ở stack hay heap.
Nếu giá trị chỉ dùng trong hàm thì để ở stack cho nhanh. Nếu nó “thoát” ra ngoài — được trả về, hay bị con trỏ khác giữ — thì phải đẩy lên heap. Bạn không điều khiển việc này, nhưng biết nó giúp hiểu vì sao mã của mình cấp phát nhiều.
GOGC
Nút chỉnh tần suất dọn rác.
Mặc định 100, nghĩa là để heap phình gấp đôi phần đang dùng rồi mới dọn. Đặt thấp hơn thì dọn thường xuyên hơn, tốn CPU nhưng ít tốn bộ nhớ. Đặt cao hơn thì ngược lại.
GOMEMLIMIT
Trần bộ nhớ mềm, ép bộ dọn rác làm việc chăm hơn khi sắp chạm trần.
Hữu ích khi chạy trong container có giới hạn RAM — thay vì bị hệ điều hành giết vì hết bộ nhớ, chương trình sẽ tự dọn gắt hơn để cầm cự.
latency
Độ trễ: một yêu cầu mất bao lâu mới có kết quả.
Đo cho từng lần. Người dùng cảm nhận rất rõ chỉ số này — trang web phản hồi trong 50 mili giây thì mượt, 2 giây thì khó chịu.
throughput
Thông lượng: xử lý được bao nhiêu việc trong một đơn vị thời gian.
Đo tổng thể, ví dụ 10.000 yêu cầu mỗi giây. Latency và throughput hay đánh đổi nhau: gom việc lại xử lý hàng loạt thì thông lượng tăng nhưng mỗi việc phải chờ lâu hơn.
profiling
Đo đạc chương trình lúc đang chạy để tìm chỗ tốn kém.
Thay vì đoán mò chỗ nào chậm, bạn cho chương trình tự ghi lại thời gian CPU hoặc bộ nhớ tiêu ở đâu, rồi xem báo cáo. Gần như luôn có bất ngờ — chỗ bạn nghi thường không phải thủ phạm.
profile
Bản ghi kết quả đo được từ việc profiling.
Một tệp dữ liệu cho biết hàm nào ngốn bao nhiêu CPU hoặc cấp phát bao nhiêu bộ nhớ. Xem bằng công cụ go tool pprof.
PGO
Tối ưu hoá dựa trên profile thực tế của chương trình.
Bạn đưa cho trình biên dịch một profile thu được lúc chạy thật, nó biết hàm nào được gọi nhiều nhất và ưu tiên tối ưu những hàm đó. Thường nhanh thêm vài phần trăm mà không phải sửa dòng mã nào.
inlining
Trình biên dịch chép thẳng thân hàm nhỏ vào chỗ gọi, bỏ qua chi phí gọi hàm.
Mỗi lần gọi hàm đều tốn một chút. Với hàm rất nhỏ, chi phí gọi có khi lớn hơn cả việc hàm làm, nên trình biên dịch chép nội dung vào thẳng nơi gọi.
data race
Hai goroutine cùng đụng vào một vùng nhớ, ít nhất một bên ghi, không có đồng bộ.
Đây là loại lỗi khó chịu nhất trong lập trình đồng thời: chương trình chạy đúng 99 lần rồi sai lần thứ 100, tuỳ vào goroutine nào nhanh hơn. Kết quả có thể là dữ liệu sai hoặc sập hẳn.
race detector
Công cụ có sẵn của Go giúp phát hiện data race lúc chạy.
Chạy go test -race hoặc go run -race, Go sẽ theo dõi mọi truy cập bộ nhớ và báo ngay khi thấy hai goroutine đụng nhau không an toàn, kèm chỉ rõ dòng nào. Chậm hơn bình thường nhiều nên chỉ bật lúc kiểm thử.
mutex
Khoá, bảo đảm mỗi lúc chỉ một goroutine được vào vùng mã nhất định.
Gọi Lock() trước khi động vào dữ liệu chung, Unlock() sau khi xong. Goroutine khác đến sẽ phải xếp hàng đợi. Cách này tránh được data race, nhưng Go khuyến khích dùng channel trước khi nghĩ đến mutex.
deadlock
Các bên khoá lẫn nhau, không ai đi tiếp được, chương trình treo.
Ví dụ A giữ khoá 1 rồi đợi khoá 2, trong khi B giữ khoá 2 rồi đợi khoá 1. Cả hai đứng im vĩnh viễn.
race condition
Kết quả phụ thuộc vào việc bên nào chạy nhanh hơn, nên lúc đúng lúc sai.
Khái niệm rộng hơn data race. Nói chung là khi tính đúng đắn của chương trình phụ thuộc vào thứ tự xảy ra mà bạn không kiểm soát được.
connection pool
Bể kết nối dùng lại, thay vì mở kết nối mới cho mỗi truy vấn.
Mở một kết nối tới cơ sở dữ liệu khá tốn thời gian. Pool giữ sẵn vài kết nối đã mở; cần thì mượn, xong thì trả lại chứ không đóng. Trong Go, sql.DB chính là một pool, không phải một kết nối đơn lẻ.
prepared statement
Câu lệnh SQL gửi trước cho cơ sở dữ liệu, chừa chỗ trống để điền giá trị sau.
Cơ sở dữ liệu phân tích câu lệnh một lần rồi dùng lại nhiều lần với giá trị khác nhau — nhanh hơn. Quan trọng hơn: giá trị đi riêng khỏi câu lệnh nên không thể bị chèn mã độc, đây là cách chặn SQL injection.
placeholder
Ký hiệu chừa chỗ cho giá trị trong câu lệnh SQL, thường là ? hoặc $1.
Bạn viết WHERE id = ? rồi truyền giá trị riêng. Tuyệt đối đừng tự ghép chuỗi SQL bằng dữ liệu người dùng — đó là lỗ hổng SQL injection.
transaction
Nhóm nhiều thao tác cơ sở dữ liệu thành một khối: hoặc thành công tất, hoặc hỏng tất.
Chuyển tiền cần trừ tài khoản A và cộng tài khoản B. Nếu trừ xong mà cộng lỗi thì tiền bốc hơi. Transaction bảo đảm hai việc đó cùng thành công (commit) hoặc cùng bị huỷ bỏ (rollback).
commit (cơ sở dữ liệu)
Xác nhận: ghi mọi thay đổi trong transaction xuống thật.
Trước khi commit, thay đổi của bạn chưa ai khác thấy. Đừng nhầm với commit của git.
rollback
Huỷ bỏ: vứt hết thay đổi trong transaction, trả về như chưa có gì.
Dùng khi giữa chừng gặp lỗi. Thói quen tốt là defer tx.Rollback() ngay sau khi mở transaction — nếu đã commit rồi thì lệnh này vô hại.
driver
Thư viện làm cầu nối giữa Go và một loại cơ sở dữ liệu cụ thể.
Go cung cấp giao diện chung database/sql, nhưng mỗi hệ MySQL, Postgres, SQLite nói một thứ tiếng riêng. Driver lo phần phiên dịch. Bạn import driver vào là dùng được, mã phía trên không phải đổi.
DSN
Chuỗi mô tả cách kết nối tới cơ sở dữ liệu.
Gói gọn địa chỉ máy chủ, cổng, tên cơ sở dữ liệu, tài khoản mật khẩu trong một chuỗi. Vì có mật khẩu nên đừng bao giờ ghi thẳng vào mã nguồn — để trong biến môi trường.
schema
Bản thiết kế của cơ sở dữ liệu: có bảng nào, cột nào, kiểu gì.
Giống bản vẽ cấu trúc. Thay đổi schema (thêm cột, đổi kiểu) trên hệ thống đang chạy là việc cần cẩn thận.
row
Một dòng dữ liệu trong bảng.
Truy vấn trả về các row; bạn dùng Scan để lấy giá trị từng cột ra biến Go.
scan
Đọc giá trị các cột của một dòng vào biến Go.
Bạn truyền con trỏ tới các biến, Go điền giá trị vào. Thứ tự biến phải khớp thứ tự cột trong câu truy vấn.
endpoint
Một địa chỉ cụ thể trên máy chủ mà client gọi tới.
Ví dụ GET /albums để lấy danh sách, POST /albums để thêm mới. Mỗi endpoint làm một việc.
handler
Hàm xử lý một yêu cầu gửi tới máy chủ.
Khi có yêu cầu đến một đường dẫn, máy chủ gọi handler tương ứng; handler đọc yêu cầu và viết ra phản hồi.
middleware
Lớp xử lý chen giữa, chạy trước hoặc sau handler chính.
Dùng cho những việc lặp lại ở mọi yêu cầu: ghi log, kiểm tra đăng nhập, đo thời gian. Viết một lần rồi áp cho tất cả, khỏi lặp trong từng handler.
request
Yêu cầu client gửi lên máy chủ.
Gồm phương thức (GET, POST…), đường dẫn, phần đầu (header) và có thể kèm dữ liệu.
response
Phản hồi máy chủ trả về cho client.
Gồm mã trạng thái (200 là ổn, 404 là không tìm thấy, 500 là lỗi máy chủ), header và nội dung.
marshal
Chuyển giá trị Go thành định dạng truyền đi được, ví dụ JSON.
Struct trong bộ nhớ không gửi qua mạng được. json.Marshal biến nó thành chuỗi JSON. Chiều ngược lại là unmarshal.
unmarshal
Đọc dữ liệu JSON và đổ ngược vào struct Go.
Bạn nhận về một chuỗi JSON, json.Unmarshal phân tích rồi điền vào các trường của struct.
struct tag
Ghi chú nhỏ gắn sau trường struct, hướng dẫn thư viện xử lý trường đó.
Ví dụ Name string json:“ten”`` nghĩa là khi chuyển sang JSON, trường Name sẽ mang tên ten. Dùng nhiều với JSON và với thư viện cơ sở dữ liệu.
vulnerability
Lỗ hổng bảo mật: điểm yếu kẻ xấu có thể lợi dụng.
Mỗi lỗ hổng đã biết có mã định danh riêng. Chạy govulncheck để xem dự án của bạn có dùng thư viện nào chứa lỗ hổng không.
threat model
Bản liệt kê: ai có thể tấn công, bằng cách nào, và ta phòng tới đâu.
Không có hệ thống nào an toàn tuyệt đối trước mọi kiểu tấn công. Threat model nói rõ ta chống loại nào và chấp nhận rủi ro nào, để mọi người cùng hiểu giới hạn.
supply chain
Chuỗi cung ứng phần mềm: mọi thư viện và công cụ mà sản phẩm của bạn phụ thuộc.
Mã của bạn có thể sạch, nhưng nó gọi thư viện A, A lại gọi B, B gọi C. Kẻ xấu chiếm được C là chạm tới được bạn. Đó là lý do cần theo dõi phụ thuộc và khoá checksum.
telemetry
Việc thu thập dữ liệu sử dụng để cải thiện công cụ.
Telemetry của Go chỉ đếm số lần các tính năng được dùng, không lấy mã nguồn hay dữ liệu của bạn, và mặc định tắt — bạn phải chủ động bật.
opt-in
Mặc định tắt, bạn phải chủ động bật thì mới có.
Ngược lại là opt-out: mặc định bật, muốn tắt thì phải tự đi tắt.
IDE
Phần mềm soạn mã tích hợp sẵn nhiều công cụ: gõ mã, gỡ lỗi, chạy test.
Viết tắt của Integrated Development Environment. Khác trình soạn thảo văn bản thường ở chỗ nó hiểu mã của bạn — gợi ý tên hàm, báo lỗi ngay khi gõ, nhảy tới định nghĩa. Với Go thì VS Code và GoLand là hai lựa chọn phổ biến nhất.
plugin
Phần mở rộng cắm thêm vào phần mềm để nó biết làm việc mới.
VS Code vốn không hiểu Go; cài plugin Go vào thì nó mới gợi ý mã, báo lỗi và chạy test được. Cũng gọi là extension.
environment variable
Biến cấu hình của hệ điều hành, chương trình đọc được khi chạy.
Đặt ở ngoài mã nguồn nên đổi được mà không phải biên dịch lại, và là chỗ an toàn để cất mật khẩu thay vì ghi thẳng vào mã. PATH, GOPATH, GOMEMLIMIT đều là biến môi trường.
PATH
Danh sách thư mục mà hệ điều hành lục tìm khi bạn gõ một lệnh.
Gõ go mà máy báo không tìm thấy lệnh, gần như chắc chắn là thư mục chứa Go chưa được thêm vào PATH.
GOPATH
Thư mục làm việc mặc định của Go thời chưa có module.
Ngày trước mọi mã Go bắt buộc phải nằm trong GOPATH. Từ khi có module (Go 1.11) thì không còn bắt buộc nữa; giờ GOPATH chủ yếu là nơi chứa mã tải về và tệp thực thi đã cài.
GOROOT
Thư mục cài đặt bản thân Go.
Nơi chứa trình biên dịch và thư viện chuẩn. Bình thường bạn không cần đụng tới; Go tự biết nó nằm ở đâu.
installer
Chương trình cài đặt, chạy là nó tự đặt mọi thứ vào đúng chỗ.
Trên Windows và macOS, Go có installer lo luôn cả việc thêm vào PATH. Trên Linux thường là tải tệp nén về rồi tự giải.
archive
Tệp nén gói nhiều tệp lại làm một, ví dụ .zip hay .tar.gz.
Bản Go cho Linux thường phát hành dạng này: tải về, giải nén vào /usr/local, xong.
API
Tập hợp các hàm hoặc địa chỉ mà chương trình khác gọi vào để dùng.
Có hai nghĩa hay gặp: API của một thư viện là danh sách hàm nó cho bạn gọi; API của một dịch vụ web là các địa chỉ bạn gửi yêu cầu tới. Điểm chung là một giao kèo: gọi thế này thì nhận lại thế kia.
REST
Kiểu thiết kế API web dựa trên các phương thức có sẵn của HTTP.
Mỗi thứ dữ liệu có một địa chỉ, và bạn thao tác bằng động từ HTTP: GET để đọc, POST để tạo, PUT để sửa, DELETE để xoá. API làm theo lối này gọi là RESTful.
JSON
Định dạng văn bản để trao đổi dữ liệu giữa các chương trình.
Viết dưới dạng cặp khoá–giá trị trong ngoặc nhọn, người đọc được mà máy phân tích cũng dễ. Gần như mọi API web ngày nay đều nói chuyện bằng JSON.
client
Bên gửi yêu cầu đi.
Trình duyệt, ứng dụng điện thoại, hay chính chương trình Go của bạn khi nó gọi ra ngoài.
server
Bên nhận yêu cầu và trả lời.
Chạy liên tục, chờ client gọi tới. Một chương trình có thể vừa là server với bên này vừa là client của bên kia.
stack trace
Danh sách các hàm đang gọi lồng nhau tại lúc xảy ra lỗi.
Đọc từ trên xuống: dòng đầu là nơi vỡ, các dòng sau cho biết đã đi qua đâu để tới đó. Đây là manh mối quan trọng nhất khi lần tìm nguyên nhân một panic.
debugger
Công cụ cho phép dừng chương trình giữa chừng để xem xét.
Đặt điểm dừng ở một dòng, chương trình chạy tới đó thì đứng lại, bạn xem giá trị từng biến, rồi cho chạy tiếp từng dòng một. Với Go thường dùng Delve.
breakpoint
Điểm dừng bạn đặt trong mã để debugger tạm dừng ở đó.
timeout
Hạn chờ tối đa; quá hạn thì bỏ, không đợi nữa.
Không đặt timeout là một lỗi hay gặp: một truy vấn treo có thể giữ kết nối mãi mãi và kéo sập cả dịch vụ.
context
Giá trị mang theo hạn chờ và tín hiệu huỷ, truyền xuyên suốt các lời gọi hàm.
Khi người dùng đóng trình duyệt giữa chừng, không việc gì phải chạy nốt truy vấn cho họ. Truyền context.Context qua các tầng thì tín hiệu huỷ lan tới tận nơi, giải phóng tài nguyên. Theo quy ước nó luôn là tham số đầu tiên của hàm.