Reference · Day 2

Go Day 2 — Cheat Sheet

zero values · const & iota · control flow · clean code · function design · ตัวอย่างรันจริงบน Go 1.26 แล้ว

สารบัญ

  1. Zero values
  2. Constant & iota
  3. If / Else
  4. Variable shadowing
  5. Guard clause
  6. Switch
  7. For
  8. Function
  9. Error pattern
  10. กับดัก Day 2

1 · Zero values

var i int; var f float64; var b bool; var s string
fmt.Printf("%v %v %v %q\n", i, f, b, s)
// 0 0 false ""
TypeZero value
ตัวเลขทุกชนิด0
boolfalse
string""
pointer / slice / map / func / chan / interfacenil

Go ไม่มี undefined — ประกาศแล้วใช้ได้เลย

2 · Constant & iota

const Pi = 3.14
const World = "World"
const Truth = true

// ✗ const ใช้ := ไม่ได้
// ✗ const Now = time.Now()  ← ต้องรู้ค่าตั้งแต่ compile time

iota = ลำดับของ ConstSpec ใน block นั้น

const (
    zero = iota   // 0
    one           // 1  ← ทำซ้ำนิพจน์จากบรรทัดบนอัตโนมัติ
    two           // 2
)

const (
    zero = iota   // 0
    _             // 1  (กินลำดับ แต่ไม่มีชื่อ)
    two           // 2
)

