บทเรียน 07 · Pointer & Struct

คุณกำลังแก้ของจริง หรือแก้ copy?

~15 นาที · ต้องมี: บทเรียน 06

บทที่แล้วคือ "แก้แล้วเปลี่ยนทั้งที่ไม่อยากให้เปลี่ยน" บทนี้คืออาการตรงข้าม — เขียนโค้ดแก้ค่า รันแล้วค่าไม่เปลี่ยน เหมือนโค้ดโดนเมิน รากของทั้งสองอาการคือกฎข้อเดียวกัน และมันเป็นกฎที่ Go ไม่มีข้อยกเว้นเลย

1 · กฎข้อเดียวที่ต้องจำ

กฎ

ใน Go ทุกอย่างเป็น pass by value 100% ไม่มีข้อยกเว้น
การใช้ pointer ไม่ได้ทำให้เป็น "pass by reference" — มันแค่เปลี่ยนจาก ก็อปของ เป็น ก็อปที่อยู่ของของ

ก่อนอื่น — * กับ & อ่านยังไง

ตัวแปรทุกตัวใน Go นั่งอยู่ที่ "ช่องหนึ่งในหน่วยความจำ" และช่องนั้นมีเลขที่อยู่ของมันเอง เหมือนบ้านมีเลขที่ pointer คือตัวแปรที่เก็บ "เลขที่บ้าน" ของตัวแปรอีกตัว ไม่ได้เก็บตัวของมันเอง

เขียนว่าอ่านว่าแปลว่า
*int"ชนิด pointer-to-int"ใช้ตอนประกาศชนิด — ตัวแปรนี้เก็บที่อยู่ของ int ไม่ใช่เก็บ int
&x"แอมเปอร์แซนด์ x" / "address of x"ขอที่อยู่ของ x ออกมา
*n"ดีเรฟเฟอเรนซ์ n"ใช้ตอนเขียนคำสั่ง — เดินตามที่อยู่ใน n ไปหยิบ/แก้ของจริงที่ปลายทาง

สังเกตว่า * มีสองหน้าที่คนละเรื่องกัน วิธีแยกคือ ถ้า * ติดอยู่กับชื่อชนิด = ประกาศชนิด · ถ้าติดอยู่กับชื่อตัวแปร = เดินตามที่อยู่ไป

func tryValue(n int)  { n = 999 }
func tryPtr(n *int)   { *n = 999 }

x := 42
tryValue(x)   // x ยังเป็น 42  — แก้ copy ของเลข
tryPtr(&x)    // x เป็น 999    — แก้ผ่านที่อยู่
tryValue(x) tryPtr(&x) ┌──────────┐ ก็อปเลข ┌──────────┐ ก็อป "ที่อยู่" │ x = 42 │──────────▶┌────────┐ │ x = 42 │◀─────────┐ └──────────┘ │ n = 42 │ └──────────┘ │ ไม่รู้จักกัน └────────┘ ▲ ┌─────┴────┐ แก้ตัวนี้ └────────────│ n=0x1040 │ ไม่มีผล *n เข้าถึงได้ └──────────┘

ตัว tryPtr ก็ได้ copy ของ pointer มาเหมือนกัน — แต่ copy ของ ที่อยู่บ้าน ก็ยังชี้ไปบ้านหลังเดิม เลยเข้าไปแก้ของในบ้านได้

📄 เทียบให้เห็นภาพ — ส่งสำเนาเอกสาร กับ ส่งที่อยู่บ้าน

เอกสารต้นฉบับวางบนโต๊ะที่บ้าน เพื่อนขอดู เรามีสองทาง
· ถ่ายสำเนาให้ไป — เพื่อนขีดเขียนยังไงก็ได้ ต้นฉบับยังเหมือนเดิม = tryValue(x)
· เขียนที่อยู่บ้านลงกระดาษแล้วยื่นให้ — เพื่อนเดินตามที่อยู่ไปแก้ต้นฉบับได้เลย (&x = ขอที่อยู่ · *n = เดินตามที่อยู่ไปแก้ของจริง)

