Skip to content

🔄 Control Structures (Điều khiển luồng)

Go có cú pháp control flow đơn giản nhưng mạnh mẽ. Không có while, không có do-while — chỉ có for. Không có ?: ternary — chỉ có if-else.

🔀 If-Else: The Go Idiom

Basic Syntax

go
if condition {
    // true branch
} else if anotherCondition {
    // alternative
} else {
    // default
}

Inline Declaration Idiom (Quan trọng!)

Đây là pattern bạn sẽ thấy hàng nghìn lần trong Go codebase:

go
// ✅ Go Idiom: Declare + Check in one line
if err := validateRequest(req); err != nil {
    return fmt.Errorf("validation failed: %w", err)
}

// err KHÔNG tồn tại ở đây - scoped to if block!

🎓 Professor Tom's Deep Dive: Variable Scoping

Tại sao pattern này powerful?

  1. Tight Scoping: err chỉ tồn tại trong if block → không pollute namespace
  2. Immediate Handling: Declare và check ngay lập tức → giảm bugs
  3. Readability: Cả operation và error handling cùng một chỗ
go
// ❌ Traditional way (sai Go idiom)
err := validateRequest(req)
if err != nil {
    return err
}
// err vẫn visible ở đây - có thể reuse nhầm

// ✅ Go idiom
if err := validateRequest(req); err != nil {
    return err
}
// Clean namespace - không có "orphan" err variable

Multiple Returns Pattern

go
func processOrder(orderID int64) error {
    // Pattern: resource acquisition + immediate check
    if order, err := db.GetOrder(orderID); err != nil {
        return fmt.Errorf("fetch order: %w", err)
    } else if !order.IsValid() {
        return ErrInvalidOrder
    } else {
        // order và err đều available trong else block
        return processValidOrder(order)
    }
}

🔥 HPN's Pitfall

Shadowing trap với :=:

go
var err error

if result, err := someOperation(); err != nil {  // ⚠️ err bị SHADOW
    return err
}

fmt.Println(err)  // err ở đây là outer err (nil), không phải inner err!

Fix: Dùng = thay vì := khi muốn reuse variable:

go
var err error
var result Result

if result, err = someOperation(); err != nil {
    return err
}

🎛️ Switch-Case: Cleaner Branching

Basic Switch

go
func getStatusMessage(code int) string {
    switch code {
    case 200:
        return "OK"
    case 404:
        return "Not Found"
    case 500:
        return "Internal Server Error"
    default:
        return "Unknown Status"
    }
}

Automatic break (Unlike C/Java!)

🎓 Key Difference from C/Java

Go tự động break sau mỗi case! Không cần viết break statement.

c
// C/Java - phải có break!
switch(code) {
    case 1:
        doSomething();
        break;  // Bắt buộc, nếu không sẽ fall-through
    case 2:
        doOther();
        break;
}
go
// Go - break tự động
switch code {
case 1:
    doSomething()  // Tự động break sau dòng này
case 2:
    doOther()
}

fallthrough Keyword

Khi bạn thực sự muốn fall-through (hiếm khi cần):

go
switch level {
case "admin":
    grantAdminPrivileges()
    fallthrough  // Tiếp tục xuống case tiếp theo
case "moderator":
    grantModeratorPrivileges()
    fallthrough
case "user":
    grantBasicPrivileges()
}
// admin sẽ có tất cả privileges, moderator có mod+user, user chỉ có basic

Tagless Switch (Powerful!)

Thay thế long if-else chains:

go
// ❌ Hard to read với nhiều conditions
if score >= 90 {
    grade = "A"
} else if score >= 80 {
    grade = "B"
} else if score >= 70 {
    grade = "C"
} else if score >= 60 {
    grade = "D"
} else {
    grade = "F"
}

// ✅ Tagless switch - sạch hơn nhiều
switch {
case score >= 90:
    grade = "A"
case score >= 80:
    grade = "B"
case score >= 70:
    grade = "C"
case score >= 60:
    grade = "D"
default:
    grade = "F"
}

Type Switch

go
func describe(i interface{}) string {
    switch v := i.(type) {
    case int:
        return fmt.Sprintf("Integer: %d", v)
    case string:
        return fmt.Sprintf("String of length %d", len(v))
    case bool:
        return fmt.Sprintf("Boolean: %t", v)
    case *User:
        return fmt.Sprintf("User pointer: %s", v.Name)
    default:
        return fmt.Sprintf("Unknown type: %T", v)
    }
}

🔁 The for Loop: Go's Only Loop