const (
    Sunday = iota // 0 …ถึง… Saturday = 6
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

// ทำเลขคณิตได้
const (
    _  = 1 << (iota * 10)  // 1 << 0  (ทิ้ง)
    KB                       // 1024
    MB                       // 1048576
    GB                       // 1073741824
)

ทำให้ enum มี type ของตัวเอง

type Weekday int
const (
    Sunday Weekday = iota
    Monday
)
func schedule(d Weekday) { ... }   // ส่ง int ตัวแปรผิดเข้าไปไม่ได้

3 · If / Else

if result > 5 {
    ...
} else if result > 0 {
    ...
} else {
    ...
}

// if with short statement — ตัวแปรมีชีวิตแค่ใน if/else
if result := rand.Intn(10); result > 5 {
    fmt.Println(result)
}
fmt.Println(result)   // ✗ undefined: result

เงื่อนไข ไม่ต้องใส่วงเล็บ · ปีกกา บังคับใส่เสมอ แม้บรรทัดเดียว

4 · 💀 Variable shadowing

var sum = 0
if true {
    sum := sum + 20       // := สร้างตัวใหม่!
    fmt.Println(sum)     // 20
}
fmt.Println(sum)         // 0  ← ตัวนอกไม่เคยถูกแตะ

// แก้: ใช้ = แทน :=
if true { sum = sum + 20 }   // ข้างนอก = 20 ✓

เวอร์ชัน err — ตัวที่ทำ production พังจริง

func process() error {
    var err error
    if needFile {
        file, err := os.Open("demo.txt")  // 💀 err ตัวใหม่
        ...
    }
    return err   // คืน nil เสมอ! error หายไปแล้ว
}
สถานการณ์:= ทำอะไร
scope เดียวกัน + มีตัวแปรใหม่ ≥1 ตัวทางซ้าย✅ reuse ตัวเดิม
คนละ scope (ใน if / for / block ย่อย)💀 สร้างใหม่ทั้งหมด — bug
# go vet เปล่า ๆ จับไม่ได้ ต้องลง analyzer แยก
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
go vet -vettool=$(which shadow) ./...

5 · Guard clause

1. หา if ที่ครอบทุกอย่าง → if age >= 20 { 2. พลิกเงื่อนไขให้ตรงข้าม → if age < 20 { 3. เอาโค้ดใน else มาไว้ข้างใน + return ทำซ้ำจนหมด
// ✗ ก่อน
if age >= 20 {
    if hasJob {
        if income >= 15000 { fmt.Println("อนุมัติ") }
        else { ... }
    } else { ... }
} else { ... }

// ✓ หลัง — ชั้นเดียว ไม่มี else เลย
if age < 20        { fmt.Println("ปฏิเสธ: อายุไม่ถึง"); return }
if !hasJob         { fmt.Println("ปฏิเสธ: ไม่มีงาน");  return }
if income < 15000 { fmt.Println("ปฏิเสธ: รายได้ไม่ถึง"); return }
fmt.Println("อนุมัติ")

ตารางพลิกเงื่อนไข

เดิมพลิกเป็น
x >= 20x < 20
hasJob!hasJob
err == nilerr != nil
a && b!a || !b ← เดอมอร์แกน
a || b!a && !b

มาตรฐานทางการ — Go Code Review Comments · Indent Error Flow: "keep the normal code path at a minimal indentation, and indent the error handling, dealing with it first."

6 · Switch

switch day {
case "Monday":
    fmt.Println("Start of work week")
case "Tuesday", "Wednesday", "Thursday":   // หลายค่าใน case เดียว
    fmt.Println("Mid Week")
default:
    fmt.Println("Not found day")
}

// switch + short statement
switch status := strings.ToUpper("approved"); status {
case "APPROVED":
    fmt.Println("Request approved")
    fallthrough              // ไหลลง case ถัดไป โดยไม่เช็คเงื่อนไข
case "REJECTED":
    fmt.Println("Request rejected")
}
// ได้: Request approved / Request rejected (แล้วหยุด ไม่ถึง default)
switch ของ Go ต่างจาก C/Java
  1. ไม่ต้องเขียน break — จบ case แล้วออกเอง
  2. ใส่หลายค่าใน case เดียวได้
  3. อยากไหลต่อต้องสั่ง fallthrough เอง (ไหลแค่ 1 case)

Control Flow Decision Guide

สถานการณ์ใช้
ไม่ต้องตัดสินใจทำคำสั่งตามลำดับ
คำถาม true/false เดียวif
เลือกสองทางif-else
ตรวจหลายช่วงif-else-if หรือ guard clause
เทียบตัวแปรเดียวกับหลายค่าswitch

7 · For — Go มี loop แบบเดียว

// 1) ครบสามส่วน
for i := 1; i <= 3; i++ { ... }

// 2) เหมือน while — ละ init/post ทิ้ง semicolon ได้
sum := 1
for sum < 1000 { sum += sum }

// 3) forever
for { ... }

// 4) range (slice / array / map / string / channel)
for i, v := range nums { ... }

break / continue / labeled break

for i := 1; i <= 10; i++ {
    if i == 5 { break }      // ออกจาก loop ทันที
    if i == 5 { continue }   // ข้ามรอบนี้ ไปรอบถัดไป
}

// break ธรรมดาออกได้แค่ loop ชั้นในสุด
OuterLoop:
    for row := 1; row <= 3; row++ {
        for col := 1; col <= 3; col++ {
            if row == 2 && col == 2 {
                break OuterLoop      // ออกจาก loop นอกเลย
            }
            fmt.Println(row, col)
        }
    }
// พิมพ์: (1,1) (1,2) (1,3) (2,1) แล้วหยุด

8 · Function

func add(a int, b int) int { return a + b }
func add(a, b int) int      { return a + b }   // type เดียวกันยุบได้

// คืนหลายค่า
func swap(a, b int) (int, int) { return b, a }

// named return
func minMax(a, b int) (min, max int) {
    if a < b { return a, b }
    return b, a
}

// naked return — คืนค่าปัจจุบันของ named return
func calculate(a, b int) (sum, product int) {
    sum = a + b
    product = a * b
    return
}

⚠️ naked return ใช้กับฟังก์ชันสั้นเท่านั้น — ฟังก์ชันยาวคนอ่านต้องเลื่อนขึ้นไปดู signature (Effective Go)

9 · Error pattern

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("cannot divide by zero")
    }
    return a / b, nil
}

// สำนวนมาตรฐาน
result, err := divide(10, 2)
if err != nil { fmt.Println("Error:", err); return }

// คืนแค่ error + short statement
if err := saveUser(user); err != nil {
    fmt.Println("save failed:", err)
}

// ห่อ error ด้วย %w + named return + defer
func loadConfig(path string) (err error) {
    defer func() {
        if err != nil { err = fmt.Errorf("loadConfig %s: %w", path, err) }
    }()
    ...
}
กฎ 3 ข้อ
  1. error เป็น return value ตัวสุดท้ายเสมอ
  2. สำเร็จ = คืน nil
  3. เช็ค err != nil ทันทีที่รับค่ามา

%w = wrap (เก็บ error เดิมไว้ให้ errors.Is/errors.As ตรวจได้) · %v = แปลงเป็นข้อความเฉย ๆ

10 · กับดัก Day 2

เขียนคิดว่าเกิดจริง
const Now = time.Now()ได้เวลาตอนเริ่มรัน 💥 compile error — const ต้องรู้ค่าตอน compile
const x := 5เหมือน var 💥 const ใช้ := ไม่ได้
sum := sum + 20 ใน ifแก้ตัวนอก สร้างตัวใหม่ → ตัวนอกไม่เปลี่ยน · ใช้ =
file, err := ... ใน ifใช้ err ตัวนอก 💀 สร้าง err ใหม่ → error หาย (go vet เปล่า ๆ จับไม่ได้)
ใช้ตัวแปรจาก if x := …; หลังปิดปีกกายังใช้ได้ 💥 undefined: x
switch ไม่เขียน breakไหลลง case ถัดไป จบ case แล้วออกเอง — กลับด้านกับ C
พลิก a && b เป็น !a && !bถูกแล้ว ผิด — ต้องเป็น !a || !b (เดอมอร์แกน)
break ใน loop ซ้อนออกจากทั้งหมด ออกแค่ชั้นในสุด → ใช้ labeled break
if ซ้อน 4–5 ชั้นอ่านออกอยู่ reviewer ทักแน่ → guard clause