ที่ไม่เหมือนกัน: กระดาษที่ยื่นให้เพื่อนก็ยังเป็นสำเนาอยู่ดี — ถ้าเพื่อนลบที่อยู่บนกระดาษตัวเองแล้วเขียนบ้านหลังอื่นแทน (โค้ดคือ n = &y) บ้านเราไม่ได้ย้ายตาม นี่คือเหตุผลที่ Go ยืนยันว่าทุกอย่างเป็น pass by value — สิ่งที่ก็อปไปคือกระดาษ ไม่ใช่บ้าน

🗣 ศัพท์ที่เพิ่งเรียนไป
pass by value
"ส่งสำเนาไป" — Go เป็นแบบนี้ทั้งหมด · เวลาแก้แล้วค่าไม่เปลี่ยน พูดว่า "ฟังก์ชันนี้รับแบบ pass by value ค่าข้างนอกเลยไม่ถูกแก้" จะสื่อกว่า "มันไม่ทำงาน" (pass by reference = ส่งตัวจริง เป็นของภาษาอื่น Go ไม่มี)
pointer
ตัวแปรที่เก็บ "ที่อยู่" ของตัวแปรอีกตัว · ถามว่า "ตรงนี้ควรรับเป็น *User หรือ User" ชัดกว่าถาม "ควรส่งแบบไหน"
address-of (&) / dereference (*n)
"ขอที่อยู่" กับ "เดินตามที่อยู่ไปหยิบของจริง" · มี pointer อยู่ในมือแล้วลืมใส่ * เรียกว่า "ลืม dereference" — พูดคำนี้แล้ว senior เข้าใจทันที
อาการที่เงียบจริง (คอมไพล์ผ่าน ไม่มี error): fmt.Println(n) พิมพ์ ที่อยู่ อย่าง 0x5a53ea10020 แทนที่จะเป็นเลข · n1 == n2 เทียบว่าชี้บ้านหลังเดียวกันไหม ไม่ได้เทียบค่า (ต้องเขียน *n1 == *n2)
ส่วนการเขียน n = 999 ทั้งที่ n เป็น *int ไม่ใช่บั๊กเงียบ — compiler ดักให้ตั้งแต่แรกว่า cannot use 999 (untyped int constant) as *int value in assignment

2 · จุดที่กฎนี้เล่นงานคุณ #1: for range

type User struct{ Name string; Age int }

users := []User{{"Alice", 30}, {"Bob", 25}}

for _, u := range users {
    u.Name = "CHANGED"
}

fmt.Println(users[0].Name)

พิมพ์ออกมาว่าอะไร?

Alice — ไม่เปลี่ยนเลย

ทุกรอบของ for range ตัวแปร u ได้ copy ของ struct มา แก้ u ก็คือแก้ copy พอจบรอบ copy ก็ถูกทิ้ง

แก้ให้ถูกต้องใช้ index แทน:

for i := range users {
    users[i].Name = "CHANGED"   // ✓ เข้าถึงช่องจริงใน slice
}
ทำไม slice ของ int ถึงแก้ได้ แต่ struct แก้ไม่ได้?

มันเหมือนกันทุกประการครับ — for _, v := range nums { v = 0 } ก็ไม่เปลี่ยนอะไรเหมือนกัน สิ่งที่ทำให้คนสับสนคือ users[i].Name = "x" (ผ่าน index) กับ u.Name = "x" (ผ่านตัวแปรวนลูป) หน้าตาคล้ายกันมาก แต่ตัวแรกแตะของจริง ตัวหลังแตะ copy
กลับไปที่ภาพเมื่อกี้: u คือสำเนาที่ถ่ายมาใหม่ทุกรอบ ส่วน users[i] คือต้นฉบับที่ยังวางอยู่บนโต๊ะ

3 · จุดที่กฎนี้เล่นงานคุณ #2: struct ใน map

type Product struct{ Name string; Qty int }

inv := map[string]Product{
    "SKU-1": {Name: "Mouse", Qty: 50},
}

inv["SKU-1"].Qty = 49

โค้ดนี้ผลลัพธ์เป็นยังไง?

compile ไม่ผ่าน — cannot assign to struct field inv["SKU-1"].Qty in map

inv["SKU-1"] คืน copy ไม่ใช่ที่อยู่จริง Go เลย ห้ามตั้งแต่ตอน compile ไม่ให้คุณเขียนโค้ดที่ดูเหมือนแก้ได้แต่ไม่มีผล

