เรียน Go ภาษาง่าย 📘

🌱

หนังสือสอนภาษา Go สำหรับคนไม่เคยเขียนมาก่อนเลย

เริ่มจากศูนย์: ติดตั้ง Hello World ถึง goroutine + channel

18 บท
32 ข้อสอบ
สารบัญ
สารบัญ (อ่านตามลำดับนะ อย่าข้าม)ตอนที่ 0: โปรแกรมคืออะไร? Go คืออะไร?ตอนที่ 0.5: มีอะไรใหม่ใน Go 1.27 ฉบับภาษาง่ายๆตอนที่ 1: ติดตั้ง Go ทีละขั้น + Hello World บรรทัดต่อบรรทัดตอนที่ 2: กฎ 3 ข้อของคนเขียน Go เก่ง (จาก Effective Go)ตอนที่ 3: ตัวแปร คือกล่องใส่ของตอนที่ 4: สั่งให้คอมพิวเตอร์พูดด้วย fmtตอนที่ 5: ถ้า...ก็... ถ้าไม่...ก็... (if)ตอนที่ 6: ทำงานซ้ำๆ ด้วย for (มีแค่อันเดียว!)ตอนที่ 7: ฟังก์ชัน คือเครื่องจักรตอนที่ 8: เก็บของหลายชิ้น Array Slice Mapตอนที่ 9: switch เลือกทางแยกตอนที่ 10: struct + method คืออะไรตอนที่ 11: error ไม่ต้องกลัวตอนที่ 12: defer คือคำสัญญาว่าจะกลับมาทำตอนที่ 13: goroutine + channel หัวใจของ Goตอนที่ 14: โปรเจกต์จบเล่ม + ไปต่อยังไงคำศัพท์ท้ายเล่ม (ลืมก็กลับมาดู)
สารบัญ (อ่านตามลำดับนะ อย่าข้าม)

ตอนที่ 0: โปรแกรมคืออะไร? Go คืออะไร?
โปรแกรม = สูตรอาหารให้คอมทำตาม

สมมติคุณสั่งน้องว่า:

  1. หยิบไข่ 2 ฟอง
  2. ตอกใส่ชาม
  3. ทอด 2 นาที

น้องทำตามได้ นั่นแหละคือ โปรแกรม — แค่เราเปลี่ยนจากภาษาคนเป็นภาษาที่คอมเข้าใจ

ภาษา Go ก็คือภาษาหนึ่งที่เราใช้สั่งคอม เหมือนภาษาไทย อังกฤษ แต่เอาไว้คุยกับคอม

ทำไมต้อง Go? ไม่ใช้ภาษาอื่นล่ะ?

ให้นึกภาพแบบนี้:

  • Python = รถจักรยาน ขี่ง่าย เรียนรู้ไว แต่ช้าเวลาขนของหนัก
  • C / Java = รถบรรทุก แรงมาก แต่ขับยาก ต้องเรียนเยอะ
  • Go = รถกระบะ ขับง่ายเหมือนจักรยาน แต่ขนของหนักได้เกือบเท่ารถบรรทุก

คนสร้าง Go คือทีม Google เขารำคาญว่าภาษาเดิมๆ ช้า เขียนยาก เลยสร้างภาษาใหม่ที่:

  1. เขียนง่าย อ่านง่าย
  2. ทำงานเร็วมาก
  3. ทำงานหลายอย่างพร้อมกันเก่ง (เรื่องนี้สำคัญ เดี๋ยวเจอในบท Channels)
  4. เอาไปรันที่ไหนก็ได้ไฟล์เดียวจบ

ใช้จริงที่ไหน? Docker, Kubernetes, ระบบหลังบ้านของ LINE, Shopee, TikTok ใช้ Go หมด

เล่มนี้ใช้ Go เวอร์ชันอะไร?

ตอนนี้ (ตุลาคม 2026) เวอร์ชันใหม่สุดคือ Go 1.27 ออกเมื่อ 19 สิงหาคม 2026

ไม่ต้องตกใจถ้าคุณเคยเห็นเลข 1.22, 1.23 ในคลิปเก่าๆ มันคืออันเดียวกันแค่เก่ากว่า วิธีเขียนพื้นฐานเหมือนกันหมด เล่มนี้จะบอกจุดที่เวอร์ชันใหม่ทำง่ายขึ้นด้วยภาษาง่ายๆ

ต้องมีพื้นฐานอะไรก่อน?

ตอบ: ไม่ต้องมีเลย ขอแค่:

  1. ใช้คอมเปิดไฟล์ สร้างโฟลเดอร์ได้
  2. พิมพ์ภาษาอังกฤษได้
  3. กล้าลองผิดลองถูก

ถ้าคุณเคยใช้เครื่องคิดเลข คุณก็เรียน Go ได้แล้ว


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 0.5: มีอะไรใหม่ใน Go 1.27 ฉบับภาษาง่ายๆ

ถ้าคุณเพิ่งเริ่มเรียนวันนี้ คุณโชคดี เพราะ Go 1.26 และ 1.27 (ต้นปี - สิงหาคม 2026) ทำให้ชีวิตมือใหม่ง่ายขึ้นเยอะ อธิบายแบบบ้านๆ:

1. สร้างตัวชี้ (pointer) ง่ายขึ้นด้วย new(ค่า)

เมื่อก่อนถ้าอยากได้กล่องพร้อมที่อยู่ต้องทำ 2 บรรทัด:

