1. Go 中的函數
函數是 Go 中的基本建構塊。與其他語言相比,Go 函數有許多獨特的功能。
1.1.基本功能
package main
import "fmt"
// Function cơ bản
func greet(name string) string {
return "Hello, " + name + "!"
}
// Multiple parameters cùng type
func add(a, b int) int {
return a + b
}
// Multiple return values - đặc trưng Go
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
// Named return values
func swap(a, b string) (first, second string) {
first = b
second = a
return // naked return - trả named values
}
func main() {
fmt.Println(greet("Go")) // "Hello, Go!"
fmt.Println(add(3, 5)) // 8
result, err := divide(10, 3)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result) // 3.333...
a, b := swap("hello", "world")
fmt.Println(a, b) // "world" "hello"
}
1.2.可變參數函數
// Variadic function - nhận số lượng tham số bất kỳ
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Mix fixed và variadic params
func printf(format string, args ...interface{}) {
fmt.Printf(format, args...)
}
func main() {
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// Spread slice
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
}
1.3.一流的功能
// Function as variable
var multiply func(int, int) int = func(a, b int) int {
return a * b
}
// Function as parameter (callback)
func apply(a, b int, op func(int, int) int) int {
return op(a, b)
}
// Function as return value (closure)
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
// Function type alias
type MathFunc func(int, int) int
func calculate(a, b int, fn MathFunc) int {
return fn(a, b)
}
func main() {
// Anonymous function (lambda)
double := func(x int) int {
return x * 2
}
fmt.Println(double(5)) // 10
// Callback
fmt.Println(apply(3, 4, multiply)) // 12
fmt.Println(apply(10, 3, func(a, b int) int {
return a - b
})) // 7
// Closure
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
// IIFE (Immediately Invoked Function Expression)
result := func(x int) int {
return x * x
}(5)
fmt.Println(result) // 25
}
1.4.推遲
import "os"
// defer: thực thi khi function return (LIFO order)
func readFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // Đảm bảo file luôn được close
// ... đọc file
return nil
}
func main() {
// Defer chạy theo LIFO (Last In, First Out)
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// Output: 3, 2, 1
// Use case phổ biến: cleanup resources
// defer mutex.Unlock()
// defer rows.Close()
// defer tx.Rollback()
// defer span.End()
}
2. 介面
Go 中的介面有 隱式.隱式 ——類型只需要實作方法,不需要宣告「implements」。與Java/C#相比,這是一個很大的差異。
2.1.介面定義
// Interface definition
type Shape interface {
Area() float64
Perimeter() float64
}
// Circle implements Shape implicitly
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// Rectangle implements Shape implicitly
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Hàm nhận interface
func printShapeInfo(s Shape) {
fmt.Printf("Area: %.2f, Perimeter: %.2f\n", s.Area(), s.Perimeter())
}
func main() {
c := Circle{Radius: 5}
r := Rectangle{Width: 10, Height: 3}
printShapeInfo(c) // Area: 78.54, Perimeter: 31.42
printShapeInfo(r) // Area: 30.00, Perimeter: 26.00
// Slice of interfaces
shapes := []Shape{c, r}
for _, s := range shapes {
printShapeInfo(s)
}
}
2.2. Go 中的常用介面
// io.Reader - interface quan trọng nhất
type Reader interface {
Read(p []byte) (n int, err error)
}
// io.Writer
type Writer interface {
Write(p []byte) (n int, err error)
}
// fmt.Stringer (tương đương toString() trong Java)
type Stringer interface {
String() string
}
// error interface
type error interface {
Error() string
}
// sort.Interface
type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
// Implement Stringer
type User struct {
Name string
Age int
}
func (u User) String() string {
return fmt.Sprintf("%s (age %d)", u.Name, u.Age)
}
func main() {
user := User{Name: "Alice", Age: 30}
fmt.Println(user) // "Alice (age 30)" - tự gọi String()
}
2.3.介面組合(嵌入)
// Compose interfaces từ các interfaces nhỏ
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Composed interface
type ReadWriter interface {
Reader
Writer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
// Best practice: Keep interfaces SMALL
// Go proverb: "The bigger the interface, the weaker the abstraction"
// Thường chỉ 1-3 methods
2.4.空接口和任何
// interface{} (Go < 1.18) hoặc any (Go 1.18+) chấp nhận mọi type
func printAnything(v any) {
fmt.Printf("Type: %T, Value: %v\n", v, v)
}
func main() {
printAnything(42) // Type: int, Value: 42
printAnything("hello") // Type: string, Value: hello
printAnything(true) // Type: bool, Value: true
printAnything([]int{1, 2}) // Type: []int, Value: [1 2]
// map[string]any - thường dùng cho JSON
data := map[string]any{
"name": "Alice",
"age": 30,
"roles": []string{"admin", "user"},
}
fmt.Println(data)
}
3. 錯誤處理
Go 也不例外(try-catch)。使用的錯誤處理 明確傳回值 ——這是一個有意的設計選擇。
3.1.基本錯誤處理
import (
"errors"
"fmt"
"os"
"strconv"
)
// Hàm trả về (result, error)
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// fmt.Errorf với format string
func findUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("invalid user ID: %d", id)
}
// ... lookup user
return &User{Name: "Alice"}, nil
}
func main() {
// Pattern: check error immediately
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result)
// Chaining error checks
f, err := os.Open("config.json")
if err != nil {
fmt.Println("Cannot open file:", err)
return
}
defer f.Close()
// strconv error example
num, err := strconv.Atoi("abc")
if err != nil {
fmt.Println("Parse error:", err)
}
fmt.Println(num)
}
3.2.自訂錯誤
// Custom error type
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// Sentinel errors (pre-defined errors)
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
ErrForbidden = errors.New("forbidden")
)
func getUser(id int) (*User, error) {
if id <= 0 {
return nil, &ValidationError{
Field: "id",
Message: "must be positive",
}
}
// ... user not in DB
return nil, fmt.Errorf("user %d: %w", id, ErrNotFound)
}
3.3.錯誤包裝(Go 1.13+)
import "errors"
func readConfig() error {
f, err := os.Open("config.yaml")
if err != nil {
// %w wraps the original error
return fmt.Errorf("readConfig: %w", err)
}
defer f.Close()
// ... parse
return nil
}
func initApp() error {
if err := readConfig(); err != nil {
return fmt.Errorf("initApp: %w", err)
}
return nil
}
func main() {
err := initApp()
if err != nil {
fmt.Println(err)
// "initApp: readConfig: open config.yaml: no such file or directory"
// errors.Is - check nếu error chain chứa target error
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File does not exist")
}
// errors.As - extract specific error type từ chain
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Path:", pathErr.Path)
fmt.Println("Op:", pathErr.Op)
}
}
}
3.4.恐慌與恢復
// panic: dùng cho unrecoverable errors (thay vì throw)
// recover: bắt panic (thay vì catch)
func riskyOperation() {
// panic dừng execution, unwind stack
panic("something went terribly wrong")
}
// recover chỉ hoạt động trong defer
func safeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
riskyOperation()
fmt.Println("This won't print")
}
func main() {
safeOperation()
fmt.Println("Program continues normally")
}
// ⚠️ Quy tắc:
// - KHÔNG dùng panic cho business logic errors
// - Dùng panic khi: programmer error, unrecoverable state
// - recover thường dùng trong middleware (HTTP handler recovery)
3.5.常用的錯誤處理模式
// Pattern 1: Early return
func processOrder(order *Order) error {
if order == nil {
return errors.New("order is nil")
}
if order.Total <= 0 {
return fmt.Errorf("invalid total: %.2f", order.Total)
}
if err := validateItems(order.Items); err != nil {
return fmt.Errorf("validate items: %w", err)
}
if err := chargePayment(order); err != nil {
return fmt.Errorf("charge payment: %w", err)
}
return nil
}
// Pattern 2: Error type assertion for flow control
func handleError(err error) {
var validErr *ValidationError
if errors.As(err, &validErr) {
// 400 Bad Request
fmt.Printf("Validation: %s - %s\n", validErr.Field, validErr.Message)
return
}
if errors.Is(err, ErrNotFound) {
// 404 Not Found
fmt.Println("Resource not found")
return
}
if errors.Is(err, ErrUnauthorized) {
// 401 Unauthorized
fmt.Println("Please login")
return
}
// 500 Internal Server Error
fmt.Println("Internal error:", err)
}
// Pattern 3: errors.Join (Go 1.20+) - multiple errors
func validateUser(u *User) error {
var errs []error
if u.Name == "" {
errs = append(errs, errors.New("name is required"))
}
if u.Email == "" {
errs = append(errs, errors.New("email is required"))
}
if u.Age < 0 {
errs = append(errs, errors.New("age must be non-negative"))
}
return errors.Join(errs...)
}
4. 結構嵌入(組合)
// Go dùng composition thay vì inheritance
type BaseModel struct {
ID int
CreatedAt time.Time
UpdatedAt time.Time
}
type User struct {
BaseModel // Embedding - "kế thừa" tất cả fields và methods
Name string
Email string
}
type Post struct {
BaseModel
Title string
Content string
Author User
}
func main() {
user := User{
BaseModel: BaseModel{
ID: 1,
CreatedAt: time.Now(),
},
Name: "Alice",
Email: "[email protected]",
}
// Access embedded fields trực tiếp
fmt.Println(user.ID) // 1 (từ BaseModel)
fmt.Println(user.Name) // "Alice"
fmt.Println(user.CreatedAt) // (từ BaseModel)
}
5. 總結
- 功能:多個回傳值、一流、閉包、延遲
- 介面:隱式實作、小介面、組合
- 錯誤處理:顯式錯誤、換行(%w)、errors.Is/As、自訂錯誤
- 成分:結構嵌入而不是繼承
- 恐慌/恢復:僅針對不可恢復的錯誤,不適用於業務邏輯
下一篇: 泛型、套件和模組 — Go 中的類型參數系統和代碼管理。