เหตุผลลึก: ข้างใน map ไม่ได้เรียงของไว้นิ่ง ๆ พอของเยอะขึ้นถึงจุดหนึ่ง map จะขยายตัวแล้วย้ายทุก key ไปวางที่ใหม่หมด (เรียกว่า rehash) แปลว่าที่อยู่ของ inv["SKU-1"] วันนี้กับพรุ่งนี้อาจคนละที่ → Go เลยไม่ยอมให้เอาที่อยู่ของมันออกมาเลยตั้งแต่แรก เพราะที่อยู่ที่ขยับได้ใช้ไม่ได้จริง → ค่าที่ "เอาที่อยู่ออกมาไม่ได้" แบบนี้ Go เรียกว่า non-addressable และ = ฝั่งซ้ายต้องเป็นของที่ addressable เสมอ

นิยาม: addressable

ค่าหนึ่ง ๆ เรียกว่า addressable ถ้าเอา & ไปแปะหน้ามันแล้วคอมไพล์ผ่าน — แปลว่าค่านั้นมี "ที่อยู่ถาวร" ให้ชี้ได้ ลองในหัวง่าย ๆ ว่าเขียน &สิ่งนั้น ได้ไหม

เขียน & ได้เขียนไม่ได้ (non-addressable)
&x — ตัวแปรธรรมดา&42 — ค่าคงที่ ไม่มีบ้านอยู่
&s[0] — ช่องใน slice&m["k"] — value ใน map
&p.Name — field ของตัวแปร struct&f() — ค่าที่ฟังก์ชันเพิ่งคืนมา

เกณฑ์เดียวคือ ของสิ่งนั้นมีที่อยู่ที่ Go รับประกันว่าไม่ขยับไหม — ตัวแปรมี, ช่องใน array/slice มี, แต่ค่าใน map ไม่มี เพราะ map ย้ายของได้ตอนโต
ภาพง่าย ๆ: map เหมือนร้านที่รื้อชั้นวางใหม่ทั้งร้านเวลาของเยอะขึ้น จดไว้ว่า "นมอยู่ชั้น 3 ช่องซ้าย" แล้วเอากลับมาใช้พรุ่งนี้ไม่ได้
และเพราะ compiler ไม่มีทางรู้ล่วงหน้าว่าร้านจะรื้อชั้นตอนไหน Go เลย ห้ามจดที่อยู่ตั้งแต่แรกทุกกรณี ต่อให้ map ก้อนนั้นจะไม่มีวันโตเลยก็ตาม — &m["k"] คอมไพล์ไม่ผ่านเสมอ (cannot take address of m["k"])

ทางออก 3 แบบ เรียงตามที่ใช้จริงบ่อยที่สุด:

// สมมติว่า Product เพิ่ม field Tags เข้ามาแล้ว:
//   type Product struct{ Name string; Qty int; Tags []string }

// 1) เก็บเป็น pointer ตั้งแต่แรก — ใช้บ่อยสุดในงานจริง
inv := map[string]*Product{"SKU-1": {Name: "Mouse", Qty: 50}}
inv["SKU-1"].Qty = 49          // ✓ ได้

// 2) อ่านออกมา แก้ แล้วยัดกลับทั้งก้อน
p := inv["SKU-1"]
p.Qty = 49
inv["SKU-1"] = p               // ✓ ได้

// 3) field ที่เป็น slice แก้ได้เลย แม้ map value จะเป็น non-addressable
//    (ตรงนี้กลับมาใช้ map แบบไม่ pointer เพื่อให้เห็นประเด็นชัด ๆ)
inv2 := map[string]Product{"SKU-1": {Name: "Mouse", Qty: 50, Tags: []string{"old"}}}
inv2["SKU-1"].Tags[0] = "new"  // ✓ ได้
inv2["SKU-1"].Qty = 49         // ✗ ยังไม่ได้ — compile error เหมือนเดิม

ข้อ 3 ดูขัดกับที่เพิ่งบอกไป แต่ไม่ขัดเลย เพราะ Tags เป็น slice ซึ่งตัวมันเองก็เป็นแค่ header (ป้ายสรุปเล็ก ๆ ที่จดไว้ว่าของจริงกองอยู่ที่ไหน ยาวเท่าไหร่) ที่ชี้ไป array ก้อนหนึ่ง (บทที่ 06) Tags[0] จึงไม่ได้แตะตัว map เลย แต่แตะ array ที่อยู่นอก map ต่างหาก — และช่องใน array นั้นมีที่อยู่คงที่ เลย addressable