Go chỉ có for. Không có while, không có do-while. Nhưng for có thể làm tất cả:

3 Forms of for

go
// Form 1: Traditional C-style
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// Form 2: While-style (chỉ condition)
for count < limit {
    count++
}

// Form 3: Infinite loop
for {
    if shouldStop() {
        break
    }
    processNext()
}

For-Range: Iterating Collections

go
// Slice/Array
users := []string{"Alice", "Bob", "Charlie"}
for index, name := range users {
    fmt.Printf("%d: %s\n", index, name)
}

// Map
scores := map[string]int{"Alice": 95, "Bob": 87}
for name, score := range scores {
    fmt.Printf("%s: %d\n", name, score)
}

// String (iterates over runes, not bytes!)
for i, r := range "Việt" {
    fmt.Printf("Position %d: %c\n", i, r)
}

// Channel
for msg := range messageChannel {
    process(msg)
}

🔥 HPN's Pitfall: For-Range Variable Capture Trap

Đây là bug KINH ĐIỂN trong Go! (Fixed trong Go 1.22, nhưng vẫn cần hiểu)

go
// ❌ Bug: Tất cả goroutines sẽ in "Charlie" (value cuối cùng)
users := []string{"Alice", "Bob", "Charlie"}

for _, name := range users {
    go func() {
        fmt.Println(name)  // Capture biến 'name' - CÙNG MỘT BIẾN!
    }()
}
time.Sleep(time.Second)
// Output: Charlie, Charlie, Charlie (không phải Alice, Bob, Charlie!)

Lý do (Go < 1.22):

┌─────────────────────────────────────────────────────────────┐
│                    FOR LOOP                                 │
├─────────────────────────────────────────────────────────────┤
│   Iteration 1: name = "Alice"                               │
│   Iteration 2: name = "Bob"    ← CÙNG biến 'name' bị reused │
│   Iteration 3: name = "Charlie"                             │
│                                                             │
│   Tất cả closures capture POINTER đến biến 'name'          │
│   Khi goroutines chạy, 'name' đã = "Charlie"               │
└─────────────────────────────────────────────────────────────┘

Fix (trước Go 1.22):

go
// Fix 1: Copy vào local variable
for _, name := range users {
    name := name  // Shadow với local copy
    go func() {
        fmt.Println(name)  // Capture local copy
    }()
}

// Fix 2: Pass as parameter
for _, name := range users {
    go func(n string) {
        fmt.Println(n)
    }(name)  // Pass by value
}

Go 1.22+: Loop variables được tạo mới mỗi iteration → fix by default!

go
// Go 1.22+ - hoạt động đúng
for _, name := range users {
    go func() {
        fmt.Println(name)  // Mỗi iteration có 'name' riêng
    }()
}

Performance: Range over Large Structs

go
type BigStruct struct {
    Data [10000]byte
}

items := []BigStruct{{}, {}, {}}

// ❌ Bad: Copy 10KB mỗi iteration
for _, item := range items {
    process(item)
}

// ✅ Good: Chỉ copy index (int)
for i := range items {
    process(&items[i])  // Pass pointer, không copy
}

🏷️ Labels & Goto

Go hỗ trợ goto và labels, nhưng sử dụng rất hạn chế:

go
// Breaking out of nested loops
OuterLoop:
    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            if shouldExit(i, j) {
                break OuterLoop  // Break cả 2 loops
            }
        }
    }

📌 HPN Standard: Labels & Goto

Quy tắc:

  1. Labels chỉ dùng để break/continue nested loops
  2. KHÔNG BAO GIỜ dùng goto cho control flow logic
  3. Nếu cần goto, refactor code thành functions
go
// ❌ Never do this
func bad() {
    if condition {
        goto cleanup
    }
    // ...
cleanup:
    // ...
}

// ✅ Do this instead
func good() {
    defer cleanup()
    if condition {
        return
    }
    // ...
}

🎮 Interactive: Scenario Choice

🎯

Bạn cần xử lý HTTP status codes (200, 201, 400, 401, 403, 404, 500, 502, 503). Code nào dễ maintain hơn?


📊 Summary: Control Flow Patterns

PatternUse CaseExample
Inline if declarationError handlingif err := op(); err != nil
Tagless switchRange conditionsswitch { case x > 0: ... }
Type switchInterface handlingswitch v := i.(type)
For-rangeCollection iterationfor _, v := range slice
Labeled breakNested loop exitbreak OuterLoop

➡️ Tiếp theo

Control flow nắm vững rồi! Tiếp theo: Functions & Methods - Multiple returns, defer, closures, và variadic functions.