Tài liệu Go
Tài liệu tham khảo Go ngắn gọn, đi kèm ví dụ. Đọc khái niệm, xem code, rồi luyện tập trong một hành trình Coddy.
Bắt đầu khóa họcBắt đầu
- Go là gì?Go (thường gọi là Golang) là ngôn ngữ biên dịch, kiểu tĩnh do Google thiết kế để xây dựng máy chủ và công cụ nhanh, ổn định. Trang này giải thích Go là gì, dùng để làm gì và đâu là điểm yếu của nó.
- Cài đặt GoCách tải và cài Go trên Windows, macOS và Linux, kiểm tra bản cài bằng go version, hiểu GOPATH, và sửa lỗi "go: command not found".
- Hello WorldChương trình hello world trong Go qua từng dòng: package main, khối import, func main và fmt.Println, cùng cách chạy nó và những lỗi biên dịch người mới gặp đầu tiên.
- go run và go buildgo run, go build và go install mỗi lệnh làm gì, Go đặt tên tệp thực thi ra sao, cách biên dịch chéo với GOOS và GOARCH, và hai bước kiểm tra go fmt, go vet nên chạy trước khi commit.
- Go ModulesCách Go modules hoạt động: tạo module bằng go mod init, chọn module path, thêm dependency bằng go get, dọn dẹp bằng go mod tidy, go.sum dùng để làm gì, và replace khi phát triển ở máy cục bộ.
- Package và importPackage trong Go hoạt động thế nào: mệnh đề package, import package chuẩn và package của module, tên exported và unexported, chia một package ra nhiều tệp, package internal, alias khi import, blank import, và lỗi "undefined" khi chạy go run main.go.
Kiến thức cơ bản
- BiếnCách khai báo biến trong Go với var và dạng ngắn :=, giá trị zero là gì, gán nhiều biến cùng lúc, cùng các lỗi shadowing và biến không dùng đến khiến người mới học Go hay vấp.
- Hằng sốCách const hoạt động trong Go: khai báo hằng số, khác biệt giữa hằng số có kiểu và không có kiểu, biểu thức hằng với độ chính xác tùy ý, và lý do Go không có slice, map hay struct hằng.
- Enum và iotaGo không có từ khóa enum. Trang này giới thiệu cách thay thế chuẩn mực: một kiểu có tên cùng khối const dùng iota, kèm cách thêm String(), kiểm tra hợp lệ, phân tích chuỗi, bit flag và hỗ trợ JSON.
- Kiểu dữ liệuCác kiểu có sẵn của Go: kích thước và phạm vi số nguyên, float32 và float64, bool, string, byte và rune, số phức, giá trị zero, và cách in kiểu của một giá trị bằng %T.
- Chuyển đổi kiểuCách chuyển đổi kiểu trong Go: int sang string và string sang int bằng strconv, số thực sang số nguyên, chuyển đổi số bằng T(v), byte và rune, cái bẫy string(65), và cách xử lý lỗi khi chuyển đổi.
- ChuỗiLàm việc với chuỗi trong Go: package strings (Split, Join, Contains, Replace, Fields, TrimSpace, ToUpper), ghép chuỗi hiệu quả với strings.Builder, chuỗi raw nhiều dòng, tính bất biến, và vì sao len đếm byte.
- Rune và byteRune và byte trong Go là gì, chuỗi lưu UTF-8 ra sao, vì sao len đếm byte, range giải mã rune thế nào, cách chuyển giữa string, []byte và []rune, và cách dùng package bytes.
- fmt.Printf và SprintfCách package fmt của Go in và định dạng giá trị: so sánh Println, Printf, Sprintf và Errorf, bảng đầy đủ các verb định dạng (%v, %+v, %d, %s, %q, %f, %T, %w và nhiều hơn nữa), độ rộng, độ chính xác và căn lề.
Luồng điều khiển
- if / elseCách if, else if và else hoạt động trong Go: không cần ngoặc tròn, bắt buộc có ngoặc nhọn, câu lệnh if kèm mệnh đề khởi tạo và phạm vi của nó, thành ngữ if err != nil, và return sớm thay cho lồng nhau nhiều tầng.
- Vòng lặp forGo chỉ có một từ khóa vòng lặp là for, và nó đảm nhận mọi việc: vòng lặp đếm ba mệnh đề, vòng lặp while chỉ có điều kiện, vòng lặp vô hạn, và range trên slice, map, chuỗi, channel, số nguyên và hàm. Cùng với break, continue có nhãn và thay đổi về biến vòng lặp trong Go 1.22.
- Vòng lặp whileGo không có từ khóa while. Vòng lặp for chỉ có điều kiện chính là vòng lặp while của Go, for không có điều kiện là vòng lặp vô hạn, còn do-while là vòng lặp vô hạn kiểm tra điều kiện ở cuối.
- Vòng lặp rangefor range trả về gì với slice, mảng, chuỗi, map, channel, số nguyên và hàm iterator, vì sao biến giá trị chỉ là bản sao, chuyện gì xảy ra khi bạn sửa slice hoặc map trong lúc range, và range trên hàm ở Go 1.23.
- Câu lệnh switchCách switch hoạt động trong Go: case không tự rơi xuống case sau, một case có thể liệt kê nhiều giá trị, switch không điều kiện thay cho chuỗi if/else, switch có câu lệnh khởi tạo, từ khóa fallthrough, và sơ lược về type switch.
- Toán tử ba ngôiGo không có toán tử ba ngôi (cond ? a : b). Trang này giải thích lý do, cách thay thế chuẩn bằng if/else, cmp.Or cho giá trị mặc định, và một hàm hỗ trợ generic cùng cái bẫy khiến nó khác với toán tử ba ngôi thật.
Hàm
- HàmCách khai báo và gọi hàm trong Go: tham số, tham số dùng chung kiểu, giá trị trả về, truyền theo giá trị, và hàm như những giá trị bạn có thể lưu trữ và truyền đi.
- Trả về nhiều giá trịHàm trong Go có thể trả về nhiều giá trị. Trang này trình bày cú pháp, quy ước (value, error), thành ngữ comma-ok, named result, naked return và định danh trống.
- Hàm variadicHàm variadic trong Go nhận bao nhiêu đối số cuối cùng cùng một kiểu cũng được. Tìm hiểu cú pháp ...T, cách truyền slice bằng s..., cái bẫy dùng chung slice, và cách fmt.Println dùng ...any.
- ClosureHàm ẩn danh trong Go có thể giữ lại các biến từ phạm vi bao quanh nó. Đó chính là closure: bộ đếm, generator, middleware và callback tự giữ trạng thái riêng.
- Câu lệnh deferdefer lên lịch một lời gọi để chạy khi hàm bao quanh trả về. Tìm hiểu thứ tự LIFO, thời điểm đối số được tính, đóng tệp và mở khóa mutex, defer trong vòng lặp, và thay đổi kết quả có tên.
- Phương thứcPhương thức là một hàm có receiver. Tìm hiểu cách khai báo phương thức, khi nào dùng receiver giá trị hay receiver con trỏ, method set và interface, cùng phương thức trên các kiểu không phải struct.
Collection
- MảngMảng trong Go có độ dài cố định thuộc về chính kiểu của nó, và được sao chép khi gán. Tìm hiểu cách khai báo, duyệt, so sánh và truyền mảng, cùng lý do phần lớn code Go dùng slice thay cho mảng.
- SliceSlice là kiểu danh sách dùng hằng ngày trong Go. Tìm hiểu make, append và cách slice tăng kích thước, len so với cap, việc cắt slice chia sẻ mảng nền (và lỗi phát sinh từ đó), copy, package slices, slice 2 chiều, nil và rỗng.
- MapMap trong Go lưu các cặp khóa và giá trị, tra cứu rất nhanh. Tìm hiểu cách tạo map, kiểm tra khóa có tồn tại hay không bằng comma-ok, xóa, duyệt (theo thứ tự ngẫu nhiên), sắp xếp khóa, lưu struct, và tránh panic do map nil hay ghi đồng thời.
- SetGo không có kiểu set dựng sẵn. Cách viết chuẩn là một map với giá trị là struct rỗng. Tìm hiểu cách thêm, kiểm tra và xóa phần tử, phép hợp, giao, hiệu, và cách viết một kiểu Set generic nhỏ.
- Sắp xếpSắp xếp slice trong Go với slices.Sort và slices.SortFunc, sắp xếp struct theo một hoặc nhiều trường bằng cmp.Compare, giữ thứ tự các phần tử bằng nhau với sắp xếp ổn định, và đọc hiểu code sort.Slice cũ.
Struct, interface và generic
- StructStruct gom các trường có tên vào một kiểu. Tìm hiểu cách khai báo và khởi tạo struct, giá trị zero, con trỏ tới struct, struct ẩn danh, phép so sánh, struct tag và quy ước hàm khởi tạo NewX.
- Con trỏCon trỏ lưu địa chỉ của một giá trị. Tìm hiểu & và *, new, con trỏ tới struct, vì sao trả về con trỏ tới biến cục bộ là an toàn, khi nào nên dùng con trỏ, và panic khi giải tham chiếu con trỏ nil.
- InterfaceInterface trong Go được thỏa mãn một cách ngầm định: kiểu nào có đúng các phương thức thì triển khai interface đó. Tìm hiểu các interface nhỏ như io.Reader và fmt.Stringer, interface rỗng any, bẫy interface nil, và nguyên tắc nhận interface, trả về struct.
- Nhúng structEmbedding đặt một kiểu vào bên trong kiểu khác mà không cần tên trường, nên các trường và phương thức của nó được nâng lên kiểu bên ngoài. Tìm hiểu cách nâng hoạt động, nhúng interface, xung đột tên và vì sao embedding không phải kế thừa.
- Type assertionType assertion lấy giá trị cụ thể ra khỏi một interface. Tìm hiểu x.(T), dạng comma-ok không bao giờ panic, assert sang interface khác, type switch, và errors.As cho lỗi được bọc.
- GenericsGenerics (Go 1.18 trở lên) cho phép một hàm hoặc một kiểu làm việc với nhiều kiểu dữ liệu mà vẫn an toàn về kiểu. Tìm hiểu type parameter, các constraint any, comparable và cmp.Ordered, constraint tùy chỉnh với ~, kiểu generic, và khi nào không nên dùng.
Lỗi
- Xử lý lỗiGo xử lý lỗi như những giá trị bình thường được hàm trả về. Tìm hiểu interface error, if err != nil, errors.New và fmt.Errorf, trả về lỗi kèm ngữ cảnh, kiểm tra lỗi bằng errors.Is và errors.As, và xử lý mỗi lỗi đúng một lần.
- Lỗi tùy chỉnhĐịnh nghĩa sentinel error và kiểu lỗi riêng, bọc lỗi bằng %w, kiểm tra chúng bằng errors.Is và errors.As, gộp nhiều lỗi bằng errors.Join, và viết phương thức Unwrap và Is khi cần.
- panic và recoverPanic dừng luồng chạy bình thường và tháo ngược ngăn xếp, chạy các lời gọi đã defer. Tìm hiểu nguyên nhân gây panic, cách recover trong một hàm defer chặn panic lại, các thông báo lỗi runtime bạn sẽ gặp, và khi nào panic là lựa chọn đúng.
Lập trình đồng thời
- GoroutineCách chạy các hàm đồng thời bằng từ khóa go, chờ chúng chạy xong, lấy kết quả về, và tránh data race, rò rỉ goroutine cùng những lần crash mà goroutine dễ gây ra.
- ChannelCách channel trong Go truyền giá trị giữa các goroutine: channel unbuffered và buffered, đóng và duyệt channel, kiểu có chiều, lỗi deadlock, và một pipeline ghép từ các channel.
- Câu lệnh selectCách select chờ nhiều thao tác channel cùng lúc: chọn giữa các case đã sẵn sàng, gửi và nhận không chặn bằng default, timeout với time.After, và dừng vòng lặp bằng kênh quit hoặc context.
- WaitGroupCách sync.WaitGroup chờ một nhóm goroutine chạy xong: quy tắc của Add, Done và Wait, vì sao phải truyền nó bằng con trỏ, cách gom kết quả và lỗi, và một worker pool xây trên nó.
- MutexCách bảo vệ dữ liệu dùng chung giữa các goroutine bằng sync.Mutex và sync.RWMutex, khi nào sync/atomic là đủ, cách sync.Once chạy phần khởi tạo đúng một lần, và những lỗi khóa gây ra deadlock.
- Package contextCách context.Context mang tín hiệu hủy, deadline và các giá trị gắn với request đi khắp một chương trình Go: Background, WithCancel, WithTimeout, WithValue, ctx.Done trong select, và context trong máy chủ và máy khách HTTP.
Thư viện chuẩn
- Package timeCách làm việc với ngày giờ trong Go: time.Now và time.Sleep, tính toán với Duration, layout mẫu 2006-01-02 15:04:05 cho Format và Parse, múi giờ, Unix timestamp, timer và ticker.
- JSONCách mã hóa và giải mã JSON trong Go với encoding/json: Marshal và Unmarshal, struct tag như omitempty và omitzero, in JSON đẹp, giải mã vào map[string]any, từ chối trường lạ, và đọc theo luồng với Decoder.
- Đọc và ghi tệpCách đọc và ghi tệp trong Go: os.ReadFile và os.WriteFile, đọc từng dòng với bufio.Scanner, ghi nối thêm với os.OpenFile, kiểm tra tệp có tồn tại không, và làm việc với thư mục.
- HTTP serverCách dựng web server bằng package chuẩn net/http của Go: handler, định tuyến ServeMux theo phương thức và wildcard trong đường dẫn (Go 1.22), response JSON, mã trạng thái, middleware, timeout và tắt máy chủ an toàn.
- HTTP clientCách gửi HTTP request trong Go bằng net/http: http.Get, đọc và đóng body, kiểm tra mã trạng thái, timeout của http.Client, request kèm context và header, tham số query, và POST JSON.
- Tham số dòng lệnhCách một chương trình Go đọc dòng lệnh: os.Args, package flag cho các tùy chọn có kiểu, lệnh con với FlagSet, biến môi trường với os.Getenv và os.LookupEnv, và mã thoát với os.Exit.
- Biểu thức chính quyCách dùng biểu thức chính quy trong Go với package regexp: MustCompile, MatchString, FindString và FindAllString, nhóm bắt và nhóm có tên, ReplaceAllString, cùng giới hạn của cú pháp RE2 như không có lookbehind.
- Ghi logCách ghi log trong Go: package log truyền thống với các cờ và log.Fatal, và log/slog (Go 1.21) cho log có cấu trúc với các mức log, thuộc tính khóa giá trị, handler dạng văn bản và JSON, cùng logger mang theo ngữ cảnh nhờ With.
Kiểm thử và cấu trúc dự án
- TestingCách test code Go với package testing chuẩn và go test: tệp _test.go, hàm TestXxx, t.Errorf và t.Fatalf, table-driven test với t.Run, helper và thư mục tạm, độ bao phủ, benchmark với b.Loop và example test.
- Cấu trúc dự ánCách tổ chức một dự án Go: bắt đầu phẳng, chỉ tách package khi có lý do, dùng cmd/ cho nhiều tệp thực thi và internal/ cho code không ai khác được import, đặt tên package hợp lý, và để test cạnh code.