คู่กับ comma-ok เสมอ

เวลาอ่าน map จำไว้ว่า key ที่ไม่มีจะคืน zero value ไม่ใช่ error
(zero value = ค่าเริ่มต้นของชนิดนั้น ๆ เช่น int เป็น 0, string เป็นข้อความว่าง, pointer เป็น nil)
ดังนั้น inv["ghost"].Qty ได้ 0 ซึ่งแยกไม่ออกจาก "มีของแต่หมดสต็อก" → ใช้ p, ok := inv[k] ทุกครั้งที่สองกรณีนี้ มีความหมายต่างกัน

🗣 ศัพท์ที่เพิ่งเรียนไป
addressable / non-addressable
"มีที่อยู่ให้ชี้ได้ / ไม่มี" — ทดสอบคร่าว ๆ คือเขียน & นำหน้าแล้วคอมไพล์ผ่านไหม (ยกเว้นท่า &Point{1, 2} ที่ Go เปิดช่องให้เป็นพิเศษ — ตัว composite literal ไม่ addressable แต่เขียนติด & แบบนี้ผ่าน) · เจอ error ที่ลงท้ายว่า in map อย่างบรรทัดข้างบน ให้ถามว่า "อันนี้เพราะ map value เป็น non-addressable ใช่ไหม" — จบใน 1 ประโยค
(ถ้าเป็น cannot assign to ... เฉย ๆ ยังสรุปไม่ได้ว่าเรื่อง map — เขียนทับตัวอักษรใน string หรือทับค่าที่ฟังก์ชันเพิ่งคืนมา ก็ขึ้นข้อความนี้เหมือนกัน)
rehash
ตอนที่ map โตแล้วย้ายของทุก key ไปวางที่ใหม่ · เป็นคำที่ใช้ตอบเหตุผล ว่าทำไม map value ถึง non-addressable
zero value / comma-ok
ค่าเริ่มต้นที่ Go ใส่ให้เอง กับ ท่า v, ok := m[k] ที่รับสองค่า · พูดว่า "ตรงนี้ได้ zero value มา ไม่ได้แปลว่า key มีจริง ใช้ comma-ok ด้วยดีกว่า"
ท่าเดียวกันนี้ยังใช้กับ type assertion (v, ok := x.(T)) และ channel (v, ok := <-ch) ด้วย — เจอคำนี้ที่ไหนก็แปลว่า "ค่าที่สองบอกว่าของจริงมีไหม"

4 · จุดที่กฎนี้เล่นงานคุณ #3: receiver ของ method

Method ก็คือฟังก์ชันที่มี parameter พิเศษข้างหน้าชื่อ receiver

func (a *Account) Deposit(amount float64) error { … } └────┬────┘ └───┬───┘ └──────┬───────┘ └─┬─┘ receiver ชื่อ method parameter return list type └──────────────────── signature ─────────────┘ receiver type = *Account (pointer receiver) base type = Account (ชนิดที่ method นี้สังกัดอยู่)

ต่างจากฟังก์ชันธรรมดาตรงวงเล็บชุดแรกก่อนชื่อเท่านั้น — วงเล็บชุดนั้นเรียกว่า receiver และมันคือตัวบอกว่า method นี้ "ห้อยอยู่กับ" ชนิดไหน เวลาเรียกจึงเขียนเป็น acc.Deposit(500)

func (u User) — valuefunc (u *User) — pointer
ได้อะไรมาcopy ของ structที่อยู่ของ struct จริง
แก้ field แล้วมีผลข้างนอก❌ ไม่มี✅ มี
struct ใหญ่ก็อปทั้งก้อนทุกครั้งส่งแค่ 8 bytes
เหมาะกับgetter (method ที่มีไว้อ่านค่าอย่างเดียว)ทุกอย่างที่ต้องแก้ค่า
type Account struct{ Balance float64 }

func (a Account)  Show() float64      { return a.Balance }
func (a *Account) Deposit(x float64) { a.Balance += x }

acc := Account{Balance: 1000}
acc.Deposit(500)              // Go แปลงให้เป็น (&acc).Deposit(500)
fmt.Println(acc.Show())      // 1500
syntactic sugar ที่ทำให้งง