go
// แบบเก่า (ยังใช้ได้ แต่ยาว)
x := int64(300)
ptr := &x // & = ขอที่อยู่ของกล่อง x

ตอนนี้ทำบรรทัดเดียวจบ:

go
// แบบใหม่ Go 1.26 ขึ้นไป (สั้นกว่า)
ptr := new(int64(300))

แปลไทย: new(300) = "สร้างกล่องใส่เลข 300 พร้อมบอกที่อยู่มาเลย" จำแค่นี้พอ เดี๋ยวเจออีกทีตอน struct

2. ตั้งค่า struct ที่ซ้อนกันง่ายขึ้น

สมมติมีกล่องซ้อนกล่อง เมื่อก่อนต้องแกะทีละชั้น ตอนนี้ใส่ทีเดียวได้เลย:

go
type Habitat struct {
    Burrow string
}
type Gopher struct {
    Name string
    Habitat // ฝังกล่อง Habitat ไว้ข้างใน
}
// Go 1.27 ทำแบบนี้ได้เลย ไม่ต้องพิมพ์ Habitat{...} ซ้อนอีกชั้น
g := Gopher{
    Name:   "Gopher",
    Burrow: "Burrow #42",
}

เหมือนกรอกฟอร์ม จากที่ต้องเปิดซองในซอง ตอนนี้เขียนรวดเดียวจบ

3. ส่งของเข้า channel ฉลาดขึ้น (เกี่ยวกับบทที่ 13!)

เมื่อก่อนถ้าใช้ฟังก์ชันแบบครอบจักรวาล (generic) ต้องบอกชนิดเอง ตอนนี้ Go เดาให้:

go
// Go 1.27 เดา T = int ให้เอง ไม่ต้องพิมพ์ [int] ย้ำ
ch := make(chan func(int) string, 1)
ch <- GenericFormatter // ส่งฟังก์ชันเข้า channel ได้เลย

มือใหม่จำแค่ว่า: ถ้าเห็นโค้ดส่งฟังก์ชันเข้า channel แล้วไม่มี [ ] ต่อท้าย ไม่ต้องตกใจ มันคือของใหม่ที่ทำให้สั้นลงนั่นเอง

4. เครื่องมือช่วยแก้โค้ดเก่าให้เอง go fix

Go 1.26 รื้อ go fix ใหม่หมด เหมือนมีครูมานั่งแก้การบ้านให้:

bash
go fix ./...

มันจะช่วยเปลี่ยนโค้ดเก่าให้เป็นแบบใหม่เอง เช่น เปลี่ยนวิธีเขียน slice, วิธีใช้ atomic ให้ ไม่ต้องจำเองทั้งหมด มือใหม่ใช้คู่กับ:

bash
go fmt ./...  # จัดหน้าให้สวย
go vet ./...  # ตรวจว่าสั่งอะไรผิดไหม
5. ดูคู่มือ + จัดของง่ายขึ้น
bash
go doc fmt.Println
go doc example.com/pkg@v1.2.3

go mod tidy
6. เบื้องหลังที่เร็วขึ้น แต่เราไม่ต้องทำอะไร
  • ขยะเก็บเร็วขึ้น (Green Tea GC): โปรแกรมค้างน้อยลง เหมือนมีแม่บ้านเก็บขยะไวขึ้น เราไม่ต้องสั่งอะไร
  • แปลง JSON เร็วขึ้น (encoding/json/v2): ส่งข้อมูลไปมาระหว่างเว็บเร็วขึ้น โค้ดเดิมของเราก็เร็วขึ้นเองโดยไม่ต้องแก้
  • เทสต์เน็ตง่ายขึ้น: มี httptest.NewTestServer + testing/synctest ไว้ซ้อมเขียนเว็บโดยไม่ต้องต่อเน็ตจริง (ขั้นสูง ข้ามก่อนได้)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 1: ติดตั้ง Go ทีละขั้น + Hello World บรรทัดต่อบรรทัด
ขั้นที่ 1: โหลด Go
  1. เข้าเว็บ https://go.dev/dl/
  2. กดปุ่มดาวน์โหลดตัวบนสุด มันจะเลือกให้เอง (ตอนนี้จะได้ไฟล์ชื่อประมาณ go1.27.1) ถ้าเลือกเอง:
    • Mac รุ่นใหม่ (M1/M2/M3/M4) = macOS ARM64
    • Mac รุ่นเก่า = macOS x86-64
    • Windows = Windows x86-64
  3. ดับเบิลคลิกติดตั้ง กด Next ไปเรื่อยๆ

เช็คว่าสำเร็จไหม เปิด Terminal (Mac) หรือ PowerShell (Windows) พิมพ์:

bash
go version

ถ้าขึ้นแบบนี้คือผ่าน (เลขตรงนี้ต้องเป็น 1.27 ขึ้นไปถึงจะเป็นปัจจุบัน):

go version go1.27.1 darwin/arm64

เลขเวอร์ชันต่างกันไม่เป็นไร แต่ถ้าขึ้น go1.22 หรือ go1.20 แปลว่าคุณลงไว้นานแล้ว แนะนำให้โหลดใหม่จากเว็บจะดีที่สุด

ถ้าขึ้น command not found = ติดตั้งยังไม่เสร็จ ให้ปิดหน้าต่างแล้วเปิดใหม่

