บทที่ 1 · Part 1 — Foundations and Judgment
Go Conventions and Engineering Judgment
เข้าใจว่า Go กำหนดอะไรไว้แล้ว convention เกิดตรงไหน และตัดสินอย่างไรเมื่อไม่มีคำตอบเดียว
สมมติว่าคุณเปิด repository ของ Go service 2 ระบบที่ทำงานคล้ายกัน ระบบแรกใช้
net/http กับ concrete types เกือบทั้งหมด ส่วนอีกระบบมี framework, dependency injection
container และ interface อยู่ทุก package ทั้ง 2 ระบบผ่าน gofmt, compile ได้ และมี test
คำถามที่คนเพิ่งเข้าทีมมักถามคือ แล้วแบบไหนคือ Go ที่ถูกต้อง?
คำตอบคือทั้งคู่อาจถูกตามกติกาของภาษา แต่มี readability, testability, dependency cost และ
operational risk ต่างกัน gofmt ทำให้หน้าตาโค้ดสม่ำเสมอ แต่มันไม่ได้บอกว่าเราควรแบ่ง package
อย่างไร ควรสร้าง interface เมื่อไร หรือควรเลือก Chi, Fiber หรือ standard library
จบบทนี้คุณจะ
- อธิบายได้ว่า convention เข้ามาช่วยตรงไหนที่ภาษาไม่ได้ตัดสินให้
- แยก Guarantee, Idiom, Convention, Recommended default และ Trade-off ได้
- ใช้ Effective Go, Google, Uber และ production evidence โดยไม่ยก guide ใดเป็นกฎของภาษา
- บันทึกคำแนะนำของทีมพร้อมเหตุผล ข้อยกเว้น และเงื่อนไขที่ต้องกลับมาทบทวน
ทำไม Go Code ที่ถูกต้องจึงต่างกันมาก
Go มี opinion ชัดเจนในแกนเล็ก ๆ ของภาษาและเครื่องมือ เช่น syntax, type system,
memory model, compatibility promise, package contract และการ format ด้วย gofmt
สิ่งเหล่านี้ช่วยตัดตัวเลือกที่ไม่จำเป็นออก แต่ Go ตั้งใจเปิดพื้นที่อีกมากให้ programmer และทีมตัดสิน
เช่น architecture, package boundaries, error taxonomy, dependency injection, HTTP router,
database library และวิธีจัด lifecycle ของ service
ดังนั้นประโยคว่า “Go ไม่มี opinion” จึงกว้างเกินไป ประโยคที่แม่นกว่าคือ Go มี opinion สูงกับ แกนของภาษาและ tooling แต่ไม่ได้ออกแบบ application architecture แทนเรา ช่องว่างนี้ไม่ใช่ ข้อบกพร่อง เพราะงานแต่ละชนิดมี constraint ต่างกัน แต่ถ้าทีมไม่ตกลง convention ช่องว่างเดียวกัน จะกลายเป็นข้อถกเถียงซ้ำ ๆ ใน code review
Convention คืออะไร
Convention คือข้อตกลงร่วมของทีมสำหรับตัวเลือกที่ compiler หรือ package contract ไม่ได้
ตัดสินให้ เช่น package ที่รับ HTTP request จะตั้งชื่อ handler หรือ transport ก็ได้ ภาษาไม่ห้าม
ทั้งคู่ แต่การเลือก 1 แบบให้สม่ำเสมอช่วยให้คนหาโค้ดเจอเร็วขึ้นและ review เฉพาะสาระได้มากขึ้น
convention ที่ดีต้องมีขอบเขตและเหตุผล ไม่ใช่ “ทำเพราะเคยเห็นที่บริษัทใหญ่” อย่างน้อยควรตอบได้ว่า
- ใช้กับ code ส่วนใด
- ปัญหาอะไรที่กำลังลด
- มีข้อยกเว้นเมื่อใด
- หลักฐานหรือเหตุการณ์ใดจะทำให้กลับมาทบทวน
ตัวอย่างเช่น “service นี้รับ context.Context เป็น parameter แรกของ operation ที่ทำ I/O เพื่อให้
deadline และ cancellation เดินทางข้าม boundary ได้; ไม่เก็บ context ไว้ใน struct ยกเว้น API ที่
package contract ออกแบบไว้เช่นนั้น” นี่นำไปใช้ได้ชัดกว่า “ทุก function ต้องรับ context”
คำ 5 คำที่ต้องแยกให้ออก
| ระดับ | หมายถึง | ตัวอย่าง |
|---|---|---|
| Guarantee | สิ่งที่ language หรือ package contract รับรอง | map iteration ไม่มีลำดับที่รับรอง |
| Idiom | รูปแบบที่เข้ากับกลไกและความคาดหวังของ Go | ตรวจ error ใกล้จุดเรียกแล้ว return ให้ flow ตรงไปตรงมา |
| Convention | ข้อตกลงที่ลดตัวเลือกภายในขอบเขตหนึ่ง | ชื่อ package, error code และ project layout ของทีม |
| Recommended default | จุดเริ่มที่คอร์สแนะนำ พร้อมเหตุผลและเงื่อนไขเปลี่ยน | producer return concrete type; consumer สร้าง interface เท่าที่ใช้ |
| Trade-off | คำตอบที่ขึ้นกับ workload หรือ constraint | mutex เทียบกับ channel, Chi เทียบกับ Fiber |
คำว่า best practice ในคอร์สนี้หมายถึง Recommended default ไม่ได้หมายถึงคำตอบนิรันดร์ เราจะเขียนให้ครบว่า “เริ่มด้วย X เพราะ Y และพิจารณา Z เมื่อเจอเงื่อนไขนี้” ถ้าคำแนะนำมีแต่ X แต่ไม่มี Y หรือ Z มันยังไม่ช่วยให้ผู้อ่านตัดสินใจในสถานการณ์ใหม่
เราควรฟังใครเมื่อคำแนะนำไม่ตรงกัน
แหล่งข้อมูลแต่ละชนิดตอบคำถามคนละระดับ ให้ไล่จากฐานที่แข็งที่สุดขึ้นมา:
- Language specification, memory model และ package documentation บอกว่าโปรแกรมพึ่งพาอะไรได้
- Official documentation และ release notes บอก behavior, compatibility และเครื่องมือปัจจุบัน
- Effective Go และ Go Code Review Comments อธิบาย idiom และปัญหาที่พบซ้ำ
- Google, Uber และ production guides ให้ convention ที่ผ่านบริบทขององค์กรนั้นมาแล้ว
- Team convention เลือก default ให้ codebase นี้โดยระบุเหตุผลและข้อยกเว้น
- Tests, benchmark, profile และ production metrics ตัดสินเรื่องที่ขึ้นกับ workload จริง
Google หรือ Uber guide จึงมีประโยชน์มาก แต่เป็น production witness ไม่ใช่ language authority เราสามารถเลือกคำแนะนำของ Uber มาเป็น default ได้เมื่อเหตุผลตรงกับระบบของเรา และปฏิเสธได้เมื่อ constraint ต่างกัน หลักเดียวกันใช้กับ library: “standard library first” เป็นจุดเริ่ม ไม่ใช่คำสั่งห้าม dependency ถ้า library ทำให้ intent, ownership หรือ lifecycle ชัดขึ้นและคุ้มกับ cost ก็ควรแนะนำ
Effective Go อยู่ตรงไหนในปี 2026
Effective Go ยังอธิบาย formatting, naming, zero value,
methods, interfaces, defer, errors และ concurrency ได้ดี แต่หน้าเอกสารระบุเองว่าเนื้อหาเขียนขึ้น
ในปี 2009 และไม่ได้อัปเดตอย่าง active จึงไม่ครอบคลุม modules, generics, fuzzing, error chains,
context, modern collection APIs และ production tooling รุ่นปัจจุบัน
คอร์สนี้จึงใช้ Effective Go เป็น หนึ่งในรากของ idiom ไม่ใช่ชื่อบทหรือโครง syllabus ตัวอย่างเช่นแนวคิดเรื่อง interface ขนาดเล็กยังสำคัญ แต่ต้องประกบด้วยคำแนะนำสมัยใหม่ว่า producer มัก return concrete type และ consumer ควรเป็นเจ้าของ interface เท่าที่ตัวเองใช้ เราจะลงมือทำใน Packages as APIs
Version เป็นส่วนหนึ่งของคำแนะนำ
style guide ทุกฉบับต้องอ่านพร้อมวันที่และ Go version เพราะ workaround ที่ถูกต้องเมื่อหลายปีก่อน
อาจกลายเป็น noise วันนี้ ตัวอย่างเช่น semantics ของ loop variable เปลี่ยนตั้งแต่ Go 1.22 และ
typed atomic types มีใน sync/atomic ตั้งแต่ Go 1.19
module example.com/settlement
go 1.25.0
toolchain go1.26.5
go directive กำหนด minimum Go version และมีผลต่อ language semantics ส่วน toolchain
เสนอ toolchain ที่ควรใช้เมื่อ environment มีรุ่นเก่ากว่า ก่อนใช้คำแนะนำจาก guide ให้ตรวจ baseline
ของ module, CI image และ deployment build ให้เล่าเรื่องเดียวกัน
go version
go env GOTOOLCHAIN GOFLAGS
go test ./...
วิธีตัดสินเมื่อไม่มีคำตอบเดียว
เมื่อ review มี comment ว่า “ห้าม return nil slice” อย่าเริ่มจากการโหวต ให้เดินตามลำดับนี้:
- หา guarantee ก่อน:
nilslice ใช้len,rangeและappendได้ - หา boundary contract: JSON response อาจต้องเป็น
[]ไม่ใช่null - เลือก default ที่ง่ายและอ่านชัด: internal function ไม่ต้อง allocate ถ้า caller ไม่แยก 2 ค่า
- ใช้ tool หรือ test พิสูจน์จุดที่สำคัญ: เพิ่ม contract test ที่ HTTP boundary
- บันทึก convention: “กำหนด nil/empty ที่ boundary; ภายในใช้ค่าที่ contract อนุญาต”
reasoning loop นี้ใช้ได้ตลอดคอร์ส: guarantee → contract → readable default → evidence → documented convention มันช่วยให้ทีมย้ายจาก “guide ไหนชนะ” ไปสู่ “ระบบนี้ต้องการอะไร”
แบบฝึกทบทวน
ลองจัดแต่ละข้อความเป็น Guarantee, Idiom, Convention, Recommended default หรือ Trade-off แล้วระบุหลักฐานที่ต้องใช้ก่อนเปิดคำตอบจากแหล่งอ้างอิง:
- “ผลจากการวน map จะเรียงเหมือนเดิมทุกครั้ง”
- “ชื่อ package ของทีมต้องยาวไม่เกิน 1 คำ”
- “operation ที่ข้าม process รับ
context.Contextเป็น parameter แรก” - “Fiber เร็วกว่า Chi จึงเหมาะกับทุก service”
- “HTTP response ของ endpoint นี้ต้อง encode collection ว่างเป็น
[]”
ข้อ 1 ต้องตรวจ specification และพบว่าเป็นข้อความที่พึ่งพาไม่ได้ ข้อ 2 เป็น convention ข้อ 3 เป็น recommended default ที่อิง idiom และ contract ข้อ 4 เป็น trade-off ที่ต้อง benchmark กับ workload จริง ส่วนข้อ 5 เป็น boundary convention ซึ่งต้องยืนยันด้วย API contract และ test
Checklist ก่อนใช้คำว่า Idiomatic
- ระบุได้หรือไม่ว่ากำลังพูดถึง guarantee, idiom, convention, default หรือ trade-off
- ตรวจ Go version และวันที่ของ source แล้วหรือยัง
- คำแนะนำทำให้ contract, ownership หรือ lifecycle ชัดขึ้นจริงหรือไม่
- รู้ข้อยกเว้นและ trigger ที่ต้องกลับมาทบทวนหรือยัง
- ถ้าเลือก library รู้หรือไม่ว่ามันทดแทนโค้ดอะไรและเพิ่ม dependency cost เท่าใด
- เรื่องที่ขึ้นกับ workload มี test, benchmark, profile หรือ production evidence รองรับหรือไม่
อ่านต่อจากแหล่งต้นทาง: Go specification, Go toolchains, Go 1 compatibility, Go Code Review Comments, Google Go Style และ Uber Go Style Guide