สารบัญ
- Zero values
- Constant & iota
- If / Else
- Variable shadowing
- Guard clause
- Switch
- For
- Function
- Error pattern
- กับดัก 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 ""
| Type | Zero value |
|---|---|
| ตัวเลขทุกชนิด | 0 |
bool | false |
string | "" |
| pointer / slice / map / func / chan / interface | nil |
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
)
- เริ่มที่ 0 · เพิ่มทีละ 1 ต่อบรรทัด
- reset เป็น 0 ทุกครั้งที่เจอ
constblock ใหม่ - บรรทัดที่ไม่เขียนอะไร = ทำซ้ำนิพจน์บรรทัดบน
ทำให้ 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 >= 20 | x < 20 |
hasJob | !hasJob |
err == nil | err != 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
- ไม่ต้องเขียน
break— จบ case แล้วออกเอง - ใส่หลายค่าใน case เดียวได้
- อยากไหลต่อต้องสั่ง
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
}
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 ข้อ
errorเป็น return value ตัวสุดท้ายเสมอ- สำเร็จ = คืน
nil - เช็ค
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 |