ขั้นที่ 2: ติดตั้ง VS Code (กระดาษสำหรับเขียนโค้ด)
  1. โหลด https://code.visualstudio.com/
  2. เปิดมาแล้วกด Extensions (รูปตัวต่อ) พิมพ์ Go กด Install อันของ Google
ขั้นที่ 3: สร้างโปรเจกต์แรก

พิมพ์ใน Terminal ทีละบรรทัด:

bash
mkdir hello
cd hello
go mod init hello

อธิบาย:

  • mkdir hello = สร้างโฟลเดอร์ชื่อ hello (เหมือนสร้างแฟ้ม)
  • cd hello = เดินเข้าไปในโฟลเดอร์
  • go mod init hello = บอก Go ว่า "โฟลเดอร์นี้จะเป็นโปรเจกต์นะ" จะได้ไฟล์ go.mod มาหนึ่งไฟล์
ขั้นที่ 4: เขียน Hello World

สร้างไฟล์ชื่อ main.go ในโฟลเดอร์ hello แล้วพิมพ์ตามนี้เป๊ะๆ:

go
package main

import "fmt"

func main() {
    fmt.Println("สวัสดี Go!")
}

รันด้วยคำสั่ง:

bash
go run .

จะเห็นคำว่า สวัสดี Go! ขึ้นมา ยินดีด้วย! คุณเขียน Go ได้แล้ว!

อธิบายทุกบรรทัด แบบไม่ข้ามเลย
go
package main

แปลว่า "ไฟล์นี้เป็นโปรแกรมหลักที่จะรัน" เหมือนหน้าปกหนังสือ ทุกโปรแกรมต้องมีบรรทัดนี้

go
import "fmt"

แปลว่า "ขอยืมเครื่องมือชื่อ fmt มาหน่อย" fmt ย่อจาก format เป็นกล่องเครื่องมือสำหรับพิมพ์ข้อความ ถ้าไม่ import บรรทัดนี้ เราจะพิมพ์อะไรออกจอไม่ได้

go
func main() {

แปลว่า "เริ่มทำงานตรงนี้นะ" คอมจะวิ่งมาหา main ก่อนเสมอ เหมือนประตูทางเข้าบ้าน

go
    fmt.Println("สวัสดี Go!")
  • fmt = หยิบกล่องเครื่องมือมา
  • .Println = เลือกอุปกรณ์ "พิมพ์แล้วขึ้นบรรทัดใหม่" (Print Line)
  • ("สวัสดี Go!") = ข้อความที่จะพิมพ์ ข้อความต้องอยู่ใน " " เสมอ
go
}

ปิดประตู จบการทำงาน

ลองเล่น: เปลี่ยนข้อความใน " " เป็นชื่อตัวเองแล้ว go run . ใหม่สิ

go
fmt.Println("สวัสดี ผมชื่อสมชาย")
fmt.Println("ผมกำลังเรียน Go วันแรก")

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 2: กฎ 3 ข้อของคนเขียน Go เก่ง (จาก Effective Go)

เอกสาร https://go.dev/doc/effective_go สอนไว้เยอะ แต่สำหรับมือใหม่จำแค่ 3 ข้อนี้ก่อน:

กฎข้อ 1: อย่าเถียงเรื่องจัดหน้า ให้เครื่องจัดให้

ใน Go เราไม่ต้องมานั่งเคาะ space เอง มีคำสั่ง:

bash
go fmt ./...

มันจะจัดย่อหน้า วงเล็บ ช่องว่างให้สวยเอง เหมือนมีแม่บ้านจัดห้องให้

ใน VS Code แค่กด Save มันก็ fmt ให้อัตโนมัติแล้ว

ตัวอย่าง เครื่องจะแก้จากแบบรกๆ:

go
type T struct {
    name string // name of the object
    value int // its value
}

เป็นแบบสวยๆ:

go
type T struct {
    name  string // name of the object
    value int    // its value
}

เราไม่ต้องทำเอง!

กฎข้อ 2: ชื่อดี โปรแกรมดีครึ่งหนึ่งแล้ว
  • ชื่อที่มองเห็นจากข้างนอกได้ต้องขึ้นต้นตัวใหญ่: Owner, Add
  • ชื่อที่ใช้แค่ข้างในขึ้นต้นตัวเล็ก: owner, add
  • ใช้ชื่อสั้นๆ: bufio.Reader ดีกว่า bufio.BufReader (ไม่ต้องย้ำคำว่า Buf)
  • หลายคำให้ต่อกันแบบอูฐ: userName ไม่ใช่ user_name
  • ถ้ามี 1 หน้าที่ให้ลงท้ายด้วย er: Reader (นักอ่าน), Writer (นักเขียน)

เหมือนตั้งชื่อลูก ตั้งดีเรียกง่าย ตั้งยาวเรียกยาก

กฎข้อ 3: วงเล็บปีกกาห้ามขึ้นบรรทัดใหม่

แบบนี้ถูก:

go
if x > 0 {
    fmt.Println("บวก")
}

แบบนี้ผิด (ห้ามทำ):

go
if x > 0
{
    fmt.Println("บวก")
}

เหตุผล: Go จะแอบเติม ; ให้ตอนขึ้นบรรทัดใหม่ ทำให้พัง เราเลยต้องเอา { ไว้บรรทัดเดียวกัน จำแค่นี้พอ


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 3: ตัวแปร คือกล่องใส่ของ
กล่องคืออะไร?

นึกถึงกล่องลัง 1 ใบ แปะป้ายชื่อไว้ ข้างในใส่ของได้ 1 อย่าง เปลี่ยนของข้างในได้ตลอด นั่นคือ ตัวแปร (variable)

ใน Go สร้างกล่องแบบนี้:

go
var age int
age = 20
fmt.Println(age) // 20
  • var = "จะสร้างกล่องแล้วนะ"
  • age = ชื่อป้ายแปะกล่อง
  • int = บอกว่ากล่องนี้ใส่ได้แค่ตัวเลขจำนวนเต็ม (integer)
  • age = 20 = เอาเลข 20 ใส่กล่อง
ชนิดของที่ใส่กล่องได้ (type)
ชนิดแปลว่าตัวอย่าง
intเลขจำนวนเต็ม1, -5, 100
float64เลขทศนิยม3.14, -0.5
stringข้อความ"สวัสดี", "abc"
boolจริง/เท็จtrue, false
go
var name string = "สมชาย"
var height float64 = 175.5
var isStudent bool = true
ทางลัด := (ใช้บ่อยที่สุด)

คน Go ขี้เกียจพิมพ์ var เลยใช้ := แปลว่า "สร้างกล่อง + ใส่ของเลย + เดาชนิดให้ด้วย":

go
name := "สมชาย"   // Go รู้เองว่าเป็น string
age := 20         // Go รู้เองว่าเป็น int
height := 175.5   // Go รู้เองว่าเป็น float64
go
age := 20
age = 21 // เปลี่ยนของในกล่องเดิม ไม่ต้อง := แล้ว
ค่าเริ่มต้น (zero value)

ถ้าสร้างกล่องแล้วยังไม่ใส่ของ Go จะใส่ของเปล่าๆ ให้เอง ไม่ปล่อยขยะ:

  • int ได้ 0
  • string ได้ "" (ว่าง)
  • bool ได้ false
go
var x int
fmt.Println(x) // 0 ไม่ error

ดีมาก! ไม่ต้องกลัวลืมใส่ค่าแล้วโปรแกรมระเบิด

แบบฝึกหัดบทนี้:

  1. สร้างตัวแปรชื่อตัวเอง อายุ ส่วนสูง แล้วพิมพ์ออกมา
  2. ลองเอา string + int บวกกันดู จะ error ว่าอะไร? (นี่แหละที่เรียกว่า type safety)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 4: สั่งให้คอมพิวเตอร์พูดด้วย fmt

เรารู้จัก fmt.Println แล้ว มาดูญาติของมัน:

go
fmt.Println("สวัสดี", 23)      // พิมพ์หลายอย่างคั่นด้วยช่องว่าง + ขึ้นบรรทัดใหม่
fmt.Print("สวัสดี", 23)        // เหมือนกันแต่ไม่ขึ้นบรรทัดใหม่
fmt.Printf("อายุ %d ปี\n", 23) // พิมพ์แบบควบคุมเอง %d=เลข %s=ข้อความ
  • %d = จองที่ไว้ใส่เลขจำนวนเต็ม (d = digit)
  • %s = จองที่ไว้ใส่ข้อความ (s = string)
  • %v = จองที่ไว้ใส่อะไรก็ได้ (v = value) ใช้บ่อยสุดตอนหัด
  • \n = ขึ้นบรรทัดใหม่
go
name := "สมชาย"
age := 20
fmt.Printf("ผมชื่อ %s อายุ %d ปี\n", name, age)
fmt.Printf("%v\n", name) // %v ก็พิมพ์ได้เหมือนกัน

จำแค่ Println กับ Printf ก็พอใช้ 80% แล้ว


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 5: ถ้า...ก็... ถ้าไม่...ก็... (if)

ชีวิตเราตัดสินใจแบบนี้ทุกวัน: "ถ้าฝนตกก็เอาร่ม ถ้าไม่ตกก็ไม่เอา" คอมก็เหมือนกัน

go
age := 20

if age >= 18 {
    fmt.Println("บรรลุนิติภาวะแล้ว")
}

อ่านเป็นไทย: "ถ้า age มากกว่าเท่ากับ 18 ก็พิมพ์..."

ถ้า... ไม่งั้น... (if-else)
go
score := 45

if score >= 50 {
    fmt.Println("สอบผ่าน")
} else {
    fmt.Println("สอบตก")
}
หลายเงื่อนไข (else if)
go
score := 85

if score >= 80 {
    fmt.Println("ได้ A")
} else if score >= 70 {
    fmt.Println("ได้ B")
} else if score >= 60 {
    fmt.Println("ได้ C")
} else {
    fmt.Println("ตก")
}
ทริกของคน Go เก่ง: ตรวจ error แล้วรีบจบ
go
f, err := os.Open("a.txt")
if err != nil {
    fmt.Println("เปิดไม่ได้:", err)
    return
}
fmt.Println("เปิดได้แล้ว ใช้ f ต่อได้เลย")

แปลว่า: "ถ้า error ไม่ใช่ค่าว่าง (nil = ว่าง) แปลว่าพัง ให้จบเลย ไม่ต้องทำต่อ" โค้ดจะได้ไม่ซ้อนกันเป็นพีระมิด อ่านง่าย

ศัพท์: nil = ว่างเปล่า ไม่มีอะไรเลย, != = ไม่เท่ากับ

แบบฝึกหัด: เขียนโปรแกรมอายุ ถ้าน้อยกว่า 13 พิมพ์ "เด็ก", น้อยกว่า 20 พิมพ์ "วัยรุ่น", นอกนั้น "ผู้ใหญ่"


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 6: ทำงานซ้ำๆ ด้วย for (มีแค่อันเดียว!)

ภาษาอื่นมี while, for, do-while เยอะแยะ Go มีแค่ for อันเดียวแต่ทำได้ 3 แบบ:

แบบที่ 1: นับเลข (เหมือน for ภาษาอื่น)
go
for i := 0; i < 5; i++ {
    fmt.Println(i) // 0 1 2 3 4
}

อ่านทีละส่วน for i := 0; i < 5; i++:

  1. i := 0 = เริ่มที่ 0
  2. i < 5 = ถ้ายังน้อยกว่า 5 ก็ทำต่อ
  3. i++ = เสร็จรอบนึงให้บวกเพิ่ม 1 (++ = +1)

เหมือน "วิ่ง 5 รอบสนาม นับรอบไปด้วย"

แบบที่ 2: ทำตราบที่เงื่อนไขยังจริง (เหมือน while)
go
n := 1
for n < 100 {
    n = n * 2
    fmt.Println(n)
}
// 2 4 8 16 32 64 128
แบบที่ 3: ทำไปเรื่อยๆ ไม่หยุด (ต้องมีทางออกเอง)
go
for {
    fmt.Println("ทำตลอดไป...")
    break // break = กระโดดออก
}

ถ้าไม่มี break จะวนไม่จบ คอมค้างนะ!

วนของในกล่องรวมด้วย range (ใช้เยอะมาก)
go
fruits := []string{"แอปเปิ้ล", "กล้วย", "ส้ม"}
for index, value := range fruits {
    fmt.Printf("ช่องที่ %d คือ %s\n", index, value)
}
  • range = "ไล่ทีละชิ้น"
  • index = เลขช่อง (เริ่ม 0)
  • value = ของในช่องนั้น

ถ้าอยากได้แค่ค่า ไม่เอาหมายเลข ใช้ _ ทิ้งไป (_ = ถังขยะ):

go
for _, value := range fruits {
    fmt.Println(value)
}

แบบฝึกหัด: ใช้ for พิมพ์สูตรคูณแม่ 2 (2x1=2 ... 2x12=24)


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 7: ฟังก์ชัน คือเครื่องจักร
ทำไมต้องมีฟังก์ชัน?

สมมติคุณต้องบวกเลข 10 ครั้ง จะเขียน a+b ซ้ำ 10 รอบไหม? เหนื่อย! เราสร้าง เครื่องจักร ขึ้นมาตัวหนึ่ง โยนของเข้าไป มันคายผลออกมา เรียกใช้กี่ครั้งก็ได้ นั่นคือฟังก์ชัน

go
func Add(a int, b int) int {
    return a + b
}

func main() {
    fmt.Println(Add(2, 3))  // 5
    fmt.Println(Add(10, 20)) // 30
}

อธิบาย:

  • func = จะสร้างเครื่องจักรแล้ว
  • Add = ชื่อเครื่อง
  • (a int, b int) = ช่องใส่ของเข้า 2 ช่อง รับแต่ int
  • int หลังวงเล็บ = ชนิดของที่จะคายออก
  • return a + b = คายผลออกมา
ฟังก์ชันคืนได้หลายค่า (ของดีของ Go!)

ภาษาอื่นคืนได้ค่าเดียว แต่ Go คืนได้หลายค่า เหมาะกับการคืน "ผล + error":

go
func Divide(a, b float64) (float64, string) {
    if b == 0 {
        return 0, "หารด้วยศูนย์ไม่ได้"
    }
    return a / b, ""
}

func main() {
    result, msg := Divide(10, 2)
    fmt.Println(result, msg) // 5 ""
    result2, msg2 := Divide(10, 0)
    fmt.Println(result2, msg2) // 0 หารด้วยศูนย์ไม่ได้
}
ฟังก์ชันไม่ต้องมีชื่อก็ได้? มี แต่เอาไว้ก่อน

มือใหม่จำแค่นี้: สร้างด้วย func เรียกใช้ด้วย ชื่อ(...)

แบบฝึกหัด: สร้างฟังก์ชัน Greet(name string) string คืนค่า "สวัสดี " + name


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 8: เก็บของหลายชิ้น Array Slice Map
Array = ตู้ล็อกเกอร์ที่มีจำนวนช่องตายตัว
go
var a [3]int // ตู้ 3 ช่องใส่เลขได้เท่านั้น
a[0] = 10
a[1] = 20
a[2] = 30
fmt.Println(a) // [10 20 30]

เลขใน [ ] คือจำนวนช่อง เปลี่ยนไม่ได้! [3]int กับ [5]int ถือว่าคนละชนิดกัน

ข้อเสีย: ถ้าสร้าง 3 ช่องแล้วอยากได้ช่องที่ 4 ทำไม่ได้ ต้องสร้างตู้ใหม่ คนเลยไม่ค่อยใช้ Array

Slice = ตู้ที่ยืดได้ (ใช้ตัวนี้แหละ!)
go
s := []int{1, 2, 3} // ไม่ต้องบอกจำนวนช่อง
s = append(s, 4, 5) // append = ต่อท้าย
fmt.Println(s) // [1 2 3 4 5]

จำสูตร: s = append(s, ของที่จะเพิ่ม) ต้องมี s = ข้างหน้าด้วยนะ! ลืมบ่อยที่สุด

หั่น slice ได้ด้วย (slicing):

go
buf := []int{1, 2, 3, 4, 5}
fmt.Println(buf[0:2]) // [1 2] เอาช่อง 0 ถึงก่อนช่อง 2
Map = สมุดโทรศัพท์ (ชื่อ → เบอร์)
go
phone := map[string]string{
    "แม่":  "081-111-1111",
    "พ่อ":  "082-222-2222",
}
fmt.Println(phone["แม่"]) // 081-111-1111
phone["เพื่อน"] = "083-333-3333" // เพิ่ม
delete(phone, "พ่อ") // ลบ

เช็คว่ามีชื่อนี้ไหมด้วย comma-ok (ถ้าไม่มีจะได้ค่าว่าง ต้องแยกให้ออก):

go
เบอร์, มีไหม := phone["ลุง"]
if มีไหม {
    fmt.Println("เจอแล้ว", เบอร์)
} else {
    fmt.Println("ไม่มีชื่อนี้")
}

เปรียบเทียบให้จำ:

  • Array = ล็อกเกอร์ปูน ขยายไม่ได้
  • Slice = กระเป๋าผ้ายืดได้
  • Map = ดิกชันนารี เปิดหาจากคำ

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 9: switch เลือกทางแยก

if เหมาะกับ 2-3 ทาง ถ้าหลายทางใช้ switch สวยกว่า:

go
day := "จันทร์"

switch day {
case "จันทร์":
    fmt.Println("วันแรกของสัปดาห์ สู้ๆ")
case "ศุกร์":
    fmt.Println("พรุ่งนี้หยุดแล้ว!")
case "เสาร์", "อาทิตย์": // หลายค่าคั่นด้วย comma ได้
    fmt.Println("วันหยุด!")
default: // นอกนั้น
    fmt.Println("วันธรรมดา")
}

switch ของ Go ไม่ต้องมี break มันหยุดเอง ไม่ไหลทะลุเหมือนภาษา C

แบบไม่ใส่ค่าก็ได้ (เท่ากับ switch true):

go
score := 85
switch {
case score >= 80:
    fmt.Println("A")
case score >= 70:
    fmt.Println("B")
}

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 10: struct + method คืออะไร
struct = แบบฟอร์มรวมหลายช่อง

ตัวแปร 1 ตัวเก็บได้ 1 อย่าง แต่คน 1 คนมีทั้งชื่อ อายุ ส่วนสูง จะทำไง? เอากล่องหลายใบมัดรวมกันเรียก struct:

go
type Person struct {
    Name   string
    Age    int
    Height float64
}

func main() {
    p := Person{Name: "สมชาย", Age: 20, Height: 175.5}
    fmt.Println(p.Name) // สมชาย
    fmt.Println(p.Age)  // 20
}
  • type Person struct = ออกแบบฟอร์มชื่อ Person
  • Name string = ช่องชื่อใส่ข้อความได้
  • ขึ้นต้นตัวใหญ่ (Name) = คนอื่นเห็นได้ ขึ้นต้นตัวเล็ก = เห็นแค่ใน package เดียวกัน
method = ฟังก์ชันประจำฟอร์ม
go
func (p Person) Greet() string {
    return "สวัสดี ผมชื่อ " + p.Name
}

func main() {
    p := Person{Name: "สมชาย"}
    fmt.Println(p.Greet())
}

(p Person) ข้างหน้าคือบอกว่า "ฟังก์ชันนี้เป็นของ Person นะ" เรียกใช้ด้วย p.Greet() แทน Greet(p)

ถ้า method ต้องแก้ค่าใน struct ต้องใช้ pointer *:

go
func (p *Person) GrowUp() {
    p.Age++ // แก้ตัวจริง
}

จำง่ายๆ: แค่อ่านใช้ธรรมดา จะแก้ต้องมี *


✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 11: error ไม่ต้องกลัว

ใน Go ไม่มี try-catch แบบภาษาอื่น แต่ใช้วิธีตรงๆ: ฟังก์ชันไหนพังได้ให้คืน error มาด้วย แล้วเช็คทันที

go
f, err := os.Open("ไม่มีไฟล์นี้.txt")
if err != nil {
    fmt.Println("เปิดไม่ได้:", err)
    return
}
fmt.Println("เปิดได้แล้ว")
  • err = ตัวแปรเก็บความผิดพลาด
  • nil = ว่าง (แปลว่าไม่ผิด)
  • err != nil = "ถ้าไม่ว่าง แปลว่ามีเรื่อง!"

สร้าง error เอง:

go
import "errors"

func CheckAge(age int) error {
    if age < 0 {
        return errors.New("อายุติดลบไม่ได้")
    }
    return nil // nil = ปกติดี
}

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 12: defer คือคำสัญญาว่าจะกลับมาทำ

defer แปลว่า "เลื่อนไปทำตอนจะจบ" เหมาะกับงานเก็บกวาด เช่น เปิดไฟล์แล้วต้องปิด:

go
func ReadFile() {
    f, _ := os.Open("a.txt")
    defer f.Close() // สัญญาว่าก่อนจบฟังก์ชันจะปิดไฟล์ให้ ไม่ว่าจะออกทางไหน

    // ... อ่านไฟล์ ...
    // ... ถ้ามี return ตรงไหน f.Close() ก็ยังรันอยู่ดี
}

เหมือนบอกเพื่อนว่า "ไปเที่ยวก่อนนะ ก่อนกลับจะล็อกประตูให้" ไม่ว่าจะกลับทางไหนก็ล็อกแน่

ถ้า defer หลายอัน จะทำจากหลังมาหน้า (LIFO):

go
defer fmt.Println("1")
defer fmt.Println("2")
defer fmt.Println("3")
// พิมพ์: 3 2 1

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 13: goroutine + channel หัวใจของ Go
ปัญหา: งานช้า ทำทีละอย่างไม่ไหว

สมมติคุณเปิดร้านอาหาร มีลูกค้า 3 โต๊ะ ถ้ามีพ่อครัวคนเดียวต้องทำทีละโต๊ะ โต๊ะ 3 รอนาน

วิธีแก้: จ้างพ่อครัว 3 คนทำพร้อมกัน! ใน Go พ่อครัว 1 คน = goroutine 1 ตัว

goroutine = เติมคำว่า go ก็ได้คนเพิ่ม
go
package main

import (
    "fmt"
    "time"
)

func Cook(name string) {
    for i := 0; i < 3; i++ {
        fmt.Println(name, "กำลังทำ...")
        time.Sleep(200 * time.Millisecond)
    }
}

func main() {
    go Cook("พ่อครัว A") // จ้างเพิ่ม! ทำพร้อมกัน
    Cook("พ่อครัว B")    // คนเดิมทำ
}

แค่เติม go ข้างหน้าก็จบ! ไม่ต้องสร้าง thread ยุ่งยากเหมือนภาษาอื่น เปิดเป็นหมื่นตัวยังไหว

แล้วพ่อครัวคุยกันยังไง? ใช้ channel!

พ่อครัวทำพร้อมกันแต่ต้องส่งจานให้กัน จะตะโกนก็มั่ว ต้องมี รางส่งจาน วางจานลงไป อีกคนหยิบไป นั่นคือ channel

คำขวัญของ Go:

go
func main() {
    ch := make(chan string) // สร้างรางส่งข้อความ

    go func() {
        ch <- "ผัดกะเพราเสร็จแล้ว!" // <- คือวางจานลงราง (ส่ง)
    }()

    msg := <-ch // รับจานจากราง (รับ)
    fmt.Println(msg)
}

จำสัญลักษณ์:

  • ch <- "xxx" = ส่งเข้า (ลูกศรชี้เข้า channel)
  • x := <-ch = รับออก (ลูกศรชี้ออกจาก channel)
รางมี 2 แบบ

1. แบบไม่มีที่พัก (unbuffered) — วางแล้วต้องมีคนรับทันที เหมือนส่งลูกบอลมือต่อมือ:

go
ch := make(chan int) // ไม่ใส่เลข = ไม่มีที่พัก
go func() { ch <- 1 }()
fmt.Println(<-ch) // ต้องมีคนรอรับ ไม่งั้นค้าง

2. แบบมีกล่องพัก (buffered) — วางทิ้งไว้ได้ถ้ากล่องไม่เต็ม เหมือนตู้พัสดุ:

go
ch := make(chan int, 2) // พักได้ 2 ชิ้น
ch <- 1
ch <- 2
fmt.Println(<-ch, <-ch) // 1 2

มือใหม่เริ่มจากแบบที่ 1 ก่อนก็ได้ เข้าใจง่ายกว่า

ส่งหลายชิ้น + บอกว่าหมดแล้วด้วย close
go
func main() {
    ch := make(chan int, 5)

    go func() {
        for i := 1; i <= 3; i++ {
            ch <- i
        }
        close(ch) // ตะโกนบอกว่าหมดแล้ว! สำคัญมาก
    }()

    // range = หยิบไปเรื่อยๆ จนกว่าจะ close
    for v := range ch {
        fmt.Println("ได้รับ", v)
    }
}
  • คนส่งเท่านั้นที่ควร close
  • คนรับใช้ for v := range ch รับจนหมดเอง ไม่ต้องนับ
select = รอหลายรางพร้อมกัน

สมมติมี 2 ราง ไม่รู้รางไหนมาก่อน ใช้ select นั่งเฝ้าทั้งคู่ อันไหนมาก่อนเอาอันนั้น:

go
select {
case v := <-ch1:
    fmt.Println("ราง 1 มาแล้ว", v)
case v := <-ch2:
    fmt.Println("ราง 2 มาแล้ว", v)
case <-time.After(1 * time.Second):
    fmt.Println("รอ 1 วิแล้ว ไม่มีมาเลย เลิก!")
}

เหมือนนั่งเฝ้า 2 เตา เตาไหนดังก่อนวิ่งไปเตานั้น + ตั้งนาฬิกาปลุกกันรอเก้อ

ตัวอย่างจริง: สายพานโรงงาน (pipeline)
go
package main

import "fmt"

// โรงงานขั้นที่ 1: ผลิตเลข
func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

// โรงงานขั้นที่ 2: ยกกำลังสอง
func sq(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func main() {
    for n := range sq(gen(1, 2, 3, 4)) {
        fmt.Println(n) // 1 4 9 16
    }
}

ของไหล gen → sq → พิมพ์ เป็นทอดๆ ไม่ต้องรอให้เสร็จทั้งล็อต

ถ้าพังจะขึ้นว่าอะไร?
ข้อความแดงแปลว่าแก้ยังไง
all goroutines are asleep - deadlock!ทุกคนรอของแต่ไม่มีใครส่งลืม go หรือลืมคนส่ง
send on closed channelส่งของเข้าราที่ปิดแล้วอย่าส่งหลัง close
โปรแกรมค้างไม่จบลืม close เลยรอตลอดไปใส่ close ฝั่งคนส่ง

ตรวจหางานแย่งกันด้วย:

bash
go run -race .

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ตอนที่ 14: โปรเจกต์จบเล่ม + ไปต่อยังไง
โปรเจกต์: เช็คราคาหุ้นพร้อมกัน

นี่คือพลัง goroutine + channel ของจริง จากที่ต้องรอทีละตัว 5 ตัว = 5 รอบ กลายเป็นรอรอบเดียว:

go
package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

func fetchPrice(symbol string) int {
    time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
    return rand.Intn(1000)
}

func main() {
    symbols := []string{"AAPL", "GOOG", "TSLA", "AMZN", "MSFT"}

    type Result struct {
        Symbol string
        Price  int
    }

    ch := make(chan Result, len(symbols))
    var wg sync.WaitGroup // สมุดนับว่าเหลือกี่งาน

    for _, s := range symbols {
        wg.Add(1) // จดว่าเพิ่ม 1 งาน
        go func(sym string) {
            defer wg.Done() // เสร็จแล้วขีดออก 1
            ch <- Result{sym, fetchPrice(sym)}
        }(s)
    }

    go func() {
        wg.Wait() // รอจนงานหมด
        close(ch) // ปิดราง
    }()

    for r := range ch {
        fmt.Printf("%s = %d\n", r.Symbol, r.Price)
    }
}

ลองรันดู จะเห็นว่าเร็วและลำดับมั่วๆ (ใครเสร็จก่อนพิมพ์ก่อน) นั่นแหละ concurrency!

แบบฝึกหัดไล่ระดับ (ทำได้คือจบจริง)

ง่าย:

  1. พิมพ์ชื่อตัวเอง 3 บรรทัดด้วย Println, Print, Printf
  2. สร้างตัวแปรชื่อ อายุ แล้วพิมพ์ว่า "อีก 10 ปีจะอายุ X"
  3. เขียน if เช็คเลขคู/คี่ (%2 == 0 = คู่)
  4. เขียน for พิมพ์ 1-10

กลาง: 5. เขียนฟังก์ชันบวก ลบ คูณ หาร 6. สร้าง slice เก็บชื่อเพื่อน 5 คนแล้ววนพิมพ์ 7. สร้าง map เก็บคะแนนเพื่อนแล้วหาคนได้เยอะสุด 8. สร้าง struct Student มี Name Score + method ตัดเกรด

ยาก (Channels): 9. สร้าง 2 goroutine ส่งเลขเข้า channel เดียวกัน 10. ใช้ select ทำ timeout 2 วินาที

ไปต่อที่ไหน (เรียงตามง่ายไปยาก) — อัปเดต 2026
  1. Tour of Go (เล่นในเว็บได้เลย): https://go.dev/tour/
  2. เขียน Go แบบถูกต้อง: https://go.dev/doc/code
  3. ต้นฉบับ Effective Go: https://go.dev/doc/effective_go
  4. เรื่อง Channels อย่างเดียว: https://go.dev/blog/share
  5. คลังโค้ดมาตรฐาน: https://pkg.go.dev/std
  6. มีอะไรใหม่ Go 1.27 (ภาษาอังกฤษ ง่ายๆ): https://go.dev/blog/go1.27
  7. มีอะไรใหม่ Go 1.26: https://go.dev/blog/go1.26

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


คำศัพท์ท้ายเล่ม (ลืมก็กลับมาดู)
คำแปลแบบบ้านๆ
โค้ดตัวหนังสือสั่งคอม
รันสั่งให้คอมทำ
ตัวแปรกล่องใส่ของ
typeชนิดของที่ใส่กล่องได้
ฟังก์ชันเครื่องจักร ใส่ของเข้า คายผลออก
structแฟ้มรวมกล่องหลายใบ
array/sliceตู้หลายช่อง / กระเป๋ายืดได้
mapสมุดโทรศัพท์
errorใบแจ้งว่าเจ๊งตรงไหน
nilว่าง ไม่มีอะไร
goroutineพ่อครัวเพิ่ม (ทำงานพร้อมกัน)
channelรางส่งจานระหว่างพ่อครัว
closeตะโกนบอกว่าของหมดแล้ว
selectนั่งเฝ้าหลายรางพร้อมกัน
deferสัญญาว่าก่อนจบจะกลับมาทำ
gofmtแม่บ้านจัดห้อง(โค้ด)ให้
go fixครูแก้การบ้าน เปลี่ยนโค้ดเก่าเป็นแบบใหม่ให้ (ของใหม่ Go 1.26)
go vetหมอตรวจโรค ตรวจว่าโค้ดมีที่ผิดไหม

เล่มนี้เขียนใหม่สำหรับคนไม่เคยเขียน Go เลย จากเอกสาร Effective Go https://go.dev/doc/effective_go#channels — อัปเดตสำหรับ Go 1.27.1 (ตุลาคม 2026) — ถ้าอ่านแล้วงงตรงไหน ให้รันโค้ดตรงนั้นซ้ำ เปลี่ยนเลข เปลี่ยนข้อความดู แล้วจะเข้าใจเอง ขอให้สนุกกับ Go ครับ!