acc ไม่ใช่ pointer แต่เรียก acc.Deposit() ได้ เพราะ Go ใส่ & ให้อัตโนมัติเมื่อตัวแปร addressable — เวลาเขียนจึงแทบไม่รู้สึกต่าง แต่ผลลัพธ์ต่างกันสิ้นเชิง ต้องดูที่ประกาศ method เสมอ ไม่ใช่ดูที่จุดเรียก

🏦 เทียบให้เห็นภาพ — สลิปที่ปริ้นมา กับ บัญชีจริงในธนาคาร

· func (a Account) Show() — value receiver = เราถือสลิปยอดคงเหลือที่ปริ้นออกมา อ่านยอดได้ครบ ไม่มีปัญหา
· func (a *Account) Deposit() — pointer receiver = ทำรายการผ่านเลขบัญชี เงินเข้าบัญชีจริง

เอาปากกาไปเขียนเพิ่มยอดลงบนสลิปที่ถืออยู่ — ตัวเลขบนกระดาษเปลี่ยนจริง แต่เงินในบัญชีไม่ขึ้นสักบาท นั่นคือการเขียน a.Balance += x บน value receiver

ที่ไม่เหมือนกัน — และเป็นจุดอันตราย: เขียนบนสลิปเรารู้ตัวทันทีว่าไม่มีประโยชน์ แต่ Go ยอมให้เขียนแบบนี้เงียบ ๆ คอมไพล์ผ่าน รันผ่าน ไม่มี error มีแค่ค่าที่ไม่เปลี่ยน
อีกจุด: ถ้า field ข้างในเป็น map หรือ slice ต่อให้เป็น value receiver ก็ยังแตะของจริงที่ปลายทางได้ — แต่ไม่เท่ากันทั้งสองชนิด: map เพิ่ม/แก้ key ได้จริง · slice เขียนทับช่องเดิม (s[0] = x) ได้จริง แต่ append ไม่ออกไปข้างนอก เพราะ append ไปแก้ที่ตัว header (Len/Cap/Data) ซึ่งเป็นของที่ก็อปมา (ดูเกณฑ์ข้อ slice ด้านล่าง และภารกิจข้อ 5 ท้ายบท)

🗣 ศัพท์ที่เพิ่งเรียนไป
receiver
วงเล็บชุดแรกก่อนชื่อ method — ตัวบอกว่า method นี้สังกัดชนิดไหน
value receiver / pointer receiver
รับ "สลิป" กับ รับ "เลขบัญชี" · คำที่ต้องใช้ให้ตรงที่สุดในบทนี้ — เวลาแก้แล้วค่าไม่เปลี่ยน ให้ถาม senior หรือพิมพ์ถาม AI ว่า "method นี้เป็น value receiver หรือ pointer receiver" จะได้คำตอบตรงจุดทันที ต่างจากถามว่า "ทำไมมันไม่เปลี่ยน"
signature
"หน้าตาของ method ทั้งชุด" = receiver + ชื่อ + parameter + ชนิดที่คืนกลับ (เส้นที่ลากไว้ในภาพหัวข้อนี้) · "เปลี่ยน signature ไม่ได้ เดี๋ยวโค้ดที่เรียกอยู่พัง"
เกร็ด: ตอน Go เช็กว่า type หนึ่งตรงกับ interface ไหม มันเทียบแค่ ชื่อ + parameter + ชนิดที่คืนกลับ ไม่ได้เอา receiver มาเทียบด้วย
syntactic sugar
"ท่าเขียนสั้นที่ภาษาแปลให้เอง" เช่น acc.Deposit(500) ที่ Go เติม & ให้เป็น (&acc).Deposit(500)

เกณฑ์ตัดสินจาก Go Code Review Comments

นี่คือเกณฑ์ที่ reviewer จริงใช้ตัดสิน — ย่อจาก Go Code Review Comments · Receiver Type:

5 · ทดสอบความจำ

1. ข้อความไหนถูกต้องเกี่ยวกับการส่งค่าใน Go?

Go ไม่มี pass by reference เลย ส่ง slice = ก็อป slice header (24 bytes) ที่บังเอิญชี้ array เดิม จึงดูเหมือน by reference แต่กลไกคือ by value ทั้งหมด

พิสูจน์ได้: ฟังก์ชันที่รับ []int append ข้างในแล้ว len ของตัวแปรข้างนอกไม่เปลี่ยน เพราะแก้ได้แค่ header ที่ก็อปมา (Rob Pike อธิบายไว้)

2. m := map[string]User{...} แล้ว m["a"].Age = 31 จะเกิดอะไรขึ้น?

Go เลือกที่จะ ห้ามตั้งแต่ compile เพราะถ้าปล่อยให้รันได้แล้ว ค่าไม่เปลี่ยน จะเป็นบั๊กที่หาไม่เจอ — map value เป็น non-addressable

3. struct มี sync.Mutex อยู่ข้างใน ควรใช้ receiver แบบไหน?

copy struct ที่มี mutex = ได้ mutex คนละตัว → lock ไม่ป้องกันอะไรเลย เป็นบั๊ก concurrency ที่เงียบมาก go vet จะเตือนเรื่องนี้ให้

4. for _, u := range users แล้วแก้ u.Name ไม่มีผล เพราะอะไร?

range ก็อป element ใส่ตัวแปรวนลูปทุกรอบ ใช้ users[i].Name = ... หรือประกาศ slice เป็น []*User ถ้าตั้งใจให้แก้ได้

6 · ลงมือทำ

ภารกิจ: แปลง Inventory ให้แก้ค่าได้จริง

  1. ใน Playground สร้าง type Product struct{ Name string; Qty int } และ inv := map[string]Product{...}
  2. ลองเขียน inv["a"].Qty = 5 — อ่าน error ที่ compiler พ่นออกมาให้ครบ คุ้นหน้ามันไว้
  3. ซ่อมด้วยวิธี "อ่าน–แก้–ยัดกลับ"
  4. ซ่อมอีกรอบด้วย map[string]*Product เทียบว่าโค้ดสั้นลงแค่ไหน
  5. ประกาศชนิดใหม่ครอบ map เดิม แล้วเขียน method แบบ value receiver:
    type Inventory struct {
        items map[string]*Product
    }
    
    func (inv Inventory) Add(p *Product) {   // ← value receiver
        inv.items[p.Name] = p
    }
    เรียก Add แล้ว Println(len(inv.items)) — ผลที่ควรเห็น: จำนวนเพิ่มขึ้นจริง ทั้งที่เป็น value receiver (เพราะ inv ที่ก็อปมา มี field items ที่เป็น map header ซึ่งยังชี้ตารางเดียวกันกับของเดิม — เหมือน slice header ในบทที่ 06 เป๊ะ ไม่ใช่ข้อยกเว้นของกฎ pass by value แต่อย่างใด)
  6. ท้าทาย: เพิ่ม field count int ใน Inventory แล้วให้ Add ทำ inv.count++ ด้วย เรียก Add 3 ครั้ง แล้ว Println(inv.count) — ผลที่ควรเห็น: ได้ 0 ไม่ใช่ 3 อธิบายให้ได้ว่าทำไม items ถึงเพิ่มได้แต่ count เพิ่มไม่ได้ ทั้งที่อยู่ใน struct ก้อนเดียวกัน แล้วซ่อมด้วยการเปลี่ยนเป็น func (inv *Inventory) Add(...)
ชัยชนะของบทนี้

เวลาเห็นโค้ดที่ "แก้แล้วไม่เปลี่ยน" คุณจะไล่ได้ใน 3 คำถาม — ตัวนี้เป็น copy รึเปล่า? มัน addressable ไหม? receiver เป็น value หรือ pointer?

อ่านต่อจากต้นทาง

แหล่งหลักที่แนะนำ: Go Code Review Comments — หัวข้อ Receiver Type เป็นเกณฑ์ที่ทีม Go ใช้ review จริง ยาวไม่ถึงหน้าเดียว ควรอ่านให้จบทั้งหน้าเลยเพราะหัวข้ออื่น (Indent Error Flow, Don't Panic) จะเจอในคอร์สวันถัด ๆ ไปแน่นอน

💬 ติดตรงไหนถามได้เลย — โดยเฉพาะข้อ 6 ของภารกิจ ถ้าอธิบายไม่ได้ว่าทำไม value receiver ถึงพัง ให้ถามมาเลย นั่นคือจุดที่ความเข้าใจ กำลังจะแน่นพอดี

อ